| 1 | /** |
| 2 | * Helpers for synthesizing "unavailable" `ServerProvider` snapshots. |
| 3 | * |
| 4 | * When `ServerSettings.providerInstances` (or persisted thread/session |
| 5 | * state) references a driver this build does not ship — typical after a |
| 6 | * downgrade from a fork or a feature-branch test session — the runtime |
| 7 | * needs to surface the entry to the UI without crashing. This module |
| 8 | * produces shadow snapshots that satisfy `ServerProvider`'s wire shape |
| 9 | * while signalling unavailability. |
| 10 | * |
| 11 | * @module unavailableProviderSnapshot |
| 12 | */ |
| 13 | import { |
| 14 | ProviderDriverKind, |
| 15 | type ProviderInstanceId, |
| 16 | type ServerProvider, |
| 17 | } from "@t3tools/contracts"; |
| 18 | import * as DateTime from "effect/DateTime"; |
| 19 | import * as Effect from "effect/Effect"; |
| 20 | |
| 21 | import { buildServerProvider } from "@t3tools/provider-core/server/snapshotProbe"; |
| 22 | |
| 23 | export interface UnavailableProviderSnapshotInput { |
| 24 | readonly driverKind: ProviderDriverKind | string; |
| 25 | readonly instanceId: ProviderInstanceId; |
| 26 | readonly displayName?: string | undefined; |
| 27 | readonly accentColor?: string | undefined; |
| 28 | readonly reason: string; |
| 29 | /** |
| 30 | * Optional override for `checkedAt`. Defaulted to the current Effect |
| 31 | * `DateTime` so callers |
| 32 | * (notably tests) don't have to pass it. |
| 33 | */ |
| 34 | readonly checkedAt?: string; |
| 35 | } |
| 36 | |
| 37 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); |
| 38 | |
| 39 | /** |
| 40 | * Produce a `ServerProvider` snapshot representing a configured instance |
| 41 | * whose driver the running build does not implement. The result is safe |
| 42 | * to broadcast over the wire and is structured so the web UI can render |
| 43 | * a "missing driver" affordance without special-casing. |
| 44 | */ |
| 45 | export function buildUnavailableProviderSnapshot( |
| 46 | input: UnavailableProviderSnapshotInput, |
| 47 | ): Effect.Effect<ServerProvider> { |
| 48 | return Effect.gen(function* () { |
| 49 | const checkedAt = input.checkedAt ?? (yield* nowIso); |
| 50 | const displayName = input.displayName?.trim() || (input.driverKind as string); |
| 51 | |
| 52 | const base = buildServerProvider({ |
| 53 | presentation: { displayName }, |
| 54 | enabled: false, |
| 55 | checkedAt, |
| 56 | models: [], |
| 57 | skills: [], |
| 58 | probe: { |
| 59 | installed: false, |
| 60 | version: null, |
| 61 | status: "error", |
| 62 | auth: { status: "unknown" }, |
| 63 | message: input.reason, |
| 64 | }, |
| 65 | }); |
| 66 | |
| 67 | return { |
| 68 | ...base, |
| 69 | instanceId: input.instanceId, |
| 70 | ...(input.accentColor ? { accentColor: input.accentColor } : {}), |
| 71 | driver: |
| 72 | typeof input.driverKind === "string" |
| 73 | ? ProviderDriverKind.make(input.driverKind) |
| 74 | : input.driverKind, |
| 75 | availability: "unavailable", |
| 76 | unavailableReason: input.reason, |
| 77 | }; |
| 78 | }); |
| 79 | } |