| 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,12 +20,12 @@ import * as Effect from "effect/Effect"; |
| 20 | import * as FileSystem from "effect/FileSystem"; | 20 | import * as FileSystem from "effect/FileSystem"; |
| 21 | import * as Path from "effect/Path"; | 21 | import * as Path from "effect/Path"; |
| 22 | import * as Schema from "effect/Schema"; | 22 | import * as Schema from "effect/Schema"; |
| 23 | import * as Stream from "effect/Stream"; | ||
| 24 | import { HttpClient } from "effect/unstable/http"; | 23 | import { HttpClient } from "effect/unstable/http"; |
| 25 | import { ChildProcessSpawner } from "effect/unstable/process"; | 24 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 26 | 25 | ||
| 27 | import { makeClaudeTextGeneration } from "../../textGeneration/ClaudeTextGeneration.ts"; | 26 | import { makeClaudeTextGeneration } from "../../textGeneration/ClaudeTextGeneration.ts"; |
| 28 | import { ServerConfig } from "../../config.ts"; | 27 | import { ServerConfig } from "../../config.ts"; |
| 28 | import { ServerSettingsService } from "../../serverSettings.ts"; | ||
| 29 | import { ProviderDriverError } from "../Errors.ts"; | 29 | import { ProviderDriverError } from "../Errors.ts"; |
| 30 | import { makeClaudeAdapter } from "../Layers/ClaudeAdapter.ts"; | 30 | import { makeClaudeAdapter } from "../Layers/ClaudeAdapter.ts"; |
| 31 | import { | 31 | import { |
| ... | @@ -48,6 +48,11 @@ import { | ... | @@ -48,6 +48,11 @@ import { |
| 48 | normalizeCommandPath, | 48 | normalizeCommandPath, |
| 49 | resolveProviderMaintenanceCapabilitiesEffect, | 49 | resolveProviderMaintenanceCapabilitiesEffect, |
| 50 | } from "../providerMaintenance.ts"; | 50 | } from "../providerMaintenance.ts"; |
| 51 | import { | ||
| 52 | haveProviderSnapshotSettingsChanged, | ||
| 53 | makeProviderSnapshotSettingsSource, | ||
| 54 | type ProviderSnapshotSettings, | ||
| 55 | } from "../providerUpdateSettings.ts"; | ||
| 51 | import { makeClaudeCapabilitiesCacheKey, makeClaudeContinuationGroupKey } from "./ClaudeHome.ts"; | 56 | import { makeClaudeCapabilitiesCacheKey, makeClaudeContinuationGroupKey } from "./ClaudeHome.ts"; |
| 52 | const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings); | 57 | const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings); |
| 53 | 58 | ||
| ... | @@ -83,7 +88,8 @@ export type ClaudeDriverEnv = | ... | @@ -83,7 +88,8 @@ export type ClaudeDriverEnv = |
| 83 | | HttpClient.HttpClient | 88 | | HttpClient.HttpClient |
| 84 | | Path.Path | 89 | | Path.Path |
| 85 | | ProviderEventLoggers | 90 | | ProviderEventLoggers |
| 86 | | ServerConfig; | 91 | | ServerConfig |
| 92 | | ServerSettingsService; | ||
| 87 | 93 | ||
| 88 | const withInstanceIdentity = | 94 | const withInstanceIdentity = |
| 89 | (input: { | 95 | (input: { |
| ... | @@ -114,6 +120,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv> = { | ... | @@ -114,6 +120,7 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv> = { |
| 114 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | 120 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 115 | const path = yield* Path.Path; | 121 | const path = yield* Path.Path; |
| 116 | const httpClient = yield* HttpClient.HttpClient; | 122 | const httpClient = yield* HttpClient.HttpClient; |
| 123 | const serverSettings = yield* ServerSettingsService; | ||
| 117 | const eventLoggers = yield* ProviderEventLoggers; | 124 | const eventLoggers = yield* ProviderEventLoggers; |
| 118 | const processEnv = mergeProviderInstanceEnvironment(environment); | 125 | const processEnv = mergeProviderInstanceEnvironment(environment); |
| 119 | const fallbackContinuationIdentity = defaultProviderContinuationIdentity({ | 126 | const fallbackContinuationIdentity = defaultProviderContinuationIdentity({ |
| ... | @@ -163,16 +170,19 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv> = { | ... | @@ -163,16 +170,19 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv> = { |
| 163 | Effect.provideService(Path.Path, path), | 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 | maintenanceCapabilities, | 175 | maintenanceCapabilities, |
| 168 | getSettings: Effect.succeed(effectiveConfig), | 176 | getSettings: snapshotSettings.getSettings, |
| 169 | streamSettings: Stream.never, | 177 | streamSettings: snapshotSettings.streamSettings, |
| 170 | haveSettingsChanged: () => false, | 178 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, |
| 171 | initialSnapshot: (settings) => | 179 | initialSnapshot: (settings) => |
| 172 | makePendingClaudeProvider(settings).pipe(Effect.map(stampIdentity)), | 180 | makePendingClaudeProvider(settings.provider).pipe(Effect.map(stampIdentity)), |
| 173 | checkProvider, | 181 | checkProvider, |
| 174 | enrichSnapshot: ({ snapshot, publishSnapshot }) => | 182 | enrichSnapshot: ({ settings, snapshot, publishSnapshot }) => |
| 175 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( | 183 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities, { |
| 184 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, | ||
| 185 | }).pipe( | ||
| 176 | Effect.provideService(HttpClient.HttpClient, httpClient), | 186 | Effect.provideService(HttpClient.HttpClient, httpClient), |
| 177 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), | 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,12 +28,12 @@ import * as Effect from "effect/Effect"; |
| 28 | import * as FileSystem from "effect/FileSystem"; | 28 | import * as FileSystem from "effect/FileSystem"; |
| 29 | import * as Path from "effect/Path"; | 29 | import * as Path from "effect/Path"; |
| 30 | import * as Schema from "effect/Schema"; | 30 | import * as Schema from "effect/Schema"; |
| 31 | import * as Stream from "effect/Stream"; | ||
| 32 | import { HttpClient } from "effect/unstable/http"; | 31 | import { HttpClient } from "effect/unstable/http"; |
| 33 | import { ChildProcessSpawner } from "effect/unstable/process"; | 32 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 34 | 33 | ||
| 35 | import { makeCodexTextGeneration } from "../../textGeneration/CodexTextGeneration.ts"; | 34 | import { makeCodexTextGeneration } from "../../textGeneration/CodexTextGeneration.ts"; |
| 36 | import { ServerConfig } from "../../config.ts"; | 35 | import { ServerConfig } from "../../config.ts"; |
| 36 | import { ServerSettingsService } from "../../serverSettings.ts"; | ||
| 37 | import { ProviderDriverError } from "../Errors.ts"; | 37 | import { ProviderDriverError } from "../Errors.ts"; |
| 38 | import { makeCodexAdapter } from "../Layers/CodexAdapter.ts"; | 38 | import { makeCodexAdapter } from "../Layers/CodexAdapter.ts"; |
| 39 | import { checkCodexProviderStatus, makePendingCodexProvider } from "../Layers/CodexProvider.ts"; | 39 | import { checkCodexProviderStatus, makePendingCodexProvider } from "../Layers/CodexProvider.ts"; |
| ... | @@ -47,6 +47,11 @@ import { | ... | @@ -47,6 +47,11 @@ import { |
| 47 | makePackageManagedProviderMaintenanceResolver, | 47 | makePackageManagedProviderMaintenanceResolver, |
| 48 | resolveProviderMaintenanceCapabilitiesEffect, | 48 | resolveProviderMaintenanceCapabilitiesEffect, |
| 49 | } from "../providerMaintenance.ts"; | 49 | } from "../providerMaintenance.ts"; |
| 50 | import { | ||
| 51 | haveProviderSnapshotSettingsChanged, | ||
| 52 | makeProviderSnapshotSettingsSource, | ||
| 53 | type ProviderSnapshotSettings, | ||
| 54 | } from "../providerUpdateSettings.ts"; | ||
| 50 | import { | 55 | import { |
| 51 | codexContinuationIdentity, | 56 | codexContinuationIdentity, |
| 52 | materializeCodexShadowHome, | 57 | materializeCodexShadowHome, |
| ... | @@ -75,7 +80,8 @@ export type CodexDriverEnv = | ... | @@ -75,7 +80,8 @@ export type CodexDriverEnv = |
| 75 | | HttpClient.HttpClient | 80 | | HttpClient.HttpClient |
| 76 | | Path.Path | 81 | | Path.Path |
| 77 | | ProviderEventLoggers | 82 | | ProviderEventLoggers |
| 78 | | ServerConfig; | 83 | | ServerConfig |
| 84 | | ServerSettingsService; | ||
| 79 | 85 | ||
| 80 | /** | 86 | /** |
| 81 | * Stamp instance identity onto a `ServerProvider` snapshot produced by the | 87 | * Stamp instance identity onto a `ServerProvider` snapshot produced by the |
| ... | @@ -111,6 +117,7 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = { | ... | @@ -111,6 +117,7 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = { |
| 111 | Effect.gen(function* () { | 117 | Effect.gen(function* () { |
| 112 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | 118 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 113 | const httpClient = yield* HttpClient.HttpClient; | 119 | const httpClient = yield* HttpClient.HttpClient; |
| 120 | const serverSettings = yield* ServerSettingsService; | ||
| 114 | const eventLoggers = yield* ProviderEventLoggers; | 121 | const eventLoggers = yield* ProviderEventLoggers; |
| 115 | const processEnv = mergeProviderInstanceEnvironment(environment); | 122 | const processEnv = mergeProviderInstanceEnvironment(environment); |
| 116 | const homeLayout = yield* resolveCodexHomeLayout(config); | 123 | const homeLayout = yield* resolveCodexHomeLayout(config); |
| ... | @@ -163,16 +170,19 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = { | ... | @@ -163,16 +170,19 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = { |
| 163 | Effect.map(stampIdentity), | 170 | Effect.map(stampIdentity), |
| 164 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), | 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 | maintenanceCapabilities, | 175 | maintenanceCapabilities, |
| 168 | getSettings: Effect.succeed(effectiveConfig), | 176 | getSettings: snapshotSettings.getSettings, |
| 169 | streamSettings: Stream.never, | 177 | streamSettings: snapshotSettings.streamSettings, |
| 170 | haveSettingsChanged: () => false, | 178 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, |
| 171 | initialSnapshot: (settings) => | 179 | initialSnapshot: (settings) => |
| 172 | makePendingCodexProvider(settings).pipe(Effect.map(stampIdentity)), | 180 | makePendingCodexProvider(settings.provider).pipe(Effect.map(stampIdentity)), |
| 173 | checkProvider, | 181 | checkProvider, |
| 174 | enrichSnapshot: ({ snapshot, publishSnapshot }) => | 182 | enrichSnapshot: ({ settings, snapshot, publishSnapshot }) => |
| 175 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( | 183 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities, { |
| 184 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, | ||
| 185 | }).pipe( | ||
| 176 | Effect.provideService(HttpClient.HttpClient, httpClient), | 186 | Effect.provideService(HttpClient.HttpClient, httpClient), |
| 177 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), | 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,11 +18,11 @@ import * as Effect from "effect/Effect"; |
| 18 | import * as FileSystem from "effect/FileSystem"; | 18 | import * as FileSystem from "effect/FileSystem"; |
| 19 | import * as Path from "effect/Path"; | 19 | import * as Path from "effect/Path"; |
| 20 | import * as Schema from "effect/Schema"; | 20 | import * as Schema from "effect/Schema"; |
| 21 | import * as Stream from "effect/Stream"; | ||
| 22 | import { HttpClient } from "effect/unstable/http"; | 21 | import { HttpClient } from "effect/unstable/http"; |
| 23 | import { ChildProcessSpawner } from "effect/unstable/process"; | 22 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 24 | 23 | ||
| 25 | import { ServerConfig } from "../../config.ts"; | 24 | import { ServerConfig } from "../../config.ts"; |
| 25 | import { ServerSettingsService } from "../../serverSettings.ts"; | ||
| 26 | import { makeCursorTextGeneration } from "../../textGeneration/CursorTextGeneration.ts"; | 26 | import { makeCursorTextGeneration } from "../../textGeneration/CursorTextGeneration.ts"; |
| 27 | import { ProviderDriverError } from "../Errors.ts"; | 27 | import { ProviderDriverError } from "../Errors.ts"; |
| 28 | import { makeCursorAdapter } from "../Layers/CursorAdapter.ts"; | 28 | import { makeCursorAdapter } from "../Layers/CursorAdapter.ts"; |
| ... | @@ -45,6 +45,11 @@ import { | ... | @@ -45,6 +45,11 @@ import { |
| 45 | makeStaticProviderMaintenanceResolver, | 45 | makeStaticProviderMaintenanceResolver, |
| 46 | resolveProviderMaintenanceCapabilitiesEffect, | 46 | resolveProviderMaintenanceCapabilitiesEffect, |
| 47 | } from "../providerMaintenance.ts"; | 47 | } from "../providerMaintenance.ts"; |
| 48 | import { | ||
| 49 | haveProviderSnapshotSettingsChanged, | ||
| 50 | makeProviderSnapshotSettingsSource, | ||
| 51 | type ProviderSnapshotSettings, | ||
| 52 | } from "../providerUpdateSettings.ts"; | ||
| 48 | const decodeCursorSettings = Schema.decodeSync(CursorSettings); | 53 | const decodeCursorSettings = Schema.decodeSync(CursorSettings); |
| 49 | 54 | ||
| 50 | const DRIVER_KIND = ProviderDriverKind.make("cursor"); | 55 | const DRIVER_KIND = ProviderDriverKind.make("cursor"); |
| ... | @@ -66,7 +71,8 @@ export type CursorDriverEnv = | ... | @@ -66,7 +71,8 @@ export type CursorDriverEnv = |
| 66 | | HttpClient.HttpClient | 71 | | HttpClient.HttpClient |
| 67 | | Path.Path | 72 | | Path.Path |
| 68 | | ProviderEventLoggers | 73 | | ProviderEventLoggers |
| 69 | | ServerConfig; | 74 | | ServerConfig |
| 75 | | ServerSettingsService; | ||
| 70 | 76 | ||
| 71 | const withInstanceIdentity = | 77 | const withInstanceIdentity = |
| 72 | (input: { | 78 | (input: { |
| ... | @@ -98,6 +104,7 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = { | ... | @@ -98,6 +104,7 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = { |
| 98 | const fileSystem = yield* FileSystem.FileSystem; | 104 | const fileSystem = yield* FileSystem.FileSystem; |
| 99 | const path = yield* Path.Path; | 105 | const path = yield* Path.Path; |
| 100 | const httpClient = yield* HttpClient.HttpClient; | 106 | const httpClient = yield* HttpClient.HttpClient; |
| 107 | const serverSettings = yield* ServerSettingsService; | ||
| 101 | const eventLoggers = yield* ProviderEventLoggers; | 108 | const eventLoggers = yield* ProviderEventLoggers; |
| 102 | const processEnv = mergeProviderInstanceEnvironment(environment); | 109 | const processEnv = mergeProviderInstanceEnvironment(environment); |
| 103 | const continuationIdentity = defaultProviderContinuationIdentity({ | 110 | const continuationIdentity = defaultProviderContinuationIdentity({ |
| ... | @@ -130,21 +137,23 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = { | ... | @@ -130,21 +137,23 @@ export const CursorDriver: ProviderDriver<CursorSettings, CursorDriverEnv> = { |
| 130 | Effect.provideService(Path.Path, path), | 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 | maintenanceCapabilities, | 142 | maintenanceCapabilities, |
| 135 | getSettings: Effect.succeed(effectiveConfig), | 143 | getSettings: snapshotSettings.getSettings, |
| 136 | streamSettings: Stream.never, | 144 | streamSettings: snapshotSettings.streamSettings, |
| 137 | haveSettingsChanged: () => false, | 145 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, |
| 138 | initialSnapshot: (settings) => | 146 | initialSnapshot: (settings) => |
| 139 | buildInitialCursorProviderSnapshot(settings).pipe(Effect.map(stampIdentity)), | 147 | buildInitialCursorProviderSnapshot(settings.provider).pipe(Effect.map(stampIdentity)), |
| 140 | checkProvider, | 148 | checkProvider, |
| 141 | // Model catalog and capabilities come exclusively from Cursor's | 149 | // Model catalog and capabilities come exclusively from Cursor's |
| 142 | // list_available_models extension method during provider checks. | 150 | // list_available_models extension method during provider checks. |
| 143 | enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) => | 151 | enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) => |
| 144 | enrichCursorSnapshot({ | 152 | enrichCursorSnapshot({ |
| 145 | settings, | 153 | settings: settings.provider, |
| 146 | snapshot: currentSnapshot, | 154 | snapshot: currentSnapshot, |
| 147 | maintenanceCapabilities, | 155 | maintenanceCapabilities, |
| 156 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, | ||
| 148 | publishSnapshot, | 157 | publishSnapshot, |
| 149 | stampIdentity, | 158 | stampIdentity, |
| 150 | httpClient, | 159 | httpClient, |
apps/server/src/provider/Drivers/GrokDriver.ts+17-8| ... | @@ -5,11 +5,11 @@ import * as Effect from "effect/Effect"; | ... | @@ -5,11 +5,11 @@ import * as Effect from "effect/Effect"; |
| 5 | import * as FileSystem from "effect/FileSystem"; | 5 | import * as FileSystem from "effect/FileSystem"; |
| 6 | import * as Path from "effect/Path"; | 6 | import * as Path from "effect/Path"; |
| 7 | import * as Schema from "effect/Schema"; | 7 | import * as Schema from "effect/Schema"; |
| 8 | import * as Stream from "effect/Stream"; | ||
| 9 | import { HttpClient } from "effect/unstable/http"; | 8 | import { HttpClient } from "effect/unstable/http"; |
| 10 | import { ChildProcessSpawner } from "effect/unstable/process"; | 9 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 11 | 10 | ||
| 12 | import { ServerConfig } from "../../config.ts"; | 11 | import { ServerConfig } from "../../config.ts"; |
| 12 | import { ServerSettingsService } from "../../serverSettings.ts"; | ||
| 13 | import { makeGrokTextGeneration } from "../../textGeneration/GrokTextGeneration.ts"; | 13 | import { makeGrokTextGeneration } from "../../textGeneration/GrokTextGeneration.ts"; |
| 14 | import { ProviderDriverError } from "../Errors.ts"; | 14 | import { ProviderDriverError } from "../Errors.ts"; |
| 15 | import { makeGrokAdapter } from "../Layers/GrokAdapter.ts"; | 15 | import { makeGrokAdapter } from "../Layers/GrokAdapter.ts"; |
| ... | @@ -32,6 +32,11 @@ import { | ... | @@ -32,6 +32,11 @@ import { |
| 32 | makeStaticProviderMaintenanceResolver, | 32 | makeStaticProviderMaintenanceResolver, |
| 33 | resolveProviderMaintenanceCapabilitiesEffect, | 33 | resolveProviderMaintenanceCapabilitiesEffect, |
| 34 | } from "../providerMaintenance.ts"; | 34 | } from "../providerMaintenance.ts"; |
| 35 | import { | ||
| 36 | haveProviderSnapshotSettingsChanged, | ||
| 37 | makeProviderSnapshotSettingsSource, | ||
| 38 | type ProviderSnapshotSettings, | ||
| 39 | } from "../providerUpdateSettings.ts"; | ||
| 35 | const decodeGrokSettings = Schema.decodeSync(GrokSettings); | 40 | const decodeGrokSettings = Schema.decodeSync(GrokSettings); |
| 36 | 41 | ||
| 37 | const DRIVER_KIND = ProviderDriverKind.make("grok"); | 42 | const DRIVER_KIND = ProviderDriverKind.make("grok"); |
| ... | @@ -50,7 +55,8 @@ export type GrokDriverEnv = | ... | @@ -50,7 +55,8 @@ export type GrokDriverEnv = |
| 50 | | HttpClient.HttpClient | 55 | | HttpClient.HttpClient |
| 51 | | Path.Path | 56 | | Path.Path |
| 52 | | ProviderEventLoggers | 57 | | ProviderEventLoggers |
| 53 | | ServerConfig; | 58 | | ServerConfig |
| 59 | | ServerSettingsService; | ||
| 54 | 60 | ||
| 55 | const withInstanceIdentity = | 61 | const withInstanceIdentity = |
| 56 | (input: { | 62 | (input: { |
| ... | @@ -80,6 +86,7 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = { | ... | @@ -80,6 +86,7 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = { |
| 80 | Effect.gen(function* () { | 86 | Effect.gen(function* () { |
| 81 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | 87 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 82 | const httpClient = yield* HttpClient.HttpClient; | 88 | const httpClient = yield* HttpClient.HttpClient; |
| 89 | const serverSettings = yield* ServerSettingsService; | ||
| 83 | const eventLoggers = yield* ProviderEventLoggers; | 90 | const eventLoggers = yield* ProviderEventLoggers; |
| 84 | const processEnv = mergeProviderInstanceEnvironment(environment); | 91 | const processEnv = mergeProviderInstanceEnvironment(environment); |
| 85 | const continuationIdentity = defaultProviderContinuationIdentity({ | 92 | const continuationIdentity = defaultProviderContinuationIdentity({ |
| ... | @@ -110,18 +117,20 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = { | ... | @@ -110,18 +117,20 @@ export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = { |
| 110 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), | 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 | maintenanceCapabilities, | 122 | maintenanceCapabilities, |
| 115 | getSettings: Effect.succeed(effectiveConfig), | 123 | getSettings: snapshotSettings.getSettings, |
| 116 | streamSettings: Stream.never, | 124 | streamSettings: snapshotSettings.streamSettings, |
| 117 | haveSettingsChanged: () => false, | 125 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, |
| 118 | initialSnapshot: (settings) => | 126 | initialSnapshot: (settings) => |
| 119 | buildInitialGrokProviderSnapshot(settings).pipe(Effect.map(stampIdentity)), | 127 | buildInitialGrokProviderSnapshot(settings.provider).pipe(Effect.map(stampIdentity)), |
| 120 | checkProvider, | 128 | checkProvider, |
| 121 | enrichSnapshot: ({ snapshot: currentSnapshot, publishSnapshot }) => | 129 | enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) => |
| 122 | enrichGrokSnapshot({ | 130 | enrichGrokSnapshot({ |
| 123 | snapshot: currentSnapshot, | 131 | snapshot: currentSnapshot, |
| 124 | maintenanceCapabilities, | 132 | maintenanceCapabilities, |
| 133 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, | ||
| 125 | publishSnapshot, | 134 | publishSnapshot, |
| 126 | httpClient, | 135 | httpClient, |
| 127 | }), | 136 | }), |
apps/server/src/provider/Drivers/OpenCodeDriver.ts+29-17| ... | @@ -19,12 +19,12 @@ import * as Effect from "effect/Effect"; | ... | @@ -19,12 +19,12 @@ import * as Effect from "effect/Effect"; |
| 19 | import * as FileSystem from "effect/FileSystem"; | 19 | import * as FileSystem from "effect/FileSystem"; |
| 20 | import * as Path from "effect/Path"; | 20 | import * as Path from "effect/Path"; |
| 21 | import * as Schema from "effect/Schema"; | 21 | import * as Schema from "effect/Schema"; |
| 22 | import * as Stream from "effect/Stream"; | ||
| 23 | import { HttpClient } from "effect/unstable/http"; | 22 | import { HttpClient } from "effect/unstable/http"; |
| 24 | import { ChildProcessSpawner } from "effect/unstable/process"; | 23 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 25 | 24 | ||
| 26 | import { makeOpenCodeTextGeneration } from "../../textGeneration/OpenCodeTextGeneration.ts"; | 25 | import { makeOpenCodeTextGeneration } from "../../textGeneration/OpenCodeTextGeneration.ts"; |
| 27 | import { ServerConfig } from "../../config.ts"; | 26 | import { ServerConfig } from "../../config.ts"; |
| 27 | import { ServerSettingsService } from "../../serverSettings.ts"; | ||
| 28 | import { ProviderDriverError } from "../Errors.ts"; | 28 | import { ProviderDriverError } from "../Errors.ts"; |
| 29 | import { makeOpenCodeAdapter } from "../Layers/OpenCodeAdapter.ts"; | 29 | import { makeOpenCodeAdapter } from "../Layers/OpenCodeAdapter.ts"; |
| 30 | import { | 30 | import { |
| ... | @@ -47,6 +47,11 @@ import { | ... | @@ -47,6 +47,11 @@ import { |
| 47 | normalizeCommandPath, | 47 | normalizeCommandPath, |
| 48 | resolveProviderMaintenanceCapabilitiesEffect, | 48 | resolveProviderMaintenanceCapabilitiesEffect, |
| 49 | } from "../providerMaintenance.ts"; | 49 | } from "../providerMaintenance.ts"; |
| 50 | import { | ||
| 51 | haveProviderSnapshotSettingsChanged, | ||
| 52 | makeProviderSnapshotSettingsSource, | ||
| 53 | type ProviderSnapshotSettings, | ||
| 54 | } from "../providerUpdateSettings.ts"; | ||
| 50 | const decodeOpenCodeSettings = Schema.decodeSync(OpenCodeSettings); | 55 | const decodeOpenCodeSettings = Schema.decodeSync(OpenCodeSettings); |
| 51 | 56 | ||
| 52 | const DRIVER_KIND = ProviderDriverKind.make("opencode"); | 57 | const DRIVER_KIND = ProviderDriverKind.make("opencode"); |
| ... | @@ -80,7 +85,8 @@ export type OpenCodeDriverEnv = | ... | @@ -80,7 +85,8 @@ export type OpenCodeDriverEnv = |
| 80 | | OpenCodeRuntime | 85 | | OpenCodeRuntime |
| 81 | | Path.Path | 86 | | Path.Path |
| 82 | | ProviderEventLoggers | 87 | | ProviderEventLoggers |
| 83 | | ServerConfig; | 88 | | ServerConfig |
| 89 | | ServerSettingsService; | ||
| 84 | 90 | ||
| 85 | const withInstanceIdentity = | 91 | const withInstanceIdentity = |
| 86 | (input: { | 92 | (input: { |
| ... | @@ -111,6 +117,7 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv> | ... | @@ -111,6 +117,7 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv> |
| 111 | const openCodeRuntime = yield* OpenCodeRuntime; | 117 | const openCodeRuntime = yield* OpenCodeRuntime; |
| 112 | const serverConfig = yield* ServerConfig; | 118 | const serverConfig = yield* ServerConfig; |
| 113 | const httpClient = yield* HttpClient.HttpClient; | 119 | const httpClient = yield* HttpClient.HttpClient; |
| 120 | const serverSettings = yield* ServerSettingsService; | ||
| 114 | const eventLoggers = yield* ProviderEventLoggers; | 121 | const eventLoggers = yield* ProviderEventLoggers; |
| 115 | const processEnv = mergeProviderInstanceEnvironment(environment); | 122 | const processEnv = mergeProviderInstanceEnvironment(environment); |
| 116 | const continuationIdentity = defaultProviderContinuationIdentity({ | 123 | const continuationIdentity = defaultProviderContinuationIdentity({ |
| ... | @@ -142,21 +149,26 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv> | ... | @@ -142,21 +149,26 @@ export const OpenCodeDriver: ProviderDriver<OpenCodeSettings, OpenCodeDriverEnv> |
| 142 | processEnv, | 149 | processEnv, |
| 143 | ).pipe(Effect.map(stampIdentity), Effect.provideService(OpenCodeRuntime, openCodeRuntime)); | 150 | ).pipe(Effect.map(stampIdentity), Effect.provideService(OpenCodeRuntime, openCodeRuntime)); |
| 144 | 151 | ||
| 145 | const snapshot = yield* makeManagedServerProvider<OpenCodeSettings>({ | 152 | const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings); |
| 146 | maintenanceCapabilities, | 153 | const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<OpenCodeSettings>>( |
| 147 | getSettings: Effect.succeed(effectiveConfig), | 154 | { |
| 148 | streamSettings: Stream.never, | 155 | maintenanceCapabilities, |
| 149 | haveSettingsChanged: () => false, | 156 | getSettings: snapshotSettings.getSettings, |
| 150 | initialSnapshot: (settings) => | 157 | streamSettings: snapshotSettings.streamSettings, |
| 151 | makePendingOpenCodeProvider(settings).pipe(Effect.map(stampIdentity)), | 158 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, |
| 152 | checkProvider, | 159 | initialSnapshot: (settings) => |
| 153 | enrichSnapshot: ({ snapshot, publishSnapshot }) => | 160 | makePendingOpenCodeProvider(settings.provider).pipe(Effect.map(stampIdentity)), |
| 154 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities).pipe( | 161 | checkProvider, |
| 155 | Effect.provideService(HttpClient.HttpClient, httpClient), | 162 | enrichSnapshot: ({ settings, snapshot, publishSnapshot }) => |
| 156 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), | 163 | enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities, { |
| 157 | ), | 164 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, |
| 158 | refreshInterval: SNAPSHOT_REFRESH_INTERVAL, | 165 | }).pipe( |
| 159 | }).pipe( | 166 | Effect.provideService(HttpClient.HttpClient, httpClient), |
| 167 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), | ||
| 168 | ), | ||
| 169 | refreshInterval: SNAPSHOT_REFRESH_INTERVAL, | ||
| 170 | }, | ||
| 171 | ).pipe( | ||
| 160 | Effect.mapError( | 172 | Effect.mapError( |
| 161 | (cause) => | 173 | (cause) => |
| 162 | new ProviderDriverError({ | 174 | new ProviderDriverError({ |
apps/server/src/provider/Layers/CursorProvider.ts+4-1| ... | @@ -1106,6 +1106,7 @@ export const enrichCursorSnapshot = (input: { | ... | @@ -1106,6 +1106,7 @@ export const enrichCursorSnapshot = (input: { |
| 1106 | readonly settings: CursorSettings; | 1106 | readonly settings: CursorSettings; |
| 1107 | readonly snapshot: ServerProvider; | 1107 | readonly snapshot: ServerProvider; |
| 1108 | readonly maintenanceCapabilities: ProviderMaintenanceCapabilities; | 1108 | readonly maintenanceCapabilities: ProviderMaintenanceCapabilities; |
| 1109 | readonly enableProviderUpdateChecks?: boolean; | ||
| 1109 | readonly publishSnapshot: (snapshot: ServerProvider) => Effect.Effect<void>; | 1110 | readonly publishSnapshot: (snapshot: ServerProvider) => Effect.Effect<void>; |
| 1110 | readonly stampIdentity?: (snapshot: ServerProvider) => ServerProvider; | 1111 | readonly stampIdentity?: (snapshot: ServerProvider) => ServerProvider; |
| 1111 | readonly httpClient: HttpClient.HttpClient; | 1112 | readonly httpClient: HttpClient.HttpClient; |
| ... | @@ -1117,7 +1118,9 @@ export const enrichCursorSnapshot = (input: { | ... | @@ -1117,7 +1118,9 @@ export const enrichCursorSnapshot = (input: { |
| 1117 | return Effect.void; | 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 | Effect.provideService(HttpClient.HttpClient, input.httpClient), | 1124 | Effect.provideService(HttpClient.HttpClient, input.httpClient), |
| 1122 | Effect.flatMap((enrichedSnapshot) => | 1125 | Effect.flatMap((enrichedSnapshot) => |
| 1123 | publishSnapshot(stampIdentity(enrichedSnapshot)).pipe(Effect.as(enrichedSnapshot)), | 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,12 +311,15 @@ export const checkGrokProviderStatus = Effect.fn("checkGrokProviderStatus")(func |
| 311 | export const enrichGrokSnapshot = (input: { | 311 | export const enrichGrokSnapshot = (input: { |
| 312 | readonly snapshot: ServerProvider; | 312 | readonly snapshot: ServerProvider; |
| 313 | readonly maintenanceCapabilities: ProviderMaintenanceCapabilities; | 313 | readonly maintenanceCapabilities: ProviderMaintenanceCapabilities; |
| 314 | readonly enableProviderUpdateChecks?: boolean; | ||
| 314 | readonly publishSnapshot: (snapshot: ServerProvider) => Effect.Effect<void>; | 315 | readonly publishSnapshot: (snapshot: ServerProvider) => Effect.Effect<void>; |
| 315 | readonly httpClient: HttpClient.HttpClient; | 316 | readonly httpClient: HttpClient.HttpClient; |
| 316 | }): Effect.Effect<void> => { | 317 | }): Effect.Effect<void> => { |
| 317 | const { snapshot, publishSnapshot } = input; | 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 | Effect.provideService(HttpClient.HttpClient, input.httpClient), | 323 | Effect.provideService(HttpClient.HttpClient, input.httpClient), |
| 321 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), | 324 | Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), |
| 322 | Effect.catchCause((cause) => | 325 | Effect.catchCause((cause) => |
apps/server/src/provider/Layers/ProviderInstanceRegistryLive.test.ts+3| ... | @@ -39,6 +39,7 @@ import * as Layer from "effect/Layer"; | ... | @@ -39,6 +39,7 @@ import * as Layer from "effect/Layer"; |
| 39 | import { HttpClient, HttpClientResponse } from "effect/unstable/http"; | 39 | import { HttpClient, HttpClientResponse } from "effect/unstable/http"; |
| 40 | 40 | ||
| 41 | import { ServerConfig } from "../../config.ts"; | 41 | import { ServerConfig } from "../../config.ts"; |
| 42 | import { ServerSettingsService } from "../../serverSettings.ts"; | ||
| 42 | import { ClaudeDriver } from "../Drivers/ClaudeDriver.ts"; | 43 | import { ClaudeDriver } from "../Drivers/ClaudeDriver.ts"; |
| 43 | import { CodexDriver } from "../Drivers/CodexDriver.ts"; | 44 | import { CodexDriver } from "../Drivers/CodexDriver.ts"; |
| 44 | import { CursorDriver } from "../Drivers/CursorDriver.ts"; | 45 | import { CursorDriver } from "../Drivers/CursorDriver.ts"; |
| ... | @@ -107,6 +108,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { | ... | @@ -107,6 +108,7 @@ describe("ProviderInstanceRegistryLive — multi-instance codex slice", () => { |
| 107 | prefix: "provider-instance-registry-test", | 108 | prefix: "provider-instance-registry-test", |
| 108 | }).pipe( | 109 | }).pipe( |
| 109 | Layer.provideMerge(NodeServices.layer), | 110 | Layer.provideMerge(NodeServices.layer), |
| 111 | Layer.provideMerge(ServerSettingsService.layerTest()), | ||
| 110 | Layer.provideMerge(TestHttpClientLive), | 112 | Layer.provideMerge(TestHttpClientLive), |
| 111 | Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)), | 113 | Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)), |
| 112 | ); | 114 | ); |
| ... | @@ -244,6 +246,7 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => { | ... | @@ -244,6 +246,7 @@ describe("ProviderInstanceRegistryLive — all drivers slice", () => { |
| 244 | prefix: "provider-instance-registry-all-drivers-test", | 246 | prefix: "provider-instance-registry-all-drivers-test", |
| 245 | }).pipe( | 247 | }).pipe( |
| 246 | Layer.provideMerge(infraLayer), | 248 | Layer.provideMerge(infraLayer), |
| 249 | Layer.provideMerge(ServerSettingsService.layerTest()), | ||
| 247 | Layer.provideMerge(TestHttpClientLive), | 250 | Layer.provideMerge(TestHttpClientLive), |
| 248 | Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)), | 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,7 +21,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")( |
| 21 | Settings, | 21 | Settings, |
| 22 | >(input: { | 22 | >(input: { |
| 23 | readonly maintenanceCapabilities: ServerProviderShape["maintenanceCapabilities"]; | 23 | readonly maintenanceCapabilities: ServerProviderShape["maintenanceCapabilities"]; |
| 24 | readonly getSettings: Effect.Effect<Settings>; | 24 | readonly getSettings: Effect.Effect<Settings, ServerSettingsError>; |
| 25 | readonly streamSettings: Stream.Stream<Settings>; | 25 | readonly streamSettings: Stream.Stream<Settings>; |
| 26 | readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean; | 26 | readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean; |
| 27 | readonly initialSnapshot: (settings: Settings) => Effect.Effect<ServerProvider>; | 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,13 +4,14 @@ import { chmodSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs"; |
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import * as NodeOS from "node:os"; | 5 | import * as NodeOS from "node:os"; |
| 6 | import path from "node:path"; | 6 | import path from "node:path"; |
| 7 | import { ProviderDriverKind } from "@t3tools/contracts"; | 7 | import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts"; |
| 8 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; | 8 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| 9 | import * as Crypto from "effect/Crypto"; | 9 | import * as Crypto from "effect/Crypto"; |
| 10 | import * as Effect from "effect/Effect"; | 10 | import * as Effect from "effect/Effect"; |
| 11 | import { HttpClient } from "effect/unstable/http"; | 11 | import { HttpClient } from "effect/unstable/http"; |
| 12 | import { | 12 | import { |
| 13 | createProviderVersionAdvisory, | 13 | createProviderVersionAdvisory, |
| 14 | enrichProviderSnapshotWithVersionAdvisory, | ||
| 14 | makePackageManagedProviderMaintenanceResolver, | 15 | makePackageManagedProviderMaintenanceResolver, |
| 15 | makeProviderMaintenanceCapabilities, | 16 | makeProviderMaintenanceCapabilities, |
| 16 | makeStaticProviderMaintenanceResolver, | 17 | makeStaticProviderMaintenanceResolver, |
| ... | @@ -67,6 +68,19 @@ const staticToolUpdate = makeStaticProviderMaintenanceResolver( | ... | @@ -67,6 +68,19 @@ const staticToolUpdate = makeStaticProviderMaintenanceResolver( |
| 67 | updateLockKey: "static-tool", | 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 | it.layer(NodeServices.layer)("providerMaintenance", (it) => { | 85 | it.layer(NodeServices.layer)("providerMaintenance", (it) => { |
| 72 | it.effect("reads cached versions through the injectable cache reference", () => | 86 | it.effect("reads cached versions through the injectable cache reference", () => |
| ... | @@ -95,6 +109,32 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => { | ... | @@ -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 | it("marks providers with unknown current versions as unknown", () => { | 138 | it("marks providers with unknown current versions as unknown", () => { |
| 99 | expect( | 139 | expect( |
| 100 | createProviderVersionAdvisory({ | 140 | createProviderVersionAdvisory({ |
apps/server/src/provider/providerMaintenance.ts+13-2| ... | @@ -468,10 +468,21 @@ export const resolveLatestProviderVersion = Effect.fn("resolveLatestProviderVers | ... | @@ -468,10 +468,21 @@ export const resolveLatestProviderVersion = Effect.fn("resolveLatestProviderVers |
| 468 | 468 | ||
| 469 | export const enrichProviderSnapshotWithVersionAdvisory = Effect.fn( | 469 | export const enrichProviderSnapshotWithVersionAdvisory = Effect.fn( |
| 470 | "enrichProviderSnapshotWithVersionAdvisory", | 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 | const capabilities = | 478 | const capabilities = |
| 473 | maintenanceCapabilities ?? makeManualProviderMaintenanceCapabilities(snapshot.driver); | 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 | return { | 486 | return { |
| 476 | ...snapshot, | 487 | ...snapshot, |
| 477 | versionAdvisory: createProviderVersionAdvisory({ | 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,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 | <SettingsRow | 698 | <SettingsRow |
| 672 | title="Auto-open task panel" | 699 | title="Auto-open task panel" |
| 673 | description="Open the right-side plan and task panel automatically when steps appear." | 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,6 +365,7 @@ export const DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL = Duration.seconds(30); |
| 365 | 365 | ||
| 366 | export const ServerSettings = Schema.Struct({ | 366 | export const ServerSettings = Schema.Struct({ |
| 367 | enableAssistantStreaming: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | 367 | enableAssistantStreaming: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), |
| 368 | enableProviderUpdateChecks: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), | ||
| 368 | automaticGitFetchInterval: Schema.DurationFromMillis.pipe( | 369 | automaticGitFetchInterval: Schema.DurationFromMillis.pipe( |
| 369 | Schema.withDecodingDefault( | 370 | Schema.withDecodingDefault( |
| 370 | Effect.succeed(Duration.toMillis(DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL)), | 371 | Effect.succeed(Duration.toMillis(DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL)), |
| ... | @@ -482,6 +483,7 @@ const OpenCodeSettingsPatch = Schema.Struct({ | ... | @@ -482,6 +483,7 @@ const OpenCodeSettingsPatch = Schema.Struct({ |
| 482 | export const ServerSettingsPatch = Schema.Struct({ | 483 | export const ServerSettingsPatch = Schema.Struct({ |
| 483 | // Server settings | 484 | // Server settings |
| 484 | enableAssistantStreaming: Schema.optionalKey(Schema.Boolean), | 485 | enableAssistantStreaming: Schema.optionalKey(Schema.Boolean), |
| 486 | enableProviderUpdateChecks: Schema.optionalKey(Schema.Boolean), | ||
| 485 | automaticGitFetchInterval: Schema.optionalKey(Schema.DurationFromMillis), | 487 | automaticGitFetchInterval: Schema.optionalKey(Schema.DurationFromMillis), |
| 486 | defaultThreadEnvMode: Schema.optionalKey(ThreadEnvMode), | 488 | defaultThreadEnvMode: Schema.optionalKey(ThreadEnvMode), |
| 487 | newWorktreesStartFromOrigin: Schema.optionalKey(Schema.Boolean), | 489 | newWorktreesStartFromOrigin: Schema.optionalKey(Schema.Boolean), |