Delete the coder's backend picker instead of carrying it as debt

58640ee239b1 · Devin AI · · parent 18b4d7d6de44

Delete the coder's backend picker instead of carrying it as debt

A coder session runs on a thread and the inference proxy takes the model from
that thread's grant, so a client that picked a backend was picking something
nothing reads: --model already says as much when it is passed. defaultBackend,
findBackend, and nextBackend had only tests left calling them, and a debt entry
would have kept a chooser alive for a choice the client does not get to make.

CODER_BACKENDS and backendIds stay, because --model still validates against
that list and the status line still reads its labels from it.

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified packages/openagents-cli/src/coder-backends.ts
  • modified packages/openagents-cli/test/coder-backends.test.ts
  • modified packages/openagents-cli/test/coder-ui.test.ts
  • modified scripts/uncalled-production-symbol-baseline.json

Diff

4 files changed, +15 -56

packages/openagents-cli/src/coder-backends.ts modified +7 -20

@@ -4,8 +4,13 @@

4 4
 * The server owns the real list and publishes it at `GET /api/v3` under
5 5
 * `extensions["chat.openagents"].parameters.model`. This is the client's copy,
6 6
 * kept as data for the same reason the server keeps one: a backend the status
7
 * line offers, the `--model` flag accepts, and Tab cycles through has to be one
8
 * list, or the three drift and the CLI offers something the server refuses.
7
 * line shows and the `--model` flag accepts has to be one list, or the two
8
 * drift and the CLI offers something the server refuses.
9
 *
10
 * Choosing between them is not the client's call today. A coder session runs on
11
 * a thread, and the inference proxy takes the model from that thread's grant,
12
 * so this list is what the flag validates against and what the status line
13
 * names, nothing more.
9 14
 *
10 15
 * Adding a backend is one entry here and one entry on the server. Nothing else
11 16
 * in this package names a backend.

@@ -27,23 +32,5 @@ export const CODER_BACKENDS: readonly CoderBackend[] = [

27 32
  { id: "gemini-3.7-flash", label: "Gemini 3.7 Flash" },
28 33
];
29 34
30
/** The backend a turn uses when nothing named one. */
31
export const defaultBackend = (): CoderBackend => CODER_BACKENDS[0] as CoderBackend;
32
33
/** The backend with this id, or `undefined` when nothing matches. */
34
export const findBackend = (id: string): CoderBackend | undefined =>
35
  CODER_BACKENDS.find((backend) => backend.id === id);
36
37
/**
38
 * The next backend after this one, wrapping at the end.
39
 *
40
 * Cycling rather than toggling is what makes a third backend data: Tab keeps
41
 * working without a second key or a menu.
42
 */
43
export const nextBackend = (current: CoderBackend): CoderBackend => {
44
  const index = CODER_BACKENDS.findIndex((backend) => backend.id === current.id);
45
  return CODER_BACKENDS[(index + 1) % CODER_BACKENDS.length] as CoderBackend;
46
};
47
48 35
/** Every id, for a flag's error message and its accepted values. */
49 36
export const backendIds = (): readonly string[] => CODER_BACKENDS.map((backend) => backend.id);
packages/openagents-cli/test/coder-backends.test.ts modified +5 -29

@@ -1,19 +1,13 @@

1 1
import { describe, expect, it } from "vitest";
2 2
3
import {
4
  backendIds,
5
  CODER_BACKENDS,
6
  defaultBackend,
7
  findBackend,
8
  nextBackend,
9
} from "../src/coder-backends.js";
3
import { backendIds, CODER_BACKENDS } from "../src/coder-backends.js";
10 4
11 5
/**
12
 * The list three surfaces read.
6
 * The list two surfaces read.
13 7
 *
14
 * `--model` takes its accepted values from it, the status line takes its label
15
 * from it, and Tab walks it. These pin the properties those three depend on, so
16
 * adding a backend stays one entry rather than one entry plus three fixes.
8
 * `--model` takes its accepted values from it and the status line takes its
9
 * label from it. These pin the properties those two depend on, so adding a
10
 * backend stays one entry rather than one entry plus two fixes.
17 11
 */
18 12
describe("coder backends", () => {
19 13
  it("names each backend once", () => {

@@ -28,24 +22,6 @@ describe("coder backends", () => {

28 22
    }
29 23
  });
30 24
31
  it("defaults to the first entry, and finds a backend by the id the server takes", () => {
32
    expect(defaultBackend()).toBe(CODER_BACKENDS[0]);
33
    expect(findBackend("gemini-3.7-flash")?.label).toBe("Gemini 3.7 Flash");
34
    expect(findBackend("gpt-4")).toBeUndefined();
35
  });
36
37
  it("reaches every backend by cycling, and returns to the start", () => {
38
    const seen: string[] = [];
39
    let current = defaultBackend();
40
    for (let step = 0; step < CODER_BACKENDS.length; step += 1) {
41
      current = nextBackend(current);
42
      seen.push(current.id);
43
    }
44
45
    expect(new Set(seen)).toEqual(new Set(backendIds()));
46
    expect(current).toBe(defaultBackend());
47
  });
48
49 25
  it("publishes ids the chat API's own enum lists", () => {
50 26
    // These are the values `POST /api/v3/chat/turns` accepts as `model`, so a
51 27
    // change here without the matching server change is a refusal at runtime.
packages/openagents-cli/test/coder-ui.test.ts modified +2 -2

@@ -1,7 +1,7 @@

1 1
import { EventEmitter } from "node:events";
2 2
import { describe, expect, it } from "vitest";
3 3
4
import { CODER_BACKENDS, defaultBackend } from "../src/coder-backends.js";
4
import { CODER_BACKENDS } from "../src/coder-backends.js";
5 5
import { CoderSession, type ReplyChunk, type ReplySource } from "../src/coder-session.js";
6 6
import { CoderTaskRegistry } from "../src/coder-tasks.js";
7 7
import { runCoderUi } from "../src/coder-ui.js";

@@ -262,7 +262,7 @@ describe("runCoderUi", () => {

262 262
263 263
    it("wraps around, so every backend is reachable from one key", async () => {
264 264
      const { session } = await driveSwitchable(CODER_BACKENDS.map(() => "\t"));
265
      expect(session.snapshot().model).toBe(defaultBackend().label);
265
      expect(session.snapshot().model).toBe(CODER_BACKENDS[0]?.label);
266 266
    });
267 267
268 268
    it("does not leave a tab in the composer", async () => {
scripts/uncalled-production-symbol-baseline.json modified +1 -5

@@ -6,8 +6,7 @@

6 6
    "allowed: intentional, permanent exceptions. Each one needs a written reason.",
7 7
    "Rationale: docs/quality/uncalled-production-symbols.md",
8 8
    "2026-08-03: the guard counted names in comments and strings as callers; fixing that revealed 171 findings it had been blind to. They are inherited debt, not approvals, and the list may only shrink.",
9
    "2026-08-23: clean origin/main at df62c1fe already failed the guard with 47 pre-existing findings; the currently flagged refs recorded below are inherited debt, not approvals, and the two stale ledger entries were pruned separately.",
10
    "2026-08-23: running the coder on its own thread took the model choice from the client, so coder-backends' defaultBackend, findBackend, and nextBackend lost their production callers while their tests stayed. Inherited debt: either the proxy takes a client-named model again, or the picker and its tests go."
9
    "2026-08-23: clean origin/main at df62c1fe already failed the guard with 47 pre-existing findings; the currently flagged refs recorded below are inherited debt, not approvals, and the two stale ledger entries were pruned separately."
11 10
  ],
12 11
  "baselinedAt": "2026-07-31",
13 12
  "inheritedDebt": [

@@ -1586,9 +1585,6 @@

1586 1585
    "packages/openagents-cli/src/api-transport.ts#networkPolicyTestLoopbackLayer",
1587 1586
    "packages/openagents-cli/src/browser-launcher.ts#browserLauncherTestLayer",
1588 1587
    "packages/openagents-cli/src/cli.ts#runCliWith",
1589
    "packages/openagents-cli/src/coder-backends.ts#defaultBackend",
1590
    "packages/openagents-cli/src/coder-backends.ts#findBackend",
1591
    "packages/openagents-cli/src/coder-backends.ts#nextBackend",
1592 1588
    "packages/openagents-cli/src/credential-store.ts#credentialStoreTestFileLayer",
1593 1589
    "packages/openagents-cli/src/credential-store.ts#credentialStoreUnavailableLayer",
1594 1590
    "packages/openagents-cli/src/device-authorization-store.ts#pendingDeviceAuthorizationStoreTestLayer",

This page updates live while a promote is in flight · changelog