@adecore/agents-react 0.31.5 → 0.31.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,12 @@
1
+ import { siGithubcopilot } from 'simple-icons';
1
2
  /*
2
- * The marks of Anthropic, OpenAI and xAI, and a monogram for a provider with no mark of its own. Lucide has no brand marks, so these are the icon rule's
3
+ * The marks of Anthropic, OpenAI, xAI and GitHub Copilot, and a monogram for a provider with no mark of its own. Lucide has no brand marks, so these are the icon rule's
3
4
  * exception for provider marks. One path each, in `currentColor`, so a row decides how loud the logo is.
4
5
  */
5
6
  export const PROVIDER_PATHS = {
6
7
  claude: 'M4.709 15.955l4.72-2.647.079-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.377-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.048.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.607.212-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.914-1.316-.012.01z',
7
8
  codex: 'M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.554 5.554 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.019.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.001zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z',
8
9
  nanogpt: 'M5 20V4h2.6l8.8 11.2V4H19v16h-2.6L7.6 8.8V20z',
9
- grok: 'M6.469 8.776 16.512 23h-4.464L2.005 8.776H6.47Zm-.004 7.9 2.233 3.164L6.467 23H2l4.465-6.324ZM22 2.582V23h-3.659V7.764L22 2.582ZM22 1l-9.952 14.095-2.233-3.163L17.533 1H22Z'
10
+ grok: 'M6.469 8.776 16.512 23h-4.464L2.005 8.776H6.47Zm-.004 7.9 2.233 3.164L6.467 23H2l4.465-6.324ZM22 2.582V23h-3.659V7.764L22 2.582ZM22 1l-9.952 14.095-2.233-3.163L17.533 1H22Z',
11
+ copilot: siGithubcopilot.path
10
12
  };
@@ -76,7 +76,7 @@ export function ToggleLine({ icon, label, brand, detail, command, look, path, op
76
76
  const classes = clsx(
77
77
  // Fix the width so long commands reach the truncation inside the negative margins.
78
78
  'group/line -mx-1 mb-0.5 flex h-7 w-[calc(100%+8px)] items-center gap-2 rounded-md px-1 text-left text-xs text-text-muted', onToggle !== undefined && 'hover:bg-surface-hover', failed && 'text-status-error', className);
79
- const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: clsx(ROW_GUTTER, live && 'text-accent'), children: icon }), _jsxs("span", { className: clsx('shrink-0', live && 'chat-live-text'), children: [brand !== undefined && _jsx("span", { className: BRAND, children: brand }), brand !== undefined && label !== '' && ' ', label] }), look !== undefined ? (_jsxs(_Fragment, { children: [look.run.detail !== '' && (_jsx("span", { "data-find-field": "summary", className: "min-w-0 truncate font-mono text-code text-text-faint", children: _jsx(LookDetail, { run: look.run, chatId: look.chatId }) })), _jsx(LookTags, { run: look.run })] })) : (detail && (_jsx("span", { "data-find-field": "summary", className: "min-w-0 truncate font-mono text-code text-text-faint", children: command ? _jsx(ShellCommand, { command: detail }) : path ? _jsx(MiddleTruncate, { text: detail }) : detail }))), _jsx("span", { className: "grow" }), trailing, time !== undefined && _jsx(RowTime, { at: time }), failed && _jsx(Icon, { icon: X, size: 12, className: "shrink-0" }), onToggle !== undefined && (_jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90') }))] }));
79
+ const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: clsx(ROW_GUTTER, live && 'text-accent'), children: icon }), _jsxs("span", { className: "flex min-w-0 items-baseline gap-2", children: [_jsxs("span", { className: clsx('shrink-0', live && 'chat-live-text'), children: [brand !== undefined && _jsx("span", { className: BRAND, children: brand }), brand !== undefined && label !== '' && ' ', label] }), look !== undefined ? (_jsxs(_Fragment, { children: [look.run.detail !== '' && (_jsx("span", { "data-find-field": "summary", className: "min-w-0 truncate font-mono text-code text-text-faint", children: _jsx(LookDetail, { run: look.run, chatId: look.chatId }) })), _jsx(LookTags, { run: look.run })] })) : (detail && (_jsx("span", { "data-find-field": "summary", className: "min-w-0 truncate font-mono text-code text-text-faint", children: command ? _jsx(ShellCommand, { command: detail }) : path ? _jsx(MiddleTruncate, { text: detail }) : detail })))] }), _jsx("span", { className: "grow" }), trailing, time !== undefined && _jsx(RowTime, { at: time }), failed && _jsx(Icon, { icon: X, size: 12, className: "shrink-0" }), onToggle !== undefined && (_jsx(Icon, { icon: ChevronRight, size: 12, className: clsx('shrink-0 text-text-faint transition-transform', open && 'rotate-90') }))] }));
80
80
  if (onToggle === undefined) {
81
81
  return _jsx("div", { className: classes, children: content });
82
82
  }
@@ -13,6 +13,7 @@ import { AccountDetail } from './AccountDetail.js';
13
13
  import { AddAccountForm } from './AddAccountForm.js';
14
14
  import { CliDetail } from './CliDetail.js';
15
15
  import { InstallPage } from './InstallPage.js';
16
+ import { pickOfTarget } from './pane-target.js';
16
17
  import { CliMark } from './parts.js';
17
18
  import { useProviderAccounts } from '../state/provider-accounts.js';
18
19
  import { useProviders } from '../state/providers.js';
@@ -30,7 +31,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true, instal
30
31
  const host = useChatHost();
31
32
  const { openLogin } = host;
32
33
  const loginBlocked = host.useLoginBlocked(scopeId);
33
- const [picked, setPicked] = useState(() => (target === 'providers.apple' ? { kind: 'cli', cli: 'apple' } : null));
34
+ const [picked, setPicked] = useState(() => pickOfTarget(target, providers));
34
35
  // A provider that never runs in a terminal is no CLI to install: it is turned on in its own detail, so it is always listed.
35
36
  const installed = useMemo(() => providers.filter((provider) => provider.installed || !provider.capabilities.terminal), [providers]);
36
37
  const missing = useMemo(() => providers.filter((provider) => !provider.installed && provider.capabilities.terminal), [providers]);
@@ -47,8 +48,9 @@ export function ProvidersPane({ target = null, detailOf, defaults = true, instal
47
48
  const [seenTarget, setSeenTarget] = useState(target);
48
49
  if (target !== seenTarget) {
49
50
  setSeenTarget(target);
50
- if (target === 'providers.apple') {
51
- setPicked({ kind: 'cli', cli: 'apple' });
51
+ const opened = pickOfTarget(target, providers);
52
+ if (opened !== null) {
53
+ setPicked(opened);
52
54
  }
53
55
  else if (target?.startsWith('providers.') && current?.kind !== 'cli' && provider !== null) {
54
56
  setPicked({ kind: 'cli', cli: provider.kind });
@@ -0,0 +1,15 @@
1
+ import type { AgentKind, ProviderInfo } from '@adecore/agent-contracts';
2
+ export type Picked = {
3
+ kind: 'cli';
4
+ cli: AgentKind;
5
+ } | {
6
+ kind: 'install';
7
+ cli: AgentKind;
8
+ } | {
9
+ kind: 'account';
10
+ id: string;
11
+ } | {
12
+ kind: 'add';
13
+ cli: AgentKind;
14
+ };
15
+ export declare function pickOfTarget(target: string | null, providers: ProviderInfo[]): Picked | null;
@@ -0,0 +1,17 @@
1
+ const INSTALL_TARGET = 'providers.install.';
2
+ /* What a target opens on its own, before any CLI is picked; null for a target that only lands on a row of the CLI already shown. */
3
+ export function pickOfTarget(target, providers) {
4
+ if (target === 'providers.apple') {
5
+ return { kind: 'cli', cli: 'apple' };
6
+ }
7
+ if (!target?.startsWith(INSTALL_TARGET) || target.length === INSTALL_TARGET.length) {
8
+ return null;
9
+ }
10
+ const cli = target.slice(INSTALL_TARGET.length);
11
+ const provider = providers.find((entry) => entry.kind === cli);
12
+ // A CLI the host has not reported yet is taken as missing; once it is installed and signed in, its install page gives way to its detail.
13
+ if (provider !== undefined && (provider.installed || !provider.capabilities.terminal)) {
14
+ return { kind: 'cli', cli };
15
+ }
16
+ return { kind: 'install', cli };
17
+ }
@@ -20,12 +20,19 @@ export function moneyFormat(currency, rate) {
20
20
  return formatMoney(value, code, value !== 0 && Math.abs(value) < 0.01 ? 4 : undefined);
21
21
  };
22
22
  }
23
- export const PROVIDER_LABELS = { claude: 'Claude Code', codex: 'Codex', nanogpt: 'NanoGPT', grok: 'Grok' };
23
+ export const PROVIDER_LABELS = {
24
+ claude: 'Claude Code',
25
+ codex: 'Codex',
26
+ nanogpt: 'NanoGPT',
27
+ grok: 'Grok',
28
+ copilot: 'GitHub Copilot'
29
+ };
24
30
  export const PROVIDER_COLORS = {
25
31
  claude: 'var(--chart-claude)',
26
32
  codex: 'var(--chart-codex)',
27
33
  nanogpt: 'var(--chart-nanogpt)',
28
- grok: 'var(--chart-grok)'
34
+ grok: 'var(--chart-grok)',
35
+ copilot: 'var(--chart-copilot)'
29
36
  };
30
37
  /*
31
38
  * A slot is `2026-09-10` or `2026-09-10T14`, already in the viewer's own zone, so it is read as plain
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/agents-react",
3
- "version": "0.31.5",
3
+ "version": "0.31.7",
4
4
  "description": "React agent chat threads, composers, approvals, provider accounts and usage views.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -67,11 +67,11 @@
67
67
  "test:visuals": "bun --config=integration.bunfig.toml --conditions=source test ./src/chat/visual-viewport.integration.test.ts"
68
68
  },
69
69
  "dependencies": {
70
- "@adecore/agent-contracts": "0.31.5",
71
- "@adecore/editor": "0.31.5",
72
- "@adecore/editor-react": "0.31.5",
73
- "@adecore/intelligent-ui": "0.31.5",
74
- "@adecore/merge": "0.31.5",
70
+ "@adecore/agent-contracts": "0.31.7",
71
+ "@adecore/editor": "0.31.7",
72
+ "@adecore/editor-react": "0.31.7",
73
+ "@adecore/intelligent-ui": "0.31.7",
74
+ "@adecore/merge": "0.31.7",
75
75
  "@codemirror/commands": "^6.11.1",
76
76
  "@codemirror/language": "^6.12.4",
77
77
  "@codemirror/state": "^6.7.6",
@@ -94,14 +94,14 @@
94
94
  "zustand": "^5.0.15"
95
95
  },
96
96
  "peerDependencies": {
97
- "@adecore/ui": "^0.31.5",
97
+ "@adecore/ui": "^0.31.7",
98
98
  "i18next": "^26.4.2",
99
99
  "react": "^19.3.0",
100
100
  "react-dom": "^19.3.0",
101
101
  "react-i18next": "^17.0.15"
102
102
  },
103
103
  "devDependencies": {
104
- "@adecore/ui": "0.31.5",
104
+ "@adecore/ui": "0.31.7",
105
105
  "@types/bun": "^1.4.2",
106
106
  "@types/react": "^19.3.0",
107
107
  "@types/react-dom": "^19.3.0",
@@ -1,12 +1,14 @@
1
1
  import type { UsageProvider } from '@adecore/agent-contracts';
2
+ import { siGithubcopilot } from 'simple-icons';
2
3
 
3
4
  /*
4
- * The marks of Anthropic, OpenAI and xAI, and a monogram for a provider with no mark of its own. Lucide has no brand marks, so these are the icon rule's
5
+ * The marks of Anthropic, OpenAI, xAI and GitHub Copilot, and a monogram for a provider with no mark of its own. Lucide has no brand marks, so these are the icon rule's
5
6
  * exception for provider marks. One path each, in `currentColor`, so a row decides how loud the logo is.
6
7
  */
7
8
  export const PROVIDER_PATHS: Record<UsageProvider, string> = {
8
9
  claude: 'M4.709 15.955l4.72-2.647.079-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.377-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.048.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.607.212-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.914-1.316-.012.01z',
9
10
  codex: 'M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.554 5.554 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.019.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.001zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z',
10
11
  nanogpt: 'M5 20V4h2.6l8.8 11.2V4H19v16h-2.6L7.6 8.8V20z',
11
- grok: 'M6.469 8.776 16.512 23h-4.464L2.005 8.776H6.47Zm-.004 7.9 2.233 3.164L6.467 23H2l4.465-6.324ZM22 2.582V23h-3.659V7.764L22 2.582ZM22 1l-9.952 14.095-2.233-3.163L17.533 1H22Z'
12
+ grok: 'M6.469 8.776 16.512 23h-4.464L2.005 8.776H6.47Zm-.004 7.9 2.233 3.164L6.467 23H2l4.465-6.324ZM22 2.582V23h-3.659V7.764L22 2.582ZM22 1l-9.952 14.095-2.233-3.163L17.533 1H22Z',
13
+ copilot: siGithubcopilot.path
12
14
  };
@@ -154,27 +154,30 @@ export function ToggleLine({
154
154
  const content = (
155
155
  <>
156
156
  <span className={clsx(ROW_GUTTER, live && 'text-accent')}>{icon}</span>
157
- <span className={clsx('shrink-0', live && 'chat-live-text')}>
158
- {brand !== undefined && <span className={BRAND}>{brand}</span>}
159
- {brand !== undefined && label !== '' && ' '}
160
- {label}
161
- </span>
162
- {look !== undefined ? (
163
- <>
164
- {look.run.detail !== '' && (
157
+ {/* The label is sans and the detail mono, so only a shared baseline puts them on one line. */}
158
+ <span className="flex min-w-0 items-baseline gap-2">
159
+ <span className={clsx('shrink-0', live && 'chat-live-text')}>
160
+ {brand !== undefined && <span className={BRAND}>{brand}</span>}
161
+ {brand !== undefined && label !== '' && ' '}
162
+ {label}
163
+ </span>
164
+ {look !== undefined ? (
165
+ <>
166
+ {look.run.detail !== '' && (
167
+ <span data-find-field="summary" className="min-w-0 truncate font-mono text-code text-text-faint">
168
+ <LookDetail run={look.run} chatId={look.chatId} />
169
+ </span>
170
+ )}
171
+ <LookTags run={look.run} />
172
+ </>
173
+ ) : (
174
+ detail && (
165
175
  <span data-find-field="summary" className="min-w-0 truncate font-mono text-code text-text-faint">
166
- <LookDetail run={look.run} chatId={look.chatId} />
176
+ {command ? <ShellCommand command={detail} /> : path ? <MiddleTruncate text={detail} /> : detail}
167
177
  </span>
168
- )}
169
- <LookTags run={look.run} />
170
- </>
171
- ) : (
172
- detail && (
173
- <span data-find-field="summary" className="min-w-0 truncate font-mono text-code text-text-faint">
174
- {command ? <ShellCommand command={detail} /> : path ? <MiddleTruncate text={detail} /> : detail}
175
- </span>
176
- )
177
- )}
178
+ )
179
+ )}
180
+ </span>
178
181
  <span className="grow" />
179
182
  {trailing}
180
183
  {time !== undefined && <RowTime at={time} />}
@@ -14,13 +14,11 @@ import { AddAccountForm } from './AddAccountForm';
14
14
  import { CliDetail } from './CliDetail';
15
15
  import { InstallPage } from './InstallPage';
16
16
  import type { ProviderInstallStep } from './install-steps';
17
+ import { pickOfTarget, type Picked } from './pane-target';
17
18
  import { CliMark } from './parts';
18
19
  import { useProviderAccounts } from '../state/provider-accounts';
19
20
  import { useProviders } from '../state/providers';
20
21
 
21
- // `install` is a CLI picked while it was missing: its install page stays until it is installed and signed in.
22
- type Picked = { kind: 'cli'; cli: AgentKind } | { kind: 'install'; cli: AgentKind } | { kind: 'account'; id: string } | { kind: 'add'; cli: AgentKind };
23
-
24
22
  /* A row of the list with a control of its own beside the part that picks it, so the two never nest. */
25
23
  function ListRow({
26
24
  selected,
@@ -51,7 +49,7 @@ function ListRow({
51
49
  }
52
50
 
53
51
  interface ProvidersPaneProps {
54
- /* The row a search result leads to, by its `searchId`; the pane opens on the CLI it belongs to. */
52
+ /* The row a search result leads to, by its `searchId`; the pane opens on the CLI it belongs to. `providers.install.<kind>` opens on that CLI's install page, or on its detail once it is installed. */
55
53
  target?: string | null;
56
54
  /* The detail of a provider the app draws itself, such as a model the host runs on its own; null draws the CLI's own. */
57
55
  detailOf?(provider: ProviderInfo): ReactNode | null;
@@ -71,7 +69,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true, instal
71
69
  const host = useChatHost();
72
70
  const { openLogin } = host;
73
71
  const loginBlocked = host.useLoginBlocked(scopeId);
74
- const [picked, setPicked] = useState<Picked | null>(() => (target === 'providers.apple' ? { kind: 'cli', cli: 'apple' } : null));
72
+ const [picked, setPicked] = useState<Picked | null>(() => pickOfTarget(target, providers));
75
73
 
76
74
  // A provider that never runs in a terminal is no CLI to install: it is turned on in its own detail, so it is always listed.
77
75
  const installed = useMemo(() => providers.filter((provider) => provider.installed || !provider.capabilities.terminal), [providers]);
@@ -92,8 +90,9 @@ export function ProvidersPane({ target = null, detailOf, defaults = true, instal
92
90
  const [seenTarget, setSeenTarget] = useState(target);
93
91
  if (target !== seenTarget) {
94
92
  setSeenTarget(target);
95
- if (target === 'providers.apple') {
96
- setPicked({ kind: 'cli', cli: 'apple' });
93
+ const opened = pickOfTarget(target, providers);
94
+ if (opened !== null) {
95
+ setPicked(opened);
97
96
  } else if (target?.startsWith('providers.') && current?.kind !== 'cli' && provider !== null) {
98
97
  setPicked({ kind: 'cli', cli: provider.kind });
99
98
  }
@@ -0,0 +1,23 @@
1
+ import type { AgentKind, ProviderInfo } from '@adecore/agent-contracts';
2
+
3
+ // `install` is a CLI picked while it was missing: its install page stays until it is installed and signed in.
4
+ export type Picked = { kind: 'cli'; cli: AgentKind } | { kind: 'install'; cli: AgentKind } | { kind: 'account'; id: string } | { kind: 'add'; cli: AgentKind };
5
+
6
+ const INSTALL_TARGET = 'providers.install.';
7
+
8
+ /* What a target opens on its own, before any CLI is picked; null for a target that only lands on a row of the CLI already shown. */
9
+ export function pickOfTarget(target: string | null, providers: ProviderInfo[]): Picked | null {
10
+ if (target === 'providers.apple') {
11
+ return { kind: 'cli', cli: 'apple' };
12
+ }
13
+ if (!target?.startsWith(INSTALL_TARGET) || target.length === INSTALL_TARGET.length) {
14
+ return null;
15
+ }
16
+ const cli = target.slice(INSTALL_TARGET.length) as AgentKind;
17
+ const provider = providers.find((entry) => entry.kind === cli);
18
+ // A CLI the host has not reported yet is taken as missing; once it is installed and signed in, its install page gives way to its detail.
19
+ if (provider !== undefined && (provider.installed || !provider.capabilities.terminal)) {
20
+ return { kind: 'cli', cli };
21
+ }
22
+ return { kind: 'install', cli };
23
+ }
@@ -28,13 +28,20 @@ export function moneyFormat(currency: UsageCurrency, rate: UsageRate | null): (u
28
28
  };
29
29
  }
30
30
 
31
- export const PROVIDER_LABELS: Record<UsageProvider, string> = { claude: 'Claude Code', codex: 'Codex', nanogpt: 'NanoGPT', grok: 'Grok' };
31
+ export const PROVIDER_LABELS: Record<UsageProvider, string> = {
32
+ claude: 'Claude Code',
33
+ codex: 'Codex',
34
+ nanogpt: 'NanoGPT',
35
+ grok: 'Grok',
36
+ copilot: 'GitHub Copilot'
37
+ };
32
38
 
33
39
  export const PROVIDER_COLORS: Record<UsageProvider, string> = {
34
40
  claude: 'var(--chart-claude)',
35
41
  codex: 'var(--chart-codex)',
36
42
  nanogpt: 'var(--chart-nanogpt)',
37
- grok: 'var(--chart-grok)'
43
+ grok: 'var(--chart-grok)',
44
+ copilot: 'var(--chart-copilot)'
38
45
  };
39
46
 
40
47
  /*