fix: carry the auth detail in the ACP account label (#1573)

## Summary

The ACP `auth/status` account (`label`, `active`, `name`, `email`,
`organization`) has no field for the identity's `detail`: an API key's
source, a gateway's host, or "OAuth token". It was only in
`_meta.claudeCode.detail`, so a client that shows the standard fields
could not tell which API key or gateway is in use.

`toAcpAuthStatus` now appends the detail to the account's `label` in
parentheses:

- `Anthropic API key (ANTHROPIC_API_KEY)`
- `Custom model gateway (gw.example.com)`
- `Claude subscription (OAuth token)`

An identity without a detail keeps its label as it was (`Claude Max`).

## Unchanged

- `_meta.claudeCode` still carries `kind`, `detail` and `plan` on their
own.
- The `_auth/status_update` extension payload (`label` and `detail` as
separate fields).
- The mapping is shared, so v1 and v2 report the same label in
`auth/status` and `auth/status_update`.

## Testing

- `src/tests/v1/auth-status.test.ts` expectations updated for the API
key and gateway labels.
- `npm run test:run`, `npm run check` and `npm run build` pass.
This commit is contained in:
Ben Brandt
2026-11-10 14:21:33 +01:00
committed by GitHub
parent de521df295
commit 3d6eb7b5b6
3 changed files with 10 additions and 7 deletions

View File

@@ -228,7 +228,7 @@ The adapter keeps them where they are.
## Auth source display
The connection-scoped `_auth/status_update` continues to report agent-owned authentication, not client-owned `providers/set` routing. The adapter also reports the same identity through the ACP `auth/status` method and `auth/status_update` notification (advertised as `auth.status`), with `kind`, `detail` and `plan` under the account's `_meta.claudeCode`. Each notification is sent when its own payload changed. `auth/status` answers with the identity last reported and starts a new read, whose change arrives as a notification. When the active key source is `apiKeyHelper`, the adapter reads the effective helper command through SDK `resolveSettings` / the session's `SettingsManager`, including programmatic settings overrides.
The connection-scoped `_auth/status_update` continues to report agent-owned authentication, not client-owned `providers/set` routing. The adapter also reports the same identity through the ACP `auth/status` method and `auth/status_update` notification (advertised as `auth.status`), with `kind`, `detail` and `plan` under the account's `_meta.claudeCode`. The account's `label` also carries a `detail` in parentheses, e.g. `Anthropic API key (ANTHROPIC_API_KEY)`. Each notification is sent when its own payload changed. `auth/status` answers with the identity last reported and starts a new read, whose change arrives as a notification. When the active key source is `apiKeyHelper`, the adapter reads the effective helper command through SDK `resolveSettings` / the session's `SettingsManager`, including programmatic settings overrides.
A command containing the whole word `central` and the word sequence `proxy start` is displayed as:

View File

@@ -104,8 +104,10 @@ export type AcpAuthStatus = Pick<AuthStatusResponse, "state" | "accounts">;
* - No identity read yet (`undefined`): `unknown`, no accounts.
* - `kind: "none"`: `unauthenticated`, no accounts.
* - Any other kind: `authenticated`, with the identity as the one account,
* marked `active`. `_meta.claudeCode` carries the `kind`, `detail` and
* `plan` that the account fields have no place for.
* marked `active`. The account has no field for `detail`, so its label
* carries it in parentheses, e.g. "Anthropic API key (ANTHROPIC_API_KEY)",
* for clients that show only the standard fields. `_meta.claudeCode`
* carries the `kind`, `detail` and `plan` on their own.
*
* `--hide-claude-auth` does not change the payload: it reports the login the
* CLI holds, also when the subscription guard refuses to use it.
@@ -117,7 +119,8 @@ export function toAcpAuthStatus(status: AuthStatus | undefined): AcpAuthStatus {
if (status.kind === "none") {
return { state: "unauthenticated" };
}
const account: AuthAccount = { label: status.label, active: true };
const label = status.detail ? `${status.label} (${status.detail})` : status.label;
const account: AuthAccount = { label, active: true };
if (status.account?.email) account.email = status.account.email;
if (status.account?.organization) account.organization = status.account.organization;
account._meta = {

View File

@@ -220,7 +220,7 @@ describe("auth status mappers", () => {
state: "authenticated",
accounts: [
{
label: "Anthropic API key",
label: "Anthropic API key (env)",
active: true,
_meta: { claudeCode: { kind: "api_key", detail: "env" } },
},
@@ -765,7 +765,7 @@ describe("auth status over ACP", () => {
state: "authenticated",
accounts: [
{
label: "Custom model gateway",
label: "Custom model gateway (gw.example.com)",
active: true,
_meta: { claudeCode: { kind: "gateway", detail: "gw.example.com" } },
},
@@ -783,7 +783,7 @@ describe("auth status over ACP", () => {
agent.setAuthStatus({ kind: "api_key", label: "Anthropic API key", detail: "env" });
expect(await agent.authStatusResponse()).toMatchObject({
state: "authenticated",
accounts: [{ label: "Anthropic API key" }],
accounts: [{ label: "Anthropic API key (env)" }],
});
});