| 1 | import type { |
| 2 | ServerProvider, |
| 3 | ServerProviderVersionAdvisory, |
| 4 | ServerProviderCompatibilityAdvisory, |
| 5 | } from "@t3tools/contracts"; |
| 6 | |
| 7 | /** |
| 8 | * Visual treatment for each server-reported provider status. Centralized so |
| 9 | * the default-driver card and per-instance cards share the same language. |
| 10 | */ |
| 11 | export const PROVIDER_STATUS_STYLES = { |
| 12 | disabled: { |
| 13 | dot: "bg-muted-foreground/50", |
| 14 | }, |
| 15 | error: { |
| 16 | dot: "bg-destructive", |
| 17 | }, |
| 18 | ready: { |
| 19 | dot: "bg-success", |
| 20 | }, |
| 21 | warning: { |
| 22 | dot: "bg-warning", |
| 23 | }, |
| 24 | } as const; |
| 25 | |
| 26 | export type ProviderStatusKey = keyof typeof PROVIDER_STATUS_STYLES; |
| 27 | |
| 28 | /** |
| 29 | * Derive the headline + detail copy shown under a provider's name in the |
| 30 | * settings page. Prefers `provider.message` for server-supplied detail and |
| 31 | * falls back to generic phrasing when the server has not yet reported any |
| 32 | * state — which happens before the first probe or when an instance names a |
| 33 | * driver this build does not ship. A ready provider without account metadata |
| 34 | * remains available and does not imply an authentication failure. |
| 35 | */ |
| 36 | export function getProviderSummary(provider: ServerProvider | undefined) { |
| 37 | if (!provider) { |
| 38 | return { |
| 39 | headline: "Checking provider status", |
| 40 | detail: "Waiting for the server to report installation and authentication details.", |
| 41 | }; |
| 42 | } |
| 43 | if (!provider.enabled || provider.status === "disabled") { |
| 44 | return { |
| 45 | headline: "Disabled", |
| 46 | detail: |
| 47 | provider.message ?? "This provider is installed but disabled for new sessions in The Knife.", |
| 48 | }; |
| 49 | } |
| 50 | if (!provider.installed) { |
| 51 | return { |
| 52 | headline: "Not found", |
| 53 | detail: provider.message ?? "CLI not detected on PATH.", |
| 54 | }; |
| 55 | } |
| 56 | if (provider.auth.status === "unauthenticated") { |
| 57 | const authLabel = provider.auth.label ?? provider.auth.type; |
| 58 | return { |
| 59 | headline: authLabel ? `Not authenticated · ${authLabel}` : "Not authenticated", |
| 60 | detail: provider.message ?? null, |
| 61 | }; |
| 62 | } |
| 63 | if (provider.status === "warning") { |
| 64 | return { |
| 65 | headline: "Needs attention", |
| 66 | detail: |
| 67 | provider.message ?? "The provider is installed, but the server could not fully verify it.", |
| 68 | }; |
| 69 | } |
| 70 | if (provider.status === "error") { |
| 71 | return { |
| 72 | headline: "Unavailable", |
| 73 | detail: provider.message ?? "The provider failed its startup checks.", |
| 74 | }; |
| 75 | } |
| 76 | if (provider.auth.status === "authenticated") { |
| 77 | const authLabel = provider.auth.label ?? provider.auth.type; |
| 78 | return { |
| 79 | headline: authLabel ? `Authenticated · ${authLabel}` : "Authenticated", |
| 80 | detail: provider.message ?? null, |
| 81 | }; |
| 82 | } |
| 83 | return { |
| 84 | headline: "Available", |
| 85 | detail: provider.message ?? null, |
| 86 | }; |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * Normalize a version string for display. Adds the `v` prefix when the |
| 91 | * driver reported a bare version (e.g. `1.2.3`) so cards render |
| 92 | * consistently regardless of driver. |
| 93 | */ |
| 94 | export function getProviderVersionLabel(version: string | null | undefined) { |
| 95 | if (!version) return null; |
| 96 | // Antigravity reports a release tag such as `agy_acp_server_20260818_01_RC01`. |
| 97 | // Show the date and candidate so the row title keeps room for the name. |
| 98 | const antigravity = /^agy_acp_server_(\d{4})(\d{2})(\d{2})_\d+(?:_(\w+))?$/.exec(version); |
| 99 | if (antigravity) { |
| 100 | const [, year, month, day, candidate] = antigravity; |
| 101 | return `${year}-${month}-${day}${candidate ? ` ${candidate}` : ""}`; |
| 102 | } |
| 103 | // Only bare semver-like versions get a `v` prefix. Other tags are shown as-is. |
| 104 | return /^\d/.test(version) ? `v${version}` : version; |
| 105 | } |
| 106 | |
| 107 | const COMPATIBILITY_TITLES = { |
| 108 | graceful: "Limited support", |
| 109 | unsupported: "Unsupported version", |
| 110 | broken: "Known broken version", |
| 111 | } as const; |
| 112 | |
| 113 | /** Compatibility guidance shares the version popover, with safe install actions. */ |
| 114 | export function getProviderVersionAdvisoryPresentation( |
| 115 | advisory: ServerProviderVersionAdvisory | undefined, |
| 116 | compatibility?: ServerProviderCompatibilityAdvisory | undefined, |
| 117 | showCompatibility = true, |
| 118 | ): { |
| 119 | readonly title: string; |
| 120 | readonly detail: string; |
| 121 | readonly updateCommand: string | null; |
| 122 | readonly emphasis: "normal" | "strong"; |
| 123 | readonly targetVersion: string | null; |
| 124 | } | null { |
| 125 | const latestIsIncompatible = |
| 126 | compatibility?.latestVersionStatus === "broken" || |
| 127 | compatibility?.latestVersionStatus === "unsupported"; |
| 128 | if ( |
| 129 | showCompatibility && |
| 130 | compatibility && |
| 131 | (compatibility.status === "graceful" || |
| 132 | compatibility.status === "unsupported" || |
| 133 | compatibility.status === "broken") |
| 134 | ) { |
| 135 | const targetVersion = compatibility.recommendedVersion; |
| 136 | const recommendation = getProviderVersionLabel(targetVersion) ?? compatibility.recommendedRange; |
| 137 | return { |
| 138 | title: COMPATIBILITY_TITLES[compatibility.status], |
| 139 | detail: |
| 140 | compatibility.message ?? |
| 141 | (recommendation ? `Use ${recommendation} for full support.` : "Update for full support."), |
| 142 | updateCommand: |
| 143 | targetVersion || latestIsIncompatible ? null : (advisory?.updateCommand ?? null), |
| 144 | emphasis: compatibility.status === "graceful" ? "normal" : "strong", |
| 145 | targetVersion, |
| 146 | }; |
| 147 | } |
| 148 | if ( |
| 149 | !advisory || |
| 150 | advisory.status === "current" || |
| 151 | advisory.status === "unknown" || |
| 152 | latestIsIncompatible |
| 153 | ) { |
| 154 | return null; |
| 155 | } |
| 156 | |
| 157 | const label = "Update available"; |
| 158 | const version = advisory.latestVersion; |
| 159 | const versionLabel = getProviderVersionLabel(version); |
| 160 | |
| 161 | return { |
| 162 | title: label, |
| 163 | detail: |
| 164 | advisory.message ?? |
| 165 | (versionLabel |
| 166 | ? `${label}: install ${versionLabel}.` |
| 167 | : `${label}: install the latest provider version.`), |
| 168 | updateCommand: advisory.updateCommand, |
| 169 | emphasis: "normal" as const, |
| 170 | targetVersion: null, |
| 171 | }; |
| 172 | } |