| 1 | /** |
| 2 | * Provider-instance contracts. |
| 3 | * |
| 4 | * Splits the historical "provider kind" concept into two: |
| 5 | * |
| 6 | * - `ProviderDriverKind` is the implementation kind selector (e.g. codex, |
| 7 | * claudeAgent, a fork's `ollama`, …). It picks which driver package |
| 8 | * handles the protocol, the probe, the adapter, and text generation. |
| 9 | * |
| 10 | * - `ProviderInstanceId` is the routing key (a user-defined slug). |
| 11 | * Threads, sessions, runtime events, and persisted bindings reference |
| 12 | * instance ids — never driver kinds — so a user can configure multiple |
| 13 | * instances of the same driver (e.g. `codex_personal` + `codex_work`), |
| 14 | * each with independent driver-specific configuration. |
| 15 | * |
| 16 | * Forward/backward compatibility invariant |
| 17 | * ---------------------------------------- |
| 18 | * `ProviderDriverKind` is intentionally an **open** branded slug, not a closed |
| 19 | * literal union. The server hosts forks, ships in PRs that add drivers, and |
| 20 | * users frequently roll between branches and forks. Any of those paths can |
| 21 | * leave `ServerSettings`, persisted thread state, or session bindings |
| 22 | * referencing a driver that the currently-running build does not know about. |
| 23 | * |
| 24 | * The rule: parsing any of those payloads must always succeed, and the |
| 25 | * runtime is responsible for marking the unknown driver/instance as |
| 26 | * "unavailable" rather than crashing. Built-in drivers shipped by the core |
| 27 | * product happens to register in a given build is not part of the contract |
| 28 | * layer. Driver availability is discovered through the runtime registry. |
| 29 | * |
| 30 | * Driver-specific configuration is similarly opaque at the contracts layer: |
| 31 | * drivers live in (or will be extracted to) their own packages and own their |
| 32 | * config schemas. The contracts package only knows the envelope. |
| 33 | * |
| 34 | * @module providerInstance |
| 35 | */ |
| 36 | import * as Effect from "effect/Effect"; |
| 37 | import * as Schema from "effect/Schema"; |
| 38 | import { TrimmedNonEmptyString } from "./baseSchemas.ts"; |
| 39 | |
| 40 | const PROVIDER_SLUG_MAX_CHARS = 64; |
| 41 | /** |
| 42 | * Slug pattern shared by driver kinds and instance ids — letters, digits, |
| 43 | * dashes, underscores. The first character must be a letter so slugs remain |
| 44 | * JS-identifier friendly when used as object keys, log fields, or telemetry |
| 45 | * attributes. Mixed case is permitted so historical driver kinds (e.g. |
| 46 | * `claudeAgent`) can be used verbatim during the migration and so external |
| 47 | * fork authors retain reasonable freedom. |
| 48 | */ |
| 49 | const PROVIDER_SLUG_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/; |
| 50 | const ENVIRONMENT_VARIABLE_NAME_MAX_CHARS = 128; |
| 51 | const ENVIRONMENT_VARIABLE_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/; |
| 52 | |
| 53 | const slugSchema = TrimmedNonEmptyString.check( |
| 54 | Schema.isMaxLength(PROVIDER_SLUG_MAX_CHARS), |
| 55 | Schema.isPattern(PROVIDER_SLUG_PATTERN), |
| 56 | ); |
| 57 | |
| 58 | /** |
| 59 | * `ProviderDriverKind` — open branded slug naming a driver implementation. |
| 60 | * |
| 61 | * Constraints (validated at the schema layer): |
| 62 | * - starts with a letter |
| 63 | * - only letters, digits, `-`, `_` after the first char |
| 64 | * - 1..64 characters |
| 65 | * |
| 66 | * Notably **not** validated: that the driver is one we know how to load. |
| 67 | * That check belongs to the runtime registry, which downgrades unknown |
| 68 | * drivers gracefully (see module docs). |
| 69 | */ |
| 70 | export const ProviderDriverKind = slugSchema.pipe(Schema.brand("ProviderDriverKind")); |
| 71 | export type ProviderDriverKind = typeof ProviderDriverKind.Type; |
| 72 | |
| 73 | const isProviderDriverKindValue = Schema.is(ProviderDriverKind); |
| 74 | export const isProviderDriverKind = (value: unknown): value is ProviderDriverKind => |
| 75 | isProviderDriverKindValue(value); |
| 76 | |
| 77 | /** |
| 78 | * `ProviderInstanceId` — user-defined routing key for a configured provider |
| 79 | * instance. Same slug rules as `ProviderDriverKind`; branded separately so the |
| 80 | * type system cannot confuse the two. |
| 81 | */ |
| 82 | export const ProviderInstanceId = slugSchema.pipe(Schema.brand("ProviderInstanceId")); |
| 83 | export type ProviderInstanceId = typeof ProviderInstanceId.Type; |
| 84 | |
| 85 | /** |
| 86 | * Lightweight reference identifying which driver implements an instance. |
| 87 | * Carried alongside `ProviderInstanceId` on wire shapes so consumers can |
| 88 | * branch on driver behavior (icons, capabilities, presentation) without |
| 89 | * having to look up the instance in the registry. |
| 90 | */ |
| 91 | export const ProviderInstanceRef = Schema.Struct({ |
| 92 | instanceId: ProviderInstanceId, |
| 93 | driver: ProviderDriverKind, |
| 94 | }); |
| 95 | export type ProviderInstanceRef = typeof ProviderInstanceRef.Type; |
| 96 | |
| 97 | export const ProviderInstanceEnvironmentVariableName = TrimmedNonEmptyString.check( |
| 98 | Schema.isMaxLength(ENVIRONMENT_VARIABLE_NAME_MAX_CHARS), |
| 99 | Schema.isPattern(ENVIRONMENT_VARIABLE_NAME_PATTERN), |
| 100 | ); |
| 101 | export type ProviderInstanceEnvironmentVariableName = |
| 102 | typeof ProviderInstanceEnvironmentVariableName.Type; |
| 103 | |
| 104 | export const ProviderInstanceEnvironmentVariable = Schema.Struct({ |
| 105 | name: ProviderInstanceEnvironmentVariableName, |
| 106 | value: Schema.String.pipe(Schema.withDecodingDefault(Effect.succeed(""))), |
| 107 | sensitive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), |
| 108 | valueRedacted: Schema.optionalKey(Schema.Boolean), |
| 109 | }); |
| 110 | export type ProviderInstanceEnvironmentVariable = typeof ProviderInstanceEnvironmentVariable.Type; |
| 111 | |
| 112 | export const ProviderInstanceEnvironment = Schema.Array(ProviderInstanceEnvironmentVariable); |
| 113 | export type ProviderInstanceEnvironment = typeof ProviderInstanceEnvironment.Type; |
| 114 | |
| 115 | /** |
| 116 | * Envelope shape for a provider instance configuration in `ServerSettings`. |
| 117 | * |
| 118 | * `driver` is intentionally accepted as any well-formed slug (see module |
| 119 | * docs). The driver-specific config payload is left as `Schema.Unknown`; |
| 120 | * each driver registers its own decoder with the runtime registry, and |
| 121 | * envelopes for unknown drivers are preserved verbatim so they round-trip |
| 122 | * across version changes without data loss. |
| 123 | */ |
| 124 | export const ProviderInstanceConfig = Schema.Struct({ |
| 125 | driver: ProviderDriverKind, |
| 126 | displayName: Schema.optional(TrimmedNonEmptyString), |
| 127 | accentColor: Schema.optional(TrimmedNonEmptyString), |
| 128 | environment: Schema.optionalKey(ProviderInstanceEnvironment), |
| 129 | enabled: Schema.optionalKey(Schema.Boolean), |
| 130 | config: Schema.optionalKey(Schema.Unknown), |
| 131 | }); |
| 132 | export type ProviderInstanceConfig = typeof ProviderInstanceConfig.Type; |
| 133 | |
| 134 | /** Atomic mutation for one provider-instance map entry. */ |
| 135 | export const ProviderInstanceMutation = Schema.Union([ |
| 136 | Schema.Struct({ |
| 137 | operation: Schema.Literal("create"), |
| 138 | instanceId: ProviderInstanceId, |
| 139 | instance: ProviderInstanceConfig, |
| 140 | }), |
| 141 | Schema.Struct({ |
| 142 | operation: Schema.Literal("upsert"), |
| 143 | instanceId: ProviderInstanceId, |
| 144 | instance: ProviderInstanceConfig, |
| 145 | }), |
| 146 | Schema.Struct({ |
| 147 | operation: Schema.Literal("remove"), |
| 148 | instanceId: ProviderInstanceId, |
| 149 | }), |
| 150 | ]); |
| 151 | export type ProviderInstanceMutation = typeof ProviderInstanceMutation.Type; |
| 152 | |
| 153 | /** |
| 154 | * Map shape for `ServerSettings.providerInstances`. Keyed by |
| 155 | * `ProviderInstanceId`, values are envelopes the registry feeds to drivers. |
| 156 | */ |
| 157 | export const ProviderInstanceConfigMap = Schema.Record(ProviderInstanceId, ProviderInstanceConfig); |
| 158 | export type ProviderInstanceConfigMap = typeof ProviderInstanceConfigMap.Type; |
| 159 | |
| 160 | /** |
| 161 | * Construct the canonical `ProviderInstanceId` used as a back-compat default |
| 162 | * for a built-in driver. The legacy single-instance-per-driver world used |
| 163 | * the driver kind itself as the instance id; preserving that mapping keeps |
| 164 | * existing persisted threads, bindings, and cache files routable across the |
| 165 | * migration without rewriting their stored selection payloads. |
| 166 | */ |
| 167 | export const defaultInstanceIdForDriver = (driver: ProviderDriverKind): ProviderInstanceId => |
| 168 | ProviderInstanceId.make(driver); |