[stacked on #2194] Project a declared provider icon glyph onto ProviderInfo and draw it - #2195
Closed
SawyerHood wants to merge 1 commit into
Closed
[stacked on #2194] Project a declared provider icon glyph onto ProviderInfo and draw it#2195SawyerHood wants to merge 1 commit into
SawyerHood wants to merge 1 commit into
Conversation
PluginProviderDeclaration.icon accepts a named host glyph ("Zap") or a
plugin-relative SVG path. The projection kept only the path form
(logoUrl, served by the logo route) and dropped the glyph, so a provider
plugin without an SVG asset showed its display name's initial in the
picker — a privilege asymmetry the echo canary surfaced, since every
first-party plugin ships an asset.
ProviderInfo gains an optional `icon: { glyph }` (absent when the
declaration named a path or nothing; at most one of icon/logoUrl is set),
the same vocabulary an item presentation's icon uses. The registration
projects it. The web app resolves it in getProviderIconInfo after logoUrl
and before the generic ACP glyph, drawn through the shared icon set so it
inherits the text color; an unknown glyph name resolves to nothing and the
initial fallback stands. The picker, the providers settings list and the
mobile provider picker carry it. The first-party projections are pinned
against the plugins' own declarations (four logos, four icon-less ACP
agents, no glyph).
Server→app wire only (ProviderInfo); nothing between the server and the
host daemon changes, so HOST_DAEMON_PROTOCOL_VERSION stays.
Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #2194 (event types by name). Layer 3 of 3 of the provider-gaps stack (#2193 → #2194 → #2195). Closes gap G-C from #2189.
What was wrong
PluginProviderDeclaration.iconaccepts a named host glyph ("Zap") or a plugin-relative SVG path ("./icons/agent.svg"). The projection kept only the path form —ProviderInfo.logoUrl, served by the provider-logo route — and dropped the glyph on the floor, so a provider plugin without an SVG asset showed its display name's initial ("E" for Echo) in the picker. Every first-party plugin ships an asset, which is why nobody hit it: exactly the privilege asymmetry the canary exists to find.What changed
packages/domain/src/provider-types.ts—ProviderInfogainsicon?: { glyph: string }: absent when the declaration named a path or nothing, so at most one oficon/logoUrlis set. Same vocabulary as an item presentation'sicon(and the design doc's declared{ glyph } | { asset }, with the asset half already travelling aslogoUrl). Optional per the pattern of the other WS2a projections on this schema (family,strings,extensionKinds): absence means the provider declared none, never a default.apps/server/src/services/providers/plugin-provider-registration.tsprojects a non-pathiconas{ glyph }.apps/app/src/lib/provider-icon.ts—getProviderIconInfo(providerId, source?)takes aProviderIconSource({ logoUrl, icon? }— pass theProviderInfoitself) instead of a barelogoUrl. Resolution order: plugin slot → vendored brand mark →logoUrl→ the declared glyph → generic ACP glyph → undefined (the picker's initial). The glyph renders through the shared icon set (Icon), so it inherits the surrounding text color like a vendored mark; a glyph name this host does not know resolves to nothing and the fallback chain continues. Callers:useThreadCreationOptions(the picker tabs and trigger, desktop and compact),ProvidersSettingsSection.apps/mobile—ProviderPickerOptioncarriesglyph;ProviderPickerdraws it throughisIconNamefor the row icon and the trigger, falling back to Zap as before.Tests:
plugin-provider-registration.test.tsprojects glyph vs path (never both) and pins the first-party projections against the plugins' own declarations vialoadFirstPartyProviderDeclarations— codex, claude-code, pi, acp-cursor keep their logo URLs; acp-opencode/omp/grok/hermes-agent stay icon-less (the app vendors their marks by id); none gets a glyph.provider-icon.test.tsxcovers glyph rendering (svg[data-icon="Zap"], no<img>), unknown glyph → undefined, logo winning over glyph.execution-options.test.ts(mobile) covers the option projection.docs/api_to_audit.md: theapp.experimental_useProviders"Icons" item notes the new field and the fold-into-one-field question for stabilization.Wire: this is server→app (
ProviderInfoover/api/v1/system/providersand the SDK's provider directory), additive and optional. Nothing between the server and the host daemon changes — no session payload, host RPC or WebSocket message carriesProviderInfo— soHOST_DAEMON_PROTOCOL_VERSIONdoes not bump.How you verified
scripts/bb-dev-app current, the only instance running; stopped afterwards). The echo plugin installed from its path;GET /api/v1/system/providers→echo-agent: logoUrl null, icon {glyph: "Zap"}; the four first-party rows unchanged (logoUrlset, noicon). In the picker the Echo tab renderssvg[data-icon="Zap"](was the initial "E"); selecting it puts the bolt in the trigger; Settings → Providers shows the bolt on the Echo row. Screenshots in thread storagethr_iiqzqxqgut:glyph-picker-open.png,glyph-picker-echo.png,glyph-picker-echo-crop.png,glyph-settings-providers.png.pnpm exec turbo run typecheck --filter=@bb/domain --filter=@bb/server --filter=@bb/app --filter=@bb/mobile: pass.@bb/serverplugin-provider-registrationtests: 14 pass (the first-party pin fails on the previous commit only if a glyph appears — it is a guard, not a change).@bb/appprovider-icon,ModelReasoningPicker,ProvidersSettings,useThreadCreationOptions: 56 pass.@bb/mobileexecution-options: 9 pass.@bb/domain: 150 pass.BB_PROVIDER_CORPUS_DIR, compare mode): 307/307 threads, 93,262 rows, zero diffs, empty allowlist, exit 0. Note: the shared~/.bb/provider-corpus/snapshots/rowsbaseline was rewritten at 15:13 today by the WS3 layer-1 thread (Project grammar v3 items to presentation-driven rows (WS3 layer 1) #2192; it leftrows.bak-pre-ws3-1513), so the comparison ran against that pre-WS3, main-minted baseline through a shadow corpus dir (/tmp/corpus-gc, symlinks to the corpus,rows→ the backup, empty allowlist). Against WS3's rewritten baseline the same run shows WS3's 37 changed threads, none of them from this PR.scripts/check-provider-literal-ratchet.mjs): not on this base (it lives on Add the provider-literal ratchet guardrail (G1) #2120); skipped.