diff --git a/gui/public/provider-icons/README.md b/gui/public/provider-icons/README.md index 235fa7bf27..39be97558e 100644 --- a/gui/public/provider-icons/README.md +++ b/gui/public/provider-icons/README.md @@ -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, diff --git a/gui/public/provider-icons/devin.svg b/gui/public/provider-icons/devin.svg new file mode 100644 index 0000000000..c0620e2602 --- /dev/null +++ b/gui/public/provider-icons/devin.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/src/provider-icons.ts b/gui/src/provider-icons.ts index 99f46a93c4..e27572e45c 100644 --- a/gui/src/provider-icons.ts +++ b/gui/src/provider-icons.ts @@ -13,6 +13,15 @@ const PROVIDER_ICON_ALIASES: Record = { 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", @@ -124,6 +133,8 @@ const PROVIDER_DISPLAY_NAMES: Record = { xiaomi: "Xiaomi", cursor: "Cursor", deepseek: "DeepSeek", + devin: "Cognition (Devin/Windsurf)", + "devin-cli": "Devin CLI", github: "GitHub", "github-copilot": "GitHub Copilot", "gitlab-duo": "GitLab Duo", diff --git a/src/providers/registry.ts b/src/providers/registry.ts index 27bc1738ba..a46e357390 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -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, diff --git a/tests/providers/devin-cli-adapter.test.ts b/tests/providers/devin-cli-adapter.test.ts index 270832cfa1..91bb4c2464 100644 --- a/tests/providers/devin-cli-adapter.test.ts +++ b/tests/providers/devin-cli-adapter.test.ts @@ -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"; @@ -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[1]; + expect(formatProviderDisplayName("devin", englishT)).toBe("Cognition (Devin/Windsurf)"); + expect(formatProviderDisplayName("devin-cli", englishT)).toBe("Devin CLI"); + }); }); describe("acp handshake frames", () => {