Unified · Split
| @@ -20,12 +20,12 @@ import * as Effect from "effect/Effect"; · 19 unmodified lines | ||
| 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 { · 16 unmodified lines | ||
| 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 = · 29 unmodified lines | ||
| 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> = { · 24 unmodified lines | ||
| 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> = { · 43 unmodified lines | ||
| 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 | ), |
| @@ -28,12 +28,12 @@ import * as Effect from "effect/Effect"; · 27 unmodified lines | ||
| 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 { · 7 unmodified lines | ||
| 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 = · 22 unmodified lines | ||
| 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> = { · 29 unmodified lines | ||
| 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> = { · 46 unmodified lines | ||
| 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 | ), |
| @@ -18,11 +18,11 @@ import * as Effect from "effect/Effect"; · 17 unmodified lines | ||
| 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 { · 16 unmodified lines | ||
| 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 = · 15 unmodified lines | ||
| 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> = { · 25 unmodified lines | ||
| 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> = { · 26 unmodified lines | ||
| 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, |
| @@ -5,11 +5,11 @@ import * as Effect from "effect/Effect"; · 4 unmodified lines | ||
| 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 { · 16 unmodified lines | ||
| 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 = · 12 unmodified lines | ||
| 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> = { · 23 unmodified lines | ||
| 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> = { · 24 unmodified lines | ||
| 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 | }), |
| @@ -19,12 +19,12 @@ import * as Effect from "effect/Effect"; · 18 unmodified lines | ||
| 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 { · 16 unmodified lines | ||
| 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 = · 27 unmodified lines | ||
| 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> · 24 unmodified lines | ||
| 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> · 25 unmodified lines | ||
| 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({ |
| @@ -1106,6 +1106,7 @@ export const enrichCursorSnapshot = (input: { · 1105 unmodified lines | ||
| 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: { · 5 unmodified lines | ||
| 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)), |
| @@ -311,12 +311,15 @@ export const checkGrokProviderStatus = Effect.fn("checkGrokProviderStatus")(func · 310 unmodified lines | ||
| 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) => |
| @@ -39,6 +39,7 @@ import * as Layer from "effect/Layer"; · 38 unmodified lines | ||
| 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", () => { · 62 unmodified lines | ||
| 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", () => { · 131 unmodified lines | ||
| 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 | ); |
| @@ -21,7 +21,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")( · 20 unmodified lines | ||
| 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>; |
| @@ -4,13 +4,14 @@ import { chmodSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs"; · 3 unmodified lines | ||
| 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( · 50 unmodified lines | ||
| 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) => { · 22 unmodified lines | ||
| 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({ |
| @@ -468,10 +468,21 @@ export const resolveLatestProviderVersion = Effect.fn("resolveLatestProviderVers · 467 unmodified lines | ||
| 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({ |
| @@ -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 | +} | |
| @@ -668,6 +668,33 @@ export function GeneralSettingsPanel() { · 667 unmodified lines | ||
| 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." |
| @@ -365,6 +365,7 @@ export const DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL = Duration.seconds(30); · 364 unmodified lines | ||
| 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({ · 111 unmodified lines | ||
| 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), |