| author | |
| committer | |
| log | 5d4e2fae012b74cfb323cc2cb45687caf93997cc |
| tree | ff7a70b3799b9ab0fd1eb2fc3ba5c0467b219bd6 |
| parent | 3e01c4bc572ea6a938c9a47143c914ff042ea41a |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>14 files changed, 239 insertions(+), 57 deletions(-)
apps/server/src/provider/Drivers/ClaudeDriver.ts+19-9| ... | ... | @@ -20,12 +20,12 @@ import * as Effect from "effect/Effect"; |
| 20 | 20 | import * as FileSystem from "effect/FileSystem"; |
| 21 | 21 | import * as Path from "effect/Path"; |
| 22 | 22 | import * as Schema from "effect/Schema"; |
| 23 | import * as Stream from "effect/Stream"; | |
| 24 | 23 | import { HttpClient } from "effect/unstable/http"; |
| 25 | 24 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 26 | 25 | |
| 27 | 26 | import { makeClaudeTextGeneration } from "../../textGeneration/ClaudeTextGeneration.ts"; |
| 28 | 27 | import { ServerConfig } from "../../config.ts"; |
| 28 | import { ServerSettingsService } from "../../serverSettings.ts"; | |
| 29 | 29 | import { ProviderDriverError } from "../Errors.ts"; |
| 30 | 30 | import { makeClaudeAdapter } from "../Layers/ClaudeAdapter.ts"; |
| 31 | 31 | import { |
| ... | ... | @@ -48,6 +48,11 @@ import { |
| 48 | 48 | normalizeCommandPath, |
| 49 | 49 | resolveProviderMaintenanceCapabilitiesEffect, |
| 50 | 50 | } from "../providerMaintenance.ts"; |
| 51 | import { | |
| 52 | haveProviderSnapshotSettingsChanged, | |
| 53 | makeProviderSnapshotSettingsSource, | |
| 54 | type ProviderSnapshotSettings, | |
| 55 | } from "../providerUpdateSettings.ts"; | |
| 51 | 56 | import { makeClaudeCapabilitiesCacheKey, makeClaudeContinuationGroupKey } from "./ClaudeHome.ts"; |
| 52 | 57 | const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings); |
| 53 | 58 | |
| ... | ... | @@ -83,7 +88,8 @@ export type ClaudeDriverEnv = |
| 83 | 88 | | HttpClient.HttpClient |
| 84 | 89 | | Path.Path |
| 85 | 90 | | ProviderEventLoggers |
| 86 | | ServerConfig; | |
| 91 | | ServerConfig | |
| 92 | | ServerSettingsService; | |
| 87 | 93 | |
| 88 | 94 | const withInstanceIdentity = |
| 89 | 95 | (input: { |
| ... | ... | @@ -114,6 +120,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv> = { |
| 114 | 120 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 115 | 121 | const path = yield* Path.Path; |
| 116 | 122 | const httpClient = yield* HttpClient.HttpClient; |
| 123 | const serverSettings = yield* ServerSettingsService; | |
| 117 | 124 | const eventLoggers = yield* ProviderEventLoggers; |
| 118 | 125 | const processEnv = mergeProviderInstanceEnvironment(environment); |
| 119 | 126 | const fallbackContinuationIdentity = defaultProviderContinuationIdentity({ |
| ... | ... | @@ -163,16 +170,19 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv> = { |
| 163 | 170 | Effect.provideService(Path.Path, path), |
| 164 | 171 | ); |
| 165 | 172 | |
| 166 | const snapshot = yield* makeManagedServerProvider<ClaudeSettings>({ | |
| 173 | const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings); | |
| 174 | const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<ClaudeSettings>>({ | |
| 167 | 175 | maintenanceCapabilities, |
| 168 | getSettings: Effect.succeed(effectiveConfig), | |
| 169 | streamSettings: Stream.never, | |
| 170 | haveSettingsChanged: () => false, | |
| 176 | getSettings: snapshotSettings.getSettings, | |
| 177 | streamSettings: snapshotSettings.streamSettings, | |
| 178 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, | |
| 171 | 179 | initialSnapshot: (settings) => |
| 172 | makePendingClaudeProvider(settings).pipe(Effect.map(stampIdentity)), | |
| 180 | makePendingClaudeProvider(settings.provider).pipe(Effect.map(stampIdentity)), | |
| 173 | 181 | checkProvider, |
| 174 | enrichSnapshot: ({ snapshot, publishSnapshot }) => | |
| 175 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( | |
| 182 | enrichSnapshot: ({ settings, snapshot, publishSnapshot }) => | |
| 183 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities, { | |
| 184 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, | |
| 185 | }).pipe( | |
| 176 | 186 | Effect.provideService(HttpClient.HttpClient, httpClient), |
| 177 | 187 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), |
| 178 | 188 | ), |
apps/server/src/provider/Drivers/CodexDriver.ts+19-9| ... | ... | @@ -28,12 +28,12 @@ import * as Effect from "effect/Effect"; |
| 28 | 28 | import * as FileSystem from "effect/FileSystem"; |
| 29 | 29 | import * as Path from "effect/Path"; |
| 30 | 30 | import * as Schema from "effect/Schema"; |
| 31 | import * as Stream from "effect/Stream"; | |
| 32 | 31 | import { HttpClient } from "effect/unstable/http"; |
| 33 | 32 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 34 | 33 | |
| 35 | 34 | import { makeCodexTextGeneration } from "../../textGeneration/CodexTextGeneration.ts"; |
| 36 | 35 | import { ServerConfig } from "../../config.ts"; |
| 36 | import { ServerSettingsService } from "../../serverSettings.ts"; | |
| 37 | 37 | import { ProviderDriverError } from "../Errors.ts"; |
| 38 | 38 | import { makeCodexAdapter } from "../Layers/CodexAdapter.ts"; |
| 39 | 39 | import { checkCodexProviderStatus, makePendingCodexProvider } from "../Layers/CodexProvider.ts"; |
| ... | ... | @@ -47,6 +47,11 @@ import { |
| 47 | 47 | makePackageManagedProviderMaintenanceResolver, |
| 48 | 48 | resolveProviderMaintenanceCapabilitiesEffect, |
| 49 | 49 | } from "../providerMaintenance.ts"; |
| 50 | import { | |
| 51 | haveProviderSnapshotSettingsChanged, | |
| 52 | makeProviderSnapshotSettingsSource, | |
| 53 | type ProviderSnapshotSettings, | |
| 54 | } from "../providerUpdateSettings.ts"; | |
| 50 | 55 | import { |
| 51 | 56 | codexContinuationIdentity, |
| 52 | 57 | materializeCodexShadowHome, |
| ... | ... | @@ -75,7 +80,8 @@ export type CodexDriverEnv = |
| 75 | 80 | | HttpClient.HttpClient |
| 76 | 81 | | Path.Path |
| 77 | 82 | | ProviderEventLoggers |
| 78 | | ServerConfig; | |
| 83 | | ServerConfig | |
| 84 | | ServerSettingsService; | |
| 79 | 85 | |
| 80 | 86 | /** |
| 81 | 87 | * Stamp instance identity onto a `ServerProvider` snapshot produced by the |
| ... | ... | @@ -111,6 +117,7 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = { |
| 111 | 117 | Effect.gen(function* () { |
| 112 | 118 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 113 | 119 | const httpClient = yield* HttpClient.HttpClient; |
| 120 | const serverSettings = yield* ServerSettingsService; | |
| 114 | 121 | const eventLoggers = yield* ProviderEventLoggers; |
| 115 | 122 | const processEnv = mergeProviderInstanceEnvironment(environment); |
| 116 | 123 | const homeLayout = yield* resolveCodexHomeLayout(config); |
| ... | ... | @@ -163,16 +170,19 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = { |
| 163 | 170 | Effect.map(stampIdentity), |
| 164 | 171 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 165 | 172 | ); |
| 166 | const snapshot = yield* makeManagedServerProvider<CodexSettings>({ | |
| 173 | const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings); | |
| 174 | const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<CodexSettings>>({ | |
| 167 | 175 | maintenanceCapabilities, |
| 168 | getSettings: Effect.succeed(effectiveConfig), | |
| 169 | streamSettings: Stream.never, | |
| 170 | haveSettingsChanged: () => false, | |
| 176 | getSettings: snapshotSettings.getSettings, | |
| 177 | streamSettings: snapshotSettings.streamSettings, | |
| 178 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, | |
| 171 | 179 | initialSnapshot: (settings) => |
| 172 | makePendingCodexProvider(settings).pipe(Effect.map(stampIdentity)), | |
| 180 | makePendingCodexProvider(settings.provider).pipe(Effect.map(stampIdentity)), | |
| 173 | 181 | checkProvider, |
| 174 | enrichSnapshot: ({ snapshot, publishSnapshot }) => | |
| 175 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( | |
| 182 | enrichSnapshot: ({ settings, snapshot, publishSnapshot }) => | |
| 183 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities, { | |
| 184 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, | |
| 185 | }).pipe( | |
| 176 | 186 | Effect.provideService(HttpClient.HttpClient, httpClient), |
| 177 | 187 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), |
| 178 | 188 | ), |
apps/server/src/provider/Drivers/CursorDriver.ts+17-8| ... | ... | @@ -18,11 +18,11 @@ import * as Effect from "effect/Effect"; |
| 18 | 18 | import * as FileSystem from "effect/FileSystem"; |
| 19 | 19 | import * as Path from "effect/Path"; |
| 20 | 20 | import * as Schema from "effect/Schema"; |
| 21 | import * as Stream from "effect/Stream"; | |
| 22 | 21 | import { HttpClient } from "effect/unstable/http"; |
| 23 | 22 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 24 | 23 | |
| 25 | 24 | import { ServerConfig } from "../../config.ts"; |
| 25 | import { ServerSettingsService } from "../../serverSettings.ts"; | |
| 26 | 26 | import { makeCursorTextGeneration } from "../../textGeneration/CursorTextGeneration.ts"; |
| 27 | 27 | import { ProviderDriverError } from "../Errors.ts"; |
| 28 | 28 | import { makeCursorAdapter } from "../Layers/CursorAdapter.ts"; |
| ... | ... | @@ -45,6 +45,11 @@ import { |
| 45 | 45 | makeStaticProviderMaintenanceResolver, |
| 46 | 46 | resolveProviderMaintenanceCapabilitiesEffect, |
| 47 | 47 | } from "../providerMaintenance.ts"; |
| 48 | import { | |
| 49 | haveProviderSnapshotSettingsChanged, | |
| 50 | makeProviderSnapshotSettingsSource, | |
| 51 | type ProviderSnapshotSettings, | |
| 52 | } from "../providerUpdateSettings.ts"; | |
| 48 | 53 | const decodeCursorSettings = Schema.decodeSync(CursorSettings); |
| 49 | 54 | |
| 50 | 55 | const DRIVER_KIND = ProviderDriverKind.make("cursor"); |
| ... | ... | @@ -66,7 +71,8 @@ export type CursorDriverEnv = |
| 66 | 71 | | HttpClient.HttpClient |
| 67 | 72 | | Path.Path |
| 68 | 73 | | ProviderEventLoggers |
| 69 | | ServerConfig; | |
| 74 | | ServerConfig | |
| 75 | | ServerSettingsService; | |
| 70 | 76 | |
| 71 | 77 | const withInstanceIdentity = |
| 72 | 78 | (input: { |
| ... | ... | @@ -98,6 +104,7 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = { |
| 98 | 104 | const fileSystem = yield* FileSystem.FileSystem; |
| 99 | 105 | const path = yield* Path.Path; |
| 100 | 106 | const httpClient = yield* HttpClient.HttpClient; |
| 107 | const serverSettings = yield* ServerSettingsService; | |
| 101 | 108 | const eventLoggers = yield* ProviderEventLoggers; |
| 102 | 109 | const processEnv = mergeProviderInstanceEnvironment(environment); |
| 103 | 110 | const continuationIdentity = defaultProviderContinuationIdentity({ |
| ... | ... | @@ -130,21 +137,23 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = { |
| 130 | 137 | Effect.provideService(Path.Path, path), |
| 131 | 138 | ); |
| 132 | 139 | |
| 133 | const snapshot = yield* makeManagedServerProvider<CursorSettings>({ | |
| 140 | const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings); | |
| 141 | const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<CursorSettings>>({ | |
| 134 | 142 | maintenanceCapabilities, |
| 135 | getSettings: Effect.succeed(effectiveConfig), | |
| 136 | streamSettings: Stream.never, | |
| 137 | haveSettingsChanged: () => false, | |
| 143 | getSettings: snapshotSettings.getSettings, | |
| 144 | streamSettings: snapshotSettings.streamSettings, | |
| 145 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, | |
| 138 | 146 | initialSnapshot: (settings) => |
| 139 | buildInitialCursorProviderSnapshot(settings).pipe(Effect.map(stampIdentity)), | |
| 147 | buildInitialCursorProviderSnapshot(settings.provider).pipe(Effect.map(stampIdentity)), | |
| 140 | 148 | checkProvider, |
| 141 | 149 | // Model catalog and capabilities come exclusively from Cursor's |
| 142 | 150 | // list_available_models extension method during provider checks. |
| 143 | 151 | enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) => |
| 144 | 152 | enrichCursorSnapshot({ |
| 145 | settings, | |
| 153 | settings: settings.provider, | |
| 146 | 154 | snapshot: currentSnapshot, |
| 147 | 155 | maintenanceCapabilities, |
| 156 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, | |
| 148 | 157 | publishSnapshot, |
| 149 | 158 | stampIdentity, |
| 150 | 159 | httpClient, |
apps/server/src/provider/Drivers/GrokDriver.ts+17-8| ... | ... | @@ -5,11 +5,11 @@ import * as Effect from "effect/Effect"; |
| 5 | 5 | import * as FileSystem from "effect/FileSystem"; |
| 6 | 6 | import * as Path from "effect/Path"; |
| 7 | 7 | import * as Schema from "effect/Schema"; |
| 8 | import * as Stream from "effect/Stream"; | |
| 9 | 8 | import { HttpClient } from "effect/unstable/http"; |
| 10 | 9 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 11 | 10 | |
| 12 | 11 | import { ServerConfig } from "../../config.ts"; |
| 12 | import { ServerSettingsService } from "../../serverSettings.ts"; | |
| 13 | 13 | import { makeGrokTextGeneration } from "../../textGeneration/GrokTextGeneration.ts"; |
| 14 | 14 | import { ProviderDriverError } from "../Errors.ts"; |
| 15 | 15 | import { makeGrokAdapter } from "../Layers/GrokAdapter.ts"; |
| ... | ... | @@ -32,6 +32,11 @@ import { |
| 32 | 32 | makeStaticProviderMaintenanceResolver, |
| 33 | 33 | resolveProviderMaintenanceCapabilitiesEffect, |
| 34 | 34 | } from "../providerMaintenance.ts"; |
| 35 | import { | |
| 36 | haveProviderSnapshotSettingsChanged, | |
| 37 | makeProviderSnapshotSettingsSource, | |
| 38 | type ProviderSnapshotSettings, | |
| 39 | } from "../providerUpdateSettings.ts"; | |
| 35 | 40 | const decodeGrokSettings = Schema.decodeSync(GrokSettings); |
| 36 | 41 | |
| 37 | 42 | const DRIVER_KIND = ProviderDriverKind.make("grok"); |
| ... | ... | @@ -50,7 +55,8 @@ export type GrokDriverEnv = |
| 50 | 55 | | HttpClient.HttpClient |
| 51 | 56 | | Path.Path |
| 52 | 57 | | ProviderEventLoggers |
| 53 | | ServerConfig; | |
| 58 | | ServerConfig | |
| 59 | | ServerSettingsService; | |
| 54 | 60 | |
| 55 | 61 | const withInstanceIdentity = |
| 56 | 62 | (input: { |
| ... | ... | @@ -80,6 +86,7 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = { |
| 80 | 86 | Effect.gen(function* () { |
| 81 | 87 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 82 | 88 | const httpClient = yield* HttpClient.HttpClient; |
| 89 | const serverSettings = yield* ServerSettingsService; | |
| 83 | 90 | const eventLoggers = yield* ProviderEventLoggers; |
| 84 | 91 | const processEnv = mergeProviderInstanceEnvironment(environment); |
| 85 | 92 | const continuationIdentity = defaultProviderContinuationIdentity({ |
| ... | ... | @@ -110,18 +117,20 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = { |
| 110 | 117 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 111 | 118 | ); |
| 112 | 119 | |
| 113 | const snapshot = yield* makeManagedServerProvider<GrokSettings>({ | |
| 120 | const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings); | |
| 121 | const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<GrokSettings>>({ | |
| 114 | 122 | maintenanceCapabilities, |
| 115 | getSettings: Effect.succeed(effectiveConfig), | |
| 116 | streamSettings: Stream.never, | |
| 117 | haveSettingsChanged: () => false, | |
| 123 | getSettings: snapshotSettings.getSettings, | |
| 124 | streamSettings: snapshotSettings.streamSettings, | |
| 125 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, | |
| 118 | 126 | initialSnapshot: (settings) => |
| 119 | buildInitialGrokProviderSnapshot(settings).pipe(Effect.map(stampIdentity)), | |
| 127 | buildInitialGrokProviderSnapshot(settings.provider).pipe(Effect.map(stampIdentity)), | |
| 120 | 128 | checkProvider, |
| 121 | enrichSnapshot: ({ snapshot: currentSnapshot, publishSnapshot }) => | |
| 129 | enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) => | |
| 122 | 130 | enrichGrokSnapshot({ |
| 123 | 131 | snapshot: currentSnapshot, |
| 124 | 132 | maintenanceCapabilities, |
| 133 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, | |
| 125 | 134 | publishSnapshot, |
| 126 | 135 | httpClient, |
| 127 | 136 | }), |
apps/server/src/provider/Drivers/OpenCodeDriver.ts+29-17| ... | ... | @@ -19,12 +19,12 @@ import * as Effect from "effect/Effect"; |
| 19 | 19 | import * as FileSystem from "effect/FileSystem"; |
| 20 | 20 | import * as Path from "effect/Path"; |
| 21 | 21 | import * as Schema from "effect/Schema"; |
| 22 | import * as Stream from "effect/Stream"; | |
| 23 | 22 | import { HttpClient } from "effect/unstable/http"; |
| 24 | 23 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 25 | 24 | |
| 26 | 25 | import { makeOpenCodeTextGeneration } from "../../textGeneration/OpenCodeTextGeneration.ts"; |
| 27 | 26 | import { ServerConfig } from "../../config.ts"; |
| 27 | import { ServerSettingsService } from "../../serverSettings.ts"; | |
| 28 | 28 | import { ProviderDriverError } from "../Errors.ts"; |
| 29 | 29 | import { makeOpenCodeAdapter } from "../Layers/OpenCodeAdapter.ts"; |
| 30 | 30 | import { |
| ... | ... | @@ -47,6 +47,11 @@ import { |
| 47 | 47 | normalizeCommandPath, |
| 48 | 48 | resolveProviderMaintenanceCapabilitiesEffect, |
| 49 | 49 | } from "../providerMaintenance.ts"; |
| 50 | import { | |
| 51 | haveProviderSnapshotSettingsChanged, | |
| 52 | makeProviderSnapshotSettingsSource, | |
| 53 | type ProviderSnapshotSettings, | |
| 54 | } from "../providerUpdateSettings.ts"; | |
| 50 | 55 | const decodeOpenCodeSettings = Schema.decodeSync(OpenCodeSettings); |
| 51 | 56 | |
| 52 | 57 | const DRIVER_KIND = ProviderDriverKind.make("opencode"); |
| ... | ... | @@ -80,7 +85,8 @@ export type OpenCodeDriverEnv = |
| 80 | 85 | | OpenCodeRuntime |
| 81 | 86 | | Path.Path |
| 82 | 87 | | ProviderEventLoggers |
| 83 | | ServerConfig; | |
| 88 | | ServerConfig | |
| 89 | | ServerSettingsService; | |
| 84 | 90 | |
| 85 | 91 | const withInstanceIdentity = |
| 86 | 92 | (input: { |
| ... | ... | @@ -111,6 +117,7 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv> |
| 111 | 117 | const openCodeRuntime = yield* OpenCodeRuntime; |
| 112 | 118 | const serverConfig = yield* ServerConfig; |
| 113 | 119 | const httpClient = yield* HttpClient.HttpClient; |
| 120 | const serverSettings = yield* ServerSettingsService; | |
| 114 | 121 | const eventLoggers = yield* ProviderEventLoggers; |
| 115 | 122 | const processEnv = mergeProviderInstanceEnvironment(environment); |
| 116 | 123 | const continuationIdentity = defaultProviderContinuationIdentity({ |
| ... | ... | @@ -142,21 +149,26 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv> |
| 142 | 149 | processEnv, |
| 143 | 150 | ).pipe(Effect.map(stampIdentity), Effect.provideService(OpenCodeRuntime, openCodeRuntime)); |
| 144 | 151 | |
| 145 | const snapshot = yield* makeManagedServerProvider<OpenCodeSettings>({ | |
| 146 | maintenanceCapabilities, | |
| 147 | getSettings: Effect.succeed(effectiveConfig), | |
| 148 | streamSettings: Stream.never, | |
| 149 | haveSettingsChanged: () => false, | |
| 150 | initialSnapshot: (settings) => | |
| 151 | makePendingOpenCodeProvider(settings).pipe(Effect.map(stampIdentity)), | |
| 152 | checkProvider, | |
| 153 | enrichSnapshot: ({ snapshot, publishSnapshot }) => | |
| 154 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( | |
| 155 | Effect.provideService(HttpClient.HttpClient, httpClient), | |
| 156 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), | |
| 157 | ), | |
| 158 | refreshInterval: SNAPSHOT_REFRESH_INTERVAL, | |
| 159 | }).pipe( | |
| 152 | const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings); | |
| 153 | const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<OpenCodeSettings>>( | |
| 154 | { | |
| 155 | maintenanceCapabilities, | |
| 156 | getSettings: snapshotSettings.getSettings, | |
| 157 | streamSettings: snapshotSettings.streamSettings, | |
| 158 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, | |
| 159 | initialSnapshot: (settings) => | |
| 160 | makePendingOpenCodeProvider(settings.provider).pipe(Effect.map(stampIdentity)), | |
| 161 | checkProvider, | |
| 162 | enrichSnapshot: ({ settings, snapshot, publishSnapshot }) => | |
| 163 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities, { | |
| 164 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, | |
| 165 | }).pipe( | |
| 166 | Effect.provideService(HttpClient.HttpClient, httpClient), | |
| 167 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), | |
| 168 | ), | |
| 169 | refreshInterval: SNAPSHOT_REFRESH_INTERVAL, | |
| 170 | }, | |
| 171 | ).pipe( | |
| 160 | 172 | Effect.mapError( |
| 161 | 173 | (cause) => |
| 162 | 174 | new ProviderDriverError({ |
apps/server/src/provider/Layers/CursorProvider.ts+4-1| ... | ... | @@ -1106,6 +1106,7 @@ export const enrichCursorSnapshot = (input: { |
| 1106 | 1106 | readonly settings: CursorSettings; |
| 1107 | 1107 | readonly snapshot: ServerProvider; |
| 1108 | 1108 | readonly maintenanceCapabilities: ProviderMaintenanceCapabilities; |
| 1109 | readonly enableProviderUpdateChecks?: boolean; | |
| 1109 | 1110 | readonly publishSnapshot: (snapshot: ServerProvider) => Effect.Effect<void>; |
| 1110 | 1111 | readonly stampIdentity?: (snapshot: ServerProvider) => ServerProvider; |
| 1111 | 1112 | readonly httpClient: HttpClient.HttpClient; |
| ... | ... | @@ -1117,7 +1118,9 @@ export const enrichCursorSnapshot = (input: { |
| 1117 | 1118 | return Effect.void; |
| 1118 | 1119 | } |
| 1119 | 1120 | |
| 1120 | return enrichProviderSnapshotWithVersionAdvisory(snapshot, input.maintenanceCapabilities).pipe( | |
| 1121 | return enrichProviderSnapshotWithVersionAdvisory(snapshot, input.maintenanceCapabilities, { | |
| 1122 | enableProviderUpdateChecks: input.enableProviderUpdateChecks, | |
| 1123 | }).pipe( | |
| 1121 | 1124 | Effect.provideService(HttpClient.HttpClient, input.httpClient), |
| 1122 | 1125 | Effect.flatMap((enrichedSnapshot) => |
| 1123 | 1126 | publishSnapshot(stampIdentity(enrichedSnapshot)).pipe(Effect.as(enrichedSnapshot)), |
apps/server/src/provider/Layers/GrokProvider.ts+4-1| ... | ... | @@ -311,12 +311,15 @@ export const checkGrokProviderStatus = Effect.fn("checkGrokProviderStatus")(func |
| 311 | 311 | export const enrichGrokSnapshot = (input: { |
| 312 | 312 | readonly snapshot: ServerProvider; |
| 313 | 313 | readonly maintenanceCapabilities: ProviderMaintenanceCapabilities; |
| 314 | readonly enableProviderUpdateChecks?: boolean; | |
| 314 | 315 | readonly publishSnapshot: (snapshot: ServerProvider) => Effect.Effect<void>; |
| 315 | 316 | readonly httpClient: HttpClient.HttpClient; |
| 316 | 317 | }): Effect.Effect<void> => { |
| 317 | 318 | const { snapshot, publishSnapshot } = input; |
| 318 | 319 | |
| 319 | return enrichProviderSnapshotWithVersionAdvisory(snapshot, input.maintenanceCapabilities).pipe( | |
| 320 | return enrichProviderSnapshotWithVersionAdvisory(snapshot, input.maintenanceCapabilities, { | |
| 321 | enableProviderUpdateChecks: input.enableProviderUpdateChecks, | |
| 322 | }).pipe( | |
| 320 | 323 | Effect.provideService(HttpClient.HttpClient, input.httpClient), |
| 321 | 324 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), |
| 322 | 325 | Effect.catchCause((cause) => |
apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts+3| ... | ... | @@ -39,6 +39,7 @@ import * as Layer from "effect/Layer"; |
| 39 | 39 | import { HttpClient, HttpClientResponse } from "effect/unstable/http"; |
| 40 | 40 | |
| 41 | 41 | import { ServerConfig } from "../../config.ts"; |
| 42 | import { ServerSettingsService } from "../../serverSettings.ts"; | |
| 42 | 43 | import { ClaudeDriver } from "../Drivers/ClaudeDriver.ts"; |
| 43 | 44 | import { CodexDriver } from "../Drivers/CodexDriver.ts"; |
| 44 | 45 | import { CursorDriver } from "../Drivers/CursorDriver.ts"; |
| ... | ... | @@ -107,6 +108,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 107 | 108 | prefix: "provider-instance-registry-test", |
| 108 | 109 | }).pipe( |
| 109 | 110 | Layer.provideMerge(NodeServices.layer), |
| 111 | Layer.provideMerge(ServerSettingsService.layerTest()), | |
| 110 | 112 | Layer.provideMerge(TestHttpClientLive), |
| 111 | 113 | Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)), |
| 112 | 114 | ); |
| ... | ... | @@ -244,6 +246,7 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => { |
| 244 | 246 | prefix: "provider-instance-registry-all-drivers-test", |
| 245 | 247 | }).pipe( |
| 246 | 248 | Layer.provideMerge(infraLayer), |
| 249 | Layer.provideMerge(ServerSettingsService.layerTest()), | |
| 247 | 250 | Layer.provideMerge(TestHttpClientLive), |
| 248 | 251 | Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)), |
| 249 | 252 | ); |
apps/server/src/provider/makeManagedServerProvider.ts+1-1| ... | ... | @@ -21,7 +21,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")( |
| 21 | 21 | Settings, |
| 22 | 22 | >(input: { |
| 23 | 23 | readonly maintenanceCapabilities: ServerProviderShape["maintenanceCapabilities"]; |
| 24 | readonly getSettings: Effect.Effect<Settings>; | |
| 24 | readonly getSettings: Effect.Effect<Settings, ServerSettingsError>; | |
| 25 | 25 | readonly streamSettings: Stream.Stream<Settings>; |
| 26 | 26 | readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean; |
| 27 | 27 | readonly initialSnapshot: (settings: Settings) => Effect.Effect<ServerProvider>; |
apps/server/src/provider/providerMaintenance.test.ts+41-1| ... | ... | @@ -4,13 +4,14 @@ import { chmodSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs"; |
| 4 | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | 5 | import * as NodeOS from "node:os"; |
| 6 | 6 | import path from "node:path"; |
| 7 | import { ProviderDriverKind } from "@t3tools/contracts"; | |
| 7 | import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts"; | |
| 8 | 8 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| 9 | 9 | import * as Crypto from "effect/Crypto"; |
| 10 | 10 | import * as Effect from "effect/Effect"; |
| 11 | 11 | import { HttpClient } from "effect/unstable/http"; |
| 12 | 12 | import { |
| 13 | 13 | createProviderVersionAdvisory, |
| 14 | enrichProviderSnapshotWithVersionAdvisory, | |
| 14 | 15 | makePackageManagedProviderMaintenanceResolver, |
| 15 | 16 | makeProviderMaintenanceCapabilities, |
| 16 | 17 | makeStaticProviderMaintenanceResolver, |
| ... | ... | @@ -67,6 +68,19 @@ const staticToolUpdate = makeStaticProviderMaintenanceResolver( |
| 67 | 68 | updateLockKey: "static-tool", |
| 68 | 69 | }), |
| 69 | 70 | ); |
| 71 | const installedPackageToolProvider: ServerProvider = { | |
| 72 | instanceId: ProviderInstanceId.make("packageTool"), | |
| 73 | driver: driver("packageTool"), | |
| 74 | enabled: true, | |
| 75 | installed: true, | |
| 76 | version: "1.0.0", | |
| 77 | status: "ready", | |
| 78 | auth: { status: "authenticated" }, | |
| 79 | checkedAt: "2026-04-10T00:00:00.000Z", | |
| 80 | models: [], | |
| 81 | slashCommands: [], | |
| 82 | skills: [], | |
| 83 | }; | |
| 70 | 84 | |
| 71 | 85 | it.layer(NodeServices.layer)("providerMaintenance", (it) => { |
| 72 | 86 | it.effect("reads cached versions through the injectable cache reference", () => |
| ... | ... | @@ -95,6 +109,32 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { |
| 95 | 109 | ), |
| 96 | 110 | ); |
| 97 | 111 | |
| 112 | it.effect("does not fetch latest provider versions when update checks are disabled", () => | |
| 113 | enrichProviderSnapshotWithVersionAdvisory( | |
| 114 | installedPackageToolProvider, | |
| 115 | packageToolUpdate.resolve(), | |
| 116 | { | |
| 117 | enableProviderUpdateChecks: false, | |
| 118 | }, | |
| 119 | ).pipe( | |
| 120 | Effect.provideService(ProviderVersionCache, new Map()), | |
| 121 | Effect.provideService( | |
| 122 | HttpClient.HttpClient, | |
| 123 | HttpClient.make(() => | |
| 124 | Effect.die("disabled provider update checks should not make an HTTP request"), | |
| 125 | ), | |
| 126 | ), | |
| 127 | Effect.map((provider) => { | |
| 128 | expect(provider.versionAdvisory).toMatchObject({ | |
| 129 | status: "unknown", | |
| 130 | currentVersion: "1.0.0", | |
| 131 | latestVersion: null, | |
| 132 | checkedAt: "2026-04-10T00:00:00.000Z", | |
| 133 | }); | |
| 134 | }), | |
| 135 | ), | |
| 136 | ); | |
| 137 | ||
| 98 | 138 | it("marks providers with unknown current versions as unknown", () => { |
| 99 | 139 | expect( |
| 100 | 140 | createProviderVersionAdvisory({ |
apps/server/src/provider/providerMaintenance.ts+13-2| ... | ... | @@ -468,10 +468,21 @@ export const resolveLatestProviderVersion = Effect.fn("resolveLatestProviderVers |
| 468 | 468 | |
| 469 | 469 | export const enrichProviderSnapshotWithVersionAdvisory = Effect.fn( |
| 470 | 470 | "enrichProviderSnapshotWithVersionAdvisory", |
| 471 | )(function* (snapshot: ServerProvider, maintenanceCapabilities?: ProviderMaintenanceCapabilities) { | |
| 471 | )(function* ( | |
| 472 | snapshot: ServerProvider, | |
| 473 | maintenanceCapabilities?: ProviderMaintenanceCapabilities, | |
| 474 | options?: { | |
| 475 | readonly enableProviderUpdateChecks: boolean | undefined; | |
| 476 | }, | |
| 477 | ) { | |
| 472 | 478 | const capabilities = |
| 473 | 479 | maintenanceCapabilities ?? makeManualProviderMaintenanceCapabilities(snapshot.driver); |
| 474 | if (!snapshot.enabled || !snapshot.installed || !snapshot.version) { | |
| 480 | const shouldResolveLatestVersion = | |
| 481 | options?.enableProviderUpdateChecks !== false && | |
| 482 | snapshot.enabled && | |
| 483 | snapshot.installed && | |
| 484 | Boolean(snapshot.version); | |
| 485 | if (!shouldResolveLatestVersion) { | |
| 475 | 486 | return { |
| 476 | 487 | ...snapshot, |
| 477 | 488 | versionAdvisory: createProviderVersionAdvisory({ |
apps/server/src/provider/providerUpdateSettings.ts created+43| ... | ... | @@ -0,0 +1,43 @@ |
| 1 | import type { ServerSettings, ServerSettingsError } from "@t3tools/contracts"; | |
| 2 | import * as Effect from "effect/Effect"; | |
| 3 | import * as Equal from "effect/Equal"; | |
| 4 | import * as Stream from "effect/Stream"; | |
| 5 | ||
| 6 | import type { ServerSettingsShape } from "../serverSettings.ts"; | |
| 7 | ||
| 8 | export interface ProviderSnapshotSettings<Settings> { | |
| 9 | readonly provider: Settings; | |
| 10 | readonly enableProviderUpdateChecks: boolean; | |
| 11 | } | |
| 12 | ||
| 13 | export function makeProviderSnapshotSettings<Settings>( | |
| 14 | provider: Settings, | |
| 15 | settings: ServerSettings, | |
| 16 | ): ProviderSnapshotSettings<Settings> { | |
| 17 | return { | |
| 18 | provider, | |
| 19 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, | |
| 20 | }; | |
| 21 | } | |
| 22 | ||
| 23 | export function haveProviderSnapshotSettingsChanged<Settings>( | |
| 24 | previous: ProviderSnapshotSettings<Settings>, | |
| 25 | next: ProviderSnapshotSettings<Settings>, | |
| 26 | ): boolean { | |
| 27 | return !Equal.equals(previous, next); | |
| 28 | } | |
| 29 | ||
| 30 | export function makeProviderSnapshotSettingsSource<Settings>( | |
| 31 | provider: Settings, | |
| 32 | serverSettings: ServerSettingsShape, | |
| 33 | ): { | |
| 34 | readonly getSettings: Effect.Effect<ProviderSnapshotSettings<Settings>, ServerSettingsError>; | |
| 35 | readonly streamSettings: Stream.Stream<ProviderSnapshotSettings<Settings>>; | |
| 36 | } { | |
| 37 | const mapSettings = (settings: ServerSettings) => | |
| 38 | makeProviderSnapshotSettings(provider, settings); | |
| 39 | return { | |
| 40 | getSettings: serverSettings.getSettings.pipe(Effect.map(mapSettings)), | |
| 41 | streamSettings: serverSettings.streamChanges.pipe(Stream.map(mapSettings)), | |
| 42 | }; | |
| 43 | } |
apps/web/src/components/settings/SettingsPanels.tsx+27| ... | ... | @@ -668,6 +668,33 @@ export function GeneralSettingsPanel() { |
| 668 | 668 | } |
| 669 | 669 | /> |
| 670 | 670 | |
| 671 | <SettingsRow | |
| 672 | title="Provider update checks" | |
| 673 | description="Check installed provider CLIs for newer available versions." | |
| 674 | resetAction={ | |
| 675 | settings.enableProviderUpdateChecks !== | |
| 676 | DEFAULT_UNIFIED_SETTINGS.enableProviderUpdateChecks ? ( | |
| 677 | <SettingResetButton | |
| 678 | label="provider update checks" | |
| 679 | onClick={() => | |
| 680 | updateSettings({ | |
| 681 | enableProviderUpdateChecks: DEFAULT_UNIFIED_SETTINGS.enableProviderUpdateChecks, | |
| 682 | }) | |
| 683 | } | |
| 684 | /> | |
| 685 | ) : null | |
| 686 | } | |
| 687 | control={ | |
| 688 | <Switch | |
| 689 | checked={settings.enableProviderUpdateChecks} | |
| 690 | onCheckedChange={(checked) => | |
| 691 | updateSettings({ enableProviderUpdateChecks: Boolean(checked) }) | |
| 692 | } | |
| 693 | aria-label="Check provider versions" | |
| 694 | /> | |
| 695 | } | |
| 696 | /> | |
| 697 | ||
| 671 | 698 | <SettingsRow |
| 672 | 699 | title="Auto-open task panel" |
| 673 | 700 | description="Open the right-side plan and task panel automatically when steps appear." |
packages/contracts/src/settings.ts+2| ... | ... | @@ -365,6 +365,7 @@ export const DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL = Duration.seconds(30); |
| 365 | 365 | |
| 366 | 366 | export const ServerSettings = Schema.Struct({ |
| 367 | 367 | enableAssistantStreaming: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), |
| 368 | enableProviderUpdateChecks: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), | |
| 368 | 369 | automaticGitFetchInterval: Schema.DurationFromMillis.pipe( |
| 369 | 370 | Schema.withDecodingDefault( |
| 370 | 371 | Effect.succeed(Duration.toMillis(DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL)), |
| ... | ... | @@ -482,6 +483,7 @@ const OpenCodeSettingsPatch = Schema.Struct({ |
| 482 | 483 | export const ServerSettingsPatch = Schema.Struct({ |
| 483 | 484 | // Server settings |
| 484 | 485 | enableAssistantStreaming: Schema.optionalKey(Schema.Boolean), |
| 486 | enableProviderUpdateChecks: Schema.optionalKey(Schema.Boolean), | |
| 485 | 487 | automaticGitFetchInterval: Schema.optionalKey(Schema.DurationFromMillis), |
| 486 | 488 | defaultThreadEnvMode: Schema.optionalKey(ThreadEnvMode), |
| 487 | 489 | newWorktreesStartFromOrigin: Schema.optionalKey(Schema.Boolean), |