Skip to content

fix(desktop): disambiguate provider API key labels and annotate mint key - #4406

Merged
wpfleger96 merged 5 commits into
mainfrom
duncan/provider-key-label-disambig
Aug 3, 2026
Merged

fix(desktop): disambiguate provider API key labels and annotate mint key#4406
wpfleger96 merged 5 commits into
mainfrom
duncan/provider-key-label-disambig

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Aug 2, 2026

Copy link
Copy Markdown
Member

Two different credentials were presented under the same name throughout the app. The top-level credential field for non-Anthropic providers (OpenAI, OpenAI-compatible, OpenRouter) was labeled "OpenAI API Key" via a hardcoded binary ternary repeated in three dialogs. The card-minting key (OPENAI_API_KEY) and the runtime credential (OPENAI_COMPAT_API_KEY) have independent endpoint namespaces and consumers (OPENAI_COMPAT_BASE_URL/OPENAI_COMPAT_API_KEY for runtime, OPENAI_BASE_URL/OPENAI_API_KEY for minting) and must remain separate — either may require a different credential. This PR makes them impossible to confuse in the UI.

Changes

Provider-accurate labels from the credential table. PROVIDER_CREDENTIAL_CONFIG entries now carry an apiKeyLabel paired with secretEnvVar as a discriminated union (both present or neither — a future provider cannot ship a secret field with no label). getProviderApiKeyLabel(providerId) is the single source of truth. The three hardcoded ternaries in AgentConfigFields, AgentInstanceEditDialog, and AgentDefinitionDialog are replaced by this helper. Labels: openai → "OpenAI Runtime API Key", openai-compat → "OpenAI-compatible Runtime API Key", openrouter → "OpenRouter API Key" (was incorrectly "OpenAI API Key"), anthropic → "Anthropic API Key" (unchanged).

Field names its backing env var. PersonaProviderApiKeyField renders the env var name as a monospace hint beneath the label with aria-describedby wiring. All three call sites pass their secretEnvVar. A user who sees OPENAI_API_KEY in the mint dialog can now confirm at a glance that the credential field shows OPENAI_COMPAT_API_KEY — a different key.

Signpost visible at the decision point. CARD_MINT_KEY_ANNOTATIONS is exported from agentConfigOptions.tsx (single source) and passed as keyAnnotations to all three generic env editors: both EnvVarsEditor branches in Agent Defaults, EditAgentAdvancedFields, and PersonaAdvancedFields. CardMintKeyCue — a new small component — renders an always-visible muted cue beneath the Advanced toggle when OPENAI_API_KEY is present in global env (Advanced is collapsed by default, so the per-row annotation is invisible until the cue guides the user to open it).

Model discovery error copy. The OPENAI_COMPAT_API_KEY required message now reads "Enter an OpenAI runtime API key (OPENAI_COMPAT_API_KEY) to load OpenAI models." — naming the env var explicitly so it cannot be confused with the mint key.

Tests

  • getProviderApiKeyLabel helper: pinned correct label per provider including the new distinct labels for openai and openai-compat
  • PersonaProviderApiKeyField render: semantic label present; env-var hint rendered when envVarName provided; aria-describedby wired to hint id; hint and describedby absent when prop omitted
  • EnvVarsEditor render: annotation appears exactly once on the matching row; absent for non-matching rows
  • personaModelDiscoveryStatus: pinned new copy naming OPENAI_COMPAT_API_KEY explicitly
  • Playwright: stale "OpenAI API Key" selectors updated; new card-mint-key-cue-visible-and-annotation-in-advanced test covers Will's exact path (databricks_v2 global provider + saved OPENAI_API_KEY → cue visible before opening Advanced → annotation present after opening)

File sizes (post-format)

File Lines
AgentConfigFields.tsx 994 (≤ 996)
AgentInstanceEditDialog.tsx 1228 (≤ 1228)
AgentDefinitionDialog.tsx 1045 (≤ 1047)

Related: #4140

… key

Credential fields across Agent Defaults, agent edit, and agent create
dialogs previously used a hardcoded ternary that rendered any non-Anthropic
provider as 'OpenAI API Key'. OpenRouter's field was mislabeled; users
configuring an OpenRouter agent saw 'OpenAI API Key', which is misleading
and unrelated to the OPENAI_COMPAT_API_KEY it actually writes. The same
root cause made OPENAI_COMPAT_API_KEY and OPENAI_API_KEY visually
indistinguishable in the UI, contributing to the confusion reported in the
trading cards flow.

Add apiKeyLabel to PROVIDER_CREDENTIAL_CONFIG (single source of truth
alongside secretEnvVar and requiredEnvKeys) and expose
getProviderApiKeyLabel(providerId) next to getProviderApiKeyEnvVar. All
three ternaries in AgentConfigFields, AgentInstanceEditDialog, and
AgentDefinitionDialog are replaced with the helper. OpenRouter now reads
'OpenRouter API Key'; Anthropic reads 'Anthropic API Key'; OpenAI/compat
read 'OpenAI API Key'.

Add an optional envVarName prop to PersonaProviderApiKeyField that renders
the backing env var name as a muted monospace hint beneath the label. All
three call sites pass the secretEnvVar they already hold. A user who saw
OPENAI_API_KEY in the mint dialog can now tell at a glance that this field
is OPENAI_COMPAT_API_KEY, not the card-minting key.

Add an optional keyAnnotations prop to EnvVarsEditor. When a required or
user-managed row's key appears in the map, a muted one-line note renders
beneath it. AgentConfigFields passes { OPENAI_API_KEY: 'Used for minting
agent trading cards' } — the reciprocal signpost to the mint dialog's
pointer at Agent Defaults.

All ratcheted files stay within the 1000-line limit. 14 new tests added:
8 for getProviderApiKeyLabel, 4 for keyAnnotations lookup invariants, 2
for PersonaProviderApiKeyField export shape.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…oral tests

Finding 1 — label collision: rename openai->'OpenAI Runtime API Key' and
openai-compat->'OpenAI-compatible Runtime API Key' so the runtime credential
field and the mint dialog never present different OPENAI_* vars under the same
human label.  Update personaModelDiscoveryStatus.ts to name the compat key
explicitly (OPENAI_COMPAT_API_KEY) in the warning copy; pin the new text in
its test.  ProviderCredentialConfig is now a discriminated union so secretEnvVar
and apiKeyLabel are always either both present or both absent — a future
provider cannot ship a secret with no label.

Finding 2 — signpost visible at the decision point: export
CARD_MINT_KEY_ANNOTATIONS from agentConfigOptions.tsx (single source, no
per-file duplication).  Pass keyAnnotations to all three generic env editors:
both EnvVarsEditor branches in AgentConfigFields, EditAgentAdvancedFields, and
PersonaAdvancedFields.  Add CardMintKeyCue — an always-visible muted cue
beneath the Advanced toggle that fires when OPENAI_API_KEY is set in global env
(Advanced is collapsed by default, so the per-row annotation is invisible on
Will's exact databricks_v2 path until the cue guides the user to open it).

Finding 3 — ratchet: AgentConfigFields.tsx at 994 (<=996);
AgentInstanceEditDialog at 1228 (<=1228); AgentDefinitionDialog at 1045 (<=1047).

Finding 4 — behavioral tests: PersonaProviderApiKeyField renders semantic label,
env-var hint, and aria-describedby wiring; omitted-prop case verified.
EnvVarsEditor render confirms annotation on matching row only.  Playwright:
stale 'OpenAI API Key' selectors in persona-env-vars.spec.ts updated; new
card-mint-key-cue-visible-and-annotation-in-advanced test covers Will's exact
path (databricks_v2 global provider + saved OPENAI_API_KEY -> cue visible
before opening Advanced -> annotation present after).

MINOR (a): ProviderCredentialConfig is now a discriminated union.
MINOR (b): PR description updated — rationale uses independent endpoint
namespaces rather than 'minting calls real OpenAI'.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96 wpfleger96 changed the title feat(desktop): disambiguate provider API key labels and annotate mint key fix(desktop): disambiguate provider API key labels and annotate mint key Aug 2, 2026
npub1ng3jzsaqxdhrfq22dg85j3lpr0zsh3jp7g2h9jyxl59wraayapnsu6kvfg and others added 3 commits August 2, 2026 17:30
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…ent duplicate IDs in nested dialogs

Hardcoded 'persona-provider-api-key' / 'persona-provider-api-key-hint' IDs
caused duplicate-ID collisions when AgentInstanceEditDialog mounted its
credential field while its nested AgentDefaultsDialog was also open. The
nested input's aria-describedby resolved to the parent field's hint
paragraph, announcing the wrong env-var name to assistive technology.

Generate both inputId and hintId from React.useId() so each instance gets
a unique suffix. data-testid stays fixed for E2E selectors.

Test: adds two-instance rendering test asserting unique hint IDs and that
each input's aria-describedby resolves to its own hint, rendered inside a
single React root (the accurate model of the nested-dialog DOM).

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…iKeyField

- Rewrite extractAllAttrs to use String.matchAll instead of re.exec in a
  while-assignment, resolving noAssignInExpressions lint violation
- Apply Biome formatter to split the hintId ternary across lines

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96

Copy link
Copy Markdown
Member Author

Screenshots

1. OpenAI Runtime API Key — relabeled field with OPENAI_COMPAT_API_KEY hint

01-openai-runtime-api-key-field

1b. OpenAI-compatible Runtime API Key

01b-openai-compat-runtime-api-key-field

2. CardMintKeyCue in Agent Defaults (Will's databricks_v2 path)

Always-visible cue before the user opens Advanced:

02-card-mint-key-cue-visible

3. "Used for minting agent trading cards" annotation in Advanced

03-openai-api-key-annotation-advanced

4. Discovery-error copy naming the runtime key

Error message now explicitly names OPENAI_COMPAT_API_KEY:

04-discovery-error-runtime-key


Captured at head a98ef36f4 — no commits added.

@Chessing234

Copy link
Copy Markdown
Contributor

yeah the repeated "openai api key" label on non-openai providers was confusing — calling out mint-key vs provider key separately should help.

wpfleger96 added a commit that referenced this pull request Aug 3, 2026
…int dialog (#4140)

Fixes a write-once dead-end in the card mint dialog where a user with an
expired OpenAI key had no way to replace it.

**Source-aware key status (Rust + TypeScript).** `card_mint_key_status`
returns a layer discriminant (`"none" | "global" | "persona" | "agent" |
"process"`) instead of a boolean. A pure `resolve_key_layer()` helper in
`card.rs` owns the classification logic; `card_mint_key_status`
delegates to it, so the production path is under direct test with no
duplicate logic.

**Mint form always reachable.** The key panel replaces the mint form
only for `none` (first-time setup) or when the user explicitly opens the
edit panel (`editingKey`). Keys from agent/persona/process layers show
an inline provenance row on the mint form with a "Why?" affordance;
clicking it shows the read-only redirect in a panel with a Cancel button
that returns to the mint form — never a terminal state.

**Precise auth-error matching.** The 401 handling in `cardMintStore.ts`
matches `startsWith("Card mint failed (HTTP 401 ")` plus the specific
`Incorrect API key` text, so avatar-fetch 401 errors pass through
unchanged.

**Tri-state key status row.** "Using your saved OpenAI key · Update"
renders only when `keyLayer === "global"` (confirmed writable key).
Query pending or errored hides the row without asserting key existence.

**Real tests.** Panel visibility derivations live in
`cardMintKeyUtils.ts`, which `AgentCardMintDialog.tsx` imports directly.
Tests cover all layers including the mint-reachability invariant (Mint
reachable for every resolved layer; only `none` gates setup).

- `card.rs` — new `resolve_key_layer()` pure helper;
`card_mint_key_status` delegates to it; 999 lines (under the 1000-line
ratchet)
- `card/tests.rs` — precedence test calls `resolve_key_layer()` directly
(no test-local closure); adds process-layer and blank-value cases
- `tauriPersonas.ts` — `CardMintKeyLayer` type; updated
`cardMintKeyStatus` signature
- `cardMintKeyUtils.ts` — `showKeyPanel`, `showReadOnlyRow`,
`showCancelButton`, `keyPanelTitle`, and helpers; component imports all
of them
- `AgentCardMintDialog.tsx` — inline provenance rows for all key
sources; key panel only for setup/edit; no unused variables
- `cardMintStore.ts` — precise 401 prefix matching
- `e2eBridge.ts` — `card_mint_key_status` stub returns `"global"` (not
boolean)
- Tests: 3959 JS passing, 2089 Rust passing, `tsc --noEmit` clean

Related: [#4406](#4406)

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1ng3jzsaqxdhrfq22dg85j3lpr0zsh3jp7g2h9jyxl59wraayapnsu6kvfg <9a232143a0336e34814a6a0f4947e11bc50bc641f21572c886fd0ae1f7a4e867@buzz.block.builderlab.xyz>
@wpfleger96
wpfleger96 merged commit 5e0efb0 into main Aug 3, 2026
26 checks passed
@wpfleger96
wpfleger96 deleted the duncan/provider-key-label-disambig branch August 3, 2026 15:12
tlongwell-block pushed a commit that referenced this pull request Aug 3, 2026
The pre-push branch-skew hook correctly refused a tree CI will never test:
origin/main advanced over desktop/src/testing/e2eBridge.ts, which this
branch also carries via its base. Merging the PR's own base rather than
origin/main directly: 4c257c8 already contains current main (5e0efb0)
AND Max's resolution of that exact e2eBridge.ts overlap, certified with
Desktop Smoke E2E (1) green. Re-resolving main's conflict here would
duplicate that work and risk diverging from it.

Merge-only, no rebase, no force. merge-tree reported 0 conflicts.

Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
Signed-off-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>

* origin/max/tui-renderer:
  fix(desktop): disambiguate provider API key labels and annotate mint key (#4406)
  fix(desktop): make OpenAI key re-enterable after first save in card mint dialog (#4140)
  fix(config-bridge): add harness-definition env tier and fix equal-value model override (#3580)
  test(desktop): prove channel repair boundary
  Polish mobile composer and messaging UI (#3918)
  ci(linux): enable mesh-llm feature in Linux release and canary builds (#4524)
  test(desktop): arm validation error before read
  test(desktop): atomically start channel validation
  fix(desktop): retain terminal focus on viewport click
  test(desktop): isolate deferred channel read
  fix(desktop): stop the terminal fade clobbering the app surface's compositor hint
  Revert "Merge PR #4523: close channel validation latch race"
  test(desktop): close channel validation latch race
wpfleger96 pushed a commit that referenced this pull request Aug 3, 2026
…ed-unread

* origin/main: (44 commits)
  chore(release): release Buzz Desktop version 0.5.4 (#4562)
  test(mobile): assert follow boundary semantics (#4559)
  docs(release): align desktop handoff instructions (#3988)
  fix: report agent usage per provider round, not once per turn (#4545)
  fix(desktop): harden Windows installs against Defender block and orphaned Node (#4382)
  feat(desktop): improve channel template discovery (#4549)
  fix(desktop): save key backups to authorized path (#4022)
  Add channel activity hover menu (#3935)
  feat(desktop): show saved Run on settings when editing an agent (#4539)
  fix(desktop): disambiguate provider API key labels and annotate mint key (#4406)
  fix(desktop): make OpenAI key re-enterable after first save in card mint dialog (#4140)
  fix(config-bridge): add harness-definition env tier and fix equal-value model override (#3580)
  Polish mobile composer and messaging UI (#3918)
  ci(linux): enable mesh-llm feature in Linux release and canary builds (#4524)
  fix(desktop): stop the create-agent provider config probe from erasing keystrokes (#4411)
  fix(mobile): recover and pace live subscriptions (#3053)
  feat(acp): deliver system prompt via _meta.systemPrompt for claude-agent-acp (#4395)
  fix(security): bump nostr crates for RUSTSEC-2026-0225..0232 + default sprig image to published digest (#4392)
  fix(desktop): back/forward via keyboard chords, mouse X1/X2 buttons, and swipe gestures (#3778)
  feat(k8s): Kubernetes backend plugin + desktop deploy path (#4289)
  ...

Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
wpfleger96 added a commit that referenced this pull request Aug 3, 2026
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

* origin/main: (40 commits)
  fix(mobile): recover stale relay sessions (#4372)
  chore(release): release Buzz Desktop version 0.5.4 (#4562)
  test(mobile): assert follow boundary semantics (#4559)
  docs(release): align desktop handoff instructions (#3988)
  fix: report agent usage per provider round, not once per turn (#4545)
  fix(desktop): harden Windows installs against Defender block and orphaned Node (#4382)
  feat(desktop): improve channel template discovery (#4549)
  fix(desktop): save key backups to authorized path (#4022)
  Add channel activity hover menu (#3935)
  feat(desktop): show saved Run on settings when editing an agent (#4539)
  fix(desktop): disambiguate provider API key labels and annotate mint key (#4406)
  fix(desktop): make OpenAI key re-enterable after first save in card mint dialog (#4140)
  fix(config-bridge): add harness-definition env tier and fix equal-value model override (#3580)
  Polish mobile composer and messaging UI (#3918)
  ci(linux): enable mesh-llm feature in Linux release and canary builds (#4524)
  fix(desktop): stop the create-agent provider config probe from erasing keystrokes (#4411)
  fix(mobile): recover and pace live subscriptions (#3053)
  feat(acp): deliver system prompt via _meta.systemPrompt for claude-agent-acp (#4395)
  fix(security): bump nostr crates for RUSTSEC-2026-0225..0232 + default sprig image to published digest (#4392)
  fix(desktop): back/forward via keyboard chords, mouse X1/X2 buttons, and swipe gestures (#3778)
  ...
wpfleger96 added a commit that referenced this pull request Aug 3, 2026
Syncs Cargo.lock and ci.yml updates from origin/main via the umbrella, which
resolves the RUSTSEC-2026-0225..0229 nostr advisory failures in the Security gate.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

* commit 'ad9ae1dbb0c2d9b6839c1e18d6fd6a78238b61fb': (40 commits)
  fix(mobile): recover stale relay sessions (#4372)
  chore(release): release Buzz Desktop version 0.5.4 (#4562)
  test(mobile): assert follow boundary semantics (#4559)
  docs(release): align desktop handoff instructions (#3988)
  fix: report agent usage per provider round, not once per turn (#4545)
  fix(desktop): harden Windows installs against Defender block and orphaned Node (#4382)
  feat(desktop): improve channel template discovery (#4549)
  fix(desktop): save key backups to authorized path (#4022)
  Add channel activity hover menu (#3935)
  feat(desktop): show saved Run on settings when editing an agent (#4539)
  fix(desktop): disambiguate provider API key labels and annotate mint key (#4406)
  fix(desktop): make OpenAI key re-enterable after first save in card mint dialog (#4140)
  fix(config-bridge): add harness-definition env tier and fix equal-value model override (#3580)
  Polish mobile composer and messaging UI (#3918)
  ci(linux): enable mesh-llm feature in Linux release and canary builds (#4524)
  fix(desktop): stop the create-agent provider config probe from erasing keystrokes (#4411)
  fix(mobile): recover and pace live subscriptions (#3053)
  feat(acp): deliver system prompt via _meta.systemPrompt for claude-agent-acp (#4395)
  fix(security): bump nostr crates for RUSTSEC-2026-0225..0232 + default sprig image to published digest (#4392)
  fix(desktop): back/forward via keyboard chords, mouse X1/X2 buttons, and swipe gestures (#3778)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants