Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions gui/public/provider-icons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,24 @@ at 20px.
`viewBox="0 0 823 823"` by centering the 823x806 trace. Named
`hermes-agent` rather than `hermes` because Hermes is also a provider name
and this directory is one flat namespace.
- `devin.svg` — fetched 2026-09-12 from `https://docs.devin.ai/logo/light.svg`,
Devin's own documentation logo, and CROPPED to its symbol. The source is a
160x24 horizontal lockup: the three-leaf mark occupies the left 24 units and
the `devin` wordmark starts at x=34.2, so a `viewBox="0 0 24 24"` window keeps
the whole symbol and excludes every wordmark path. The thirteen symbol paths
and all seven gradients are verbatim — nothing is translated, so the
`userSpaceOnUse` gradient coordinates stay valid — and only the two wordmark
paths are dropped. Cropping a lockup to its symbol is what `cursor-color.svg`
and `gajae-code.svg` already do; the alternative here was a wordmark in a 20px
box, which this file refuses.

`devin.ai/favicon.svg` sits behind a Vercel security checkpoint that answers
429 with an HTML page, and `cognition.ai` publishes only a raster favicon, so
the docs site is the reachable first-party vector. Windsurf does publish
`windsurf.com/favicon.svg` — a plated `W` — but that names the retired brand
for a provider labelled Cognition. Multi-colour gradient, so it is drawn as an
image and is not a candidate for the masked set.

- `gajae-code.svg` — traced 2026-08-31 from `Yeachan-Heo/gajae-code`
`assets/character.png` (3190496 bytes, 1550x2048 RGBA), the mascot. No SVG
exists upstream: `assets/` and `docs/` hold only raster, `public/` is a 404,
Expand Down
49 changes: 49 additions & 0 deletions gui/public/provider-icons/devin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions gui/src/provider-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ const PROVIDER_ICON_ALIASES: Record<string, string> = {
commandcode: "commandcode-color.svg",
cursor: "cursor-color.svg",
deepseek: "deepseek-color.svg",
/*
* One mark for both Devin providers. `devin` is Cognition's cloud, reached
* through the Windsurf sign-in, and `devin-cli` drives the installed Devin
* CLI; they are two transports into the same product, the meta-model/meta-muse
* shape. Windsurf still publishes its own `W` app icon, but showing it next
* to a row labelled Cognition would name the retired brand.
*/
devin: "devin.svg",
"devin-cli": "devin.svg",
firepass: "firepass-color.svg",
fireworks: "fireworks-color.svg",
github: "github-copilot-color.svg",
Expand Down Expand Up @@ -124,6 +133,8 @@ const PROVIDER_DISPLAY_NAMES: Record<string, string> = {
xiaomi: "Xiaomi",
cursor: "Cursor",
deepseek: "DeepSeek",
devin: "Cognition (Devin/Windsurf)",
"devin-cli": "Devin CLI",
github: "GitHub",
"github-copilot": "GitHub Copilot",
"gitlab-duo": "GitLab Duo",
Expand Down
10 changes: 9 additions & 1 deletion src/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1331,7 +1331,15 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
baseUrl: "https://cli.devin.ai",
authKind: "local",
featured: false,
dashboardPreset: false,
// Reachable from the dashboard's add-provider list. `derive.ts` builds that
// list from `featured || authKind === "key" || dashboardPreset`, and this
// provider is none of the first two, so without this flag the only way to
// add it was to hand-edit config.json — which is how it came to be missing
// from a picker that already had the cloud `devin` row. The other local
// providers (ollama, vLLM, LM Studio) are reachable through `featured`;
// this one stays out of the featured strip because it needs an installed
// CLI and a completed `devin auth login` before it can answer anything.
dashboardPreset: true,
note: "Drives the locally installed Devin CLI over the Agent Client Protocol (`devin acp`, newline-delimited JSON-RPC on stdio). Requires the CLI on PATH and a completed `devin auth login`; no API key is stored by opencodex. Set OPENCODEX_DEVIN_CLI_BIN to point at a specific build, and OPENCODEX_DEVIN_CLI_ALLOW_TOOLS=1 to let the CLI read and write files — the default is to refuse.",
models: [...DEVIN_CLI_MODELS],
defaultModel: DEVIN_CLI_DEFAULT_MODEL,
Expand Down
17 changes: 16 additions & 1 deletion tests/providers/devin-cli-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { DEVIN_CLI_BIN_ENV, resolveDevinCliBinary } from "../../src/adapters/devin-cli/binary";
import { createDevinCliAdapter } from "../../src/adapters/devin-cli/adapter";
import { PROVIDER_REGISTRY } from "../../src/providers/registry";
import { formatProviderDisplayName, providerIconSrc } from "../../gui/src/provider-icons";
import type { AdapterEvent, OcxParsedRequest } from "../../src/types";
import { EventEmitter } from "node:events";
import { PassThrough } from "node:stream";
Expand All @@ -25,9 +26,23 @@ describe("devin-cli registration", () => {
// The installed CLI carries its own credentials from `devin auth login`,
// so the proxy must never ask for or hold a key for this provider.
expect(entry?.authKind).toBe("local");
expect(entry?.dashboardPreset).toBe(false);
// Addable from the dashboard. It is neither `featured` nor key-auth, so this
// flag is the only thing that puts it in the add-provider list; without it
// the provider existed but could only be reached by hand-editing config.
expect(entry?.dashboardPreset).toBe(true);
expect(createDevinCliAdapter({ adapter: "devin-cli", baseUrl: "https://cli.devin.ai" }).name).toBe("devin-cli");
});

test("both Devin providers render the Devin mark and a readable name", () => {
// Neither id had an icon alias, so the dashboard drew a coloured initial
// tile for both, and the title-cased fallback turned the local one into
// "Devin Cli".
expect(providerIconSrc("devin")).toBe("/provider-icons/devin.svg");
expect(providerIconSrc("devin-cli")).toBe("/provider-icons/devin.svg");
const englishT = ((_key: string, fallback?: string) => fallback ?? "") as Parameters<typeof formatProviderDisplayName>[1];
expect(formatProviderDisplayName("devin", englishT)).toBe("Cognition (Devin/Windsurf)");
expect(formatProviderDisplayName("devin-cli", englishT)).toBe("Devin CLI");
});
});

describe("acp handshake frames", () => {
Expand Down
Loading