import { isProviderDriverKind, isProviderAvailable, resolveProviderInstanceEnabled, type ModelSelection, type ProjectId, type ProviderDriverKind, type ServerProvider, ServerSettings, type ServerSettingsPatch,} from "@t3tools/contracts";import * as Option from "effect/Option";import * as Schema from "effect/Schema";import { deepMerge } from "./Struct.ts";import { fromLenientJson } from "./schemaJson.ts";import { createModelSelection } from "./model.ts";import { getBackgroundActivityBaseProfile, normalizeBackgroundActivitySettings, normalizeServerBackgroundActivitySettings, resolveBackgroundActivitySettings,} from "./backgroundActivitySettings.ts"; const ServerSettingsJson = fromLenientJson(ServerSettings);const decodeServerSettingsJson = Schema.decodeUnknownOption(ServerSettingsJson); export function resolveProjectAgentBrowserAccess( settings: Pick<ServerSettings, "enableAgentBrowserAccess" | "projectAgentBrowserAccessOverrides">, projectId: ProjectId,): boolean { return ( settings.projectAgentBrowserAccessOverrides[projectId] ?? settings.enableAgentBrowserAccess );} export function resolveProjectAutoPull( settings: Pick<ServerSettings, "defaultAutoPull" | "projectAutoPullOverrides">, projectId: ProjectId, legacyAutoPull: boolean | undefined,): boolean { // Existing opt-ins stay enabled until explicitly overridden or reset. return ( settings.projectAutoPullOverrides[projectId] ?? (legacyAutoPull === true || settings.defaultAutoPull) );} type LegacyProviderSettings = ServerSettings["providers"][keyof ServerSettings["providers"]]; const getLegacyProviderSettings = ( settings: ServerSettings, provider: ProviderDriverKind,): LegacyProviderSettings | undefined => (settings.providers as Record<string, LegacyProviderSettings | undefined>)[provider]; export function isModelSelectionProviderEnabled( settings: ServerSettings, selection: ModelSelection,): boolean { const instanceConfig = settings.providerInstances[selection.instanceId]; if (instanceConfig !== undefined) { return resolveProviderInstanceEnabled(instanceConfig); } return ( isProviderDriverKind(selection.instanceId) && getLegacyProviderSettings(settings, selection.instanceId)?.enabled === true );} export function resolveSourceControlWriterModelSelection( settings: ServerSettings, providers?: ReadonlyArray<ServerProvider>,): ModelSelection { const selection = settings.sourceControlWriterModelSelection; if (!selection || !isModelSelectionProviderEnabled(settings, selection)) { return settings.textGenerationModelSelection; } if (providers === undefined) { return selection; } const provider = providers.find((candidate) => candidate.instanceId === selection.instanceId); return provider?.enabled === true && isProviderAvailable(provider) ? selection : settings.textGenerationModelSelection;} export interface PersistedServerObservabilitySettings { readonly otlpTracesUrl: string | undefined; readonly otlpMetricsUrl: string | undefined;} function normalizePersistedServerSettingString( value: string | null | undefined,): string | undefined { const trimmed = value?.trim(); return trimmed && trimmed.length > 0 ? trimmed : undefined;} function extractPersistedServerObservabilitySettings(input: { readonly observability?: { readonly otlpTracesUrl?: string; readonly otlpMetricsUrl?: string; };}): PersistedServerObservabilitySettings { return { otlpTracesUrl: normalizePersistedServerSettingString(input.observability?.otlpTracesUrl), otlpMetricsUrl: normalizePersistedServerSettingString(input.observability?.otlpMetricsUrl), };} export function parsePersistedServerObservabilitySettings( raw: string,): PersistedServerObservabilitySettings { const decoded = decodeServerSettingsJson(raw); if (Option.isSome(decoded)) { return extractPersistedServerObservabilitySettings(decoded.value); } return { otlpTracesUrl: undefined, otlpMetricsUrl: undefined };} function shouldReplaceTextGenerationModelSelection( patch: ServerSettingsPatch["textGenerationModelSelection"] | undefined,): boolean { return Boolean(patch && (patch.instanceId !== undefined || patch.model !== undefined));} function mergeModelSelectionOptionsById(input: { current: ReadonlyArray<{ readonly id: string; readonly value: string | boolean }> | undefined; patch: ReadonlyArray<{ readonly id: string; readonly value: string | boolean }> | undefined;}): Array<{ id: string; value: string | boolean }> | undefined { if (input.patch === undefined) { return input.current ? [...input.current] : undefined; } if (input.patch.length === 0) { return undefined; } const merged = new Map((input.current ?? []).map((selection) => [selection.id, selection.value])); for (const selection of input.patch) { merged.set(selection.id, selection.value); } return [...merged.entries()].map(([id, value]) => ({ id, value }));} /** Upsert each patched entry; `null` removes it. Entries the patch omits are untouched. */function mergeSettingsEntries<Value>( current: Readonly<Record<string, Value>>, patch: Readonly<Record<string, Value | null>>,): Record<string, Value> { const next = new Map(Object.entries(current)); for (const [id, config] of Object.entries(patch)) { if (config === null) { next.delete(id); } else { next.set(id, config); } } return Object.fromEntries(next);} export function applyServerSettingsPatch( current: ServerSettings, patch: ServerSettingsPatch,): ServerSettings { const selectionPatch = patch.textGenerationModelSelection; const { automaticGitFetchInterval, providerHealthRefreshInterval, backgroundActivityProfile, backgroundActivity, // Merged per entry below; its `null` removals must not reach deepMerge. usageLimitSources: usageLimitSourcesPatch, usagePriceOverrides: usagePriceOverridesPatch, projectAgentBrowserAccessOverrides: projectAgentBrowserAccessOverridesPatch, projectAutoPullOverrides: projectAutoPullOverridesPatch, ...patchForMerge } = patch; const currentBackgroundActivity = normalizeServerBackgroundActivitySettings(current); const backgroundActivityPatch = backgroundActivityProfile !== undefined ? { schemaVersion: 1 as const, profile: automaticGitFetchInterval !== undefined || providerHealthRefreshInterval !== undefined ? ("custom" as const) : backgroundActivityProfile, ...(automaticGitFetchInterval !== undefined || providerHealthRefreshInterval !== undefined ? { baseProfile: backgroundActivityProfile } : {}), overrides: { ...(automaticGitFetchInterval !== undefined ? { automaticGitFetchInterval } : {}), ...(providerHealthRefreshInterval !== undefined ? { providerHealthRefreshInterval } : {}), }, } : automaticGitFetchInterval !== undefined || providerHealthRefreshInterval !== undefined ? { schemaVersion: 1 as const, profile: "custom" as const, baseProfile: getBackgroundActivityBaseProfile(currentBackgroundActivity), overrides: { ...(currentBackgroundActivity.profile === "custom" ? currentBackgroundActivity.overrides : {}), ...(automaticGitFetchInterval !== undefined ? { automaticGitFetchInterval } : {}), ...(providerHealthRefreshInterval !== undefined ? { providerHealthRefreshInterval } : {}), }, } : undefined; const next = deepMerge(current, patchForMerge); const nextWithReplacementsBase = { ...next, ...(backgroundActivity !== undefined ? { backgroundActivity: { ...deepMerge(currentBackgroundActivity, backgroundActivity), ...(backgroundActivity.overrides !== undefined ? { overrides: backgroundActivity.overrides } : {}), }, } : { backgroundActivity: currentBackgroundActivity }), ...(backgroundActivity === undefined && backgroundActivityPatch !== undefined ? { backgroundActivity: backgroundActivityPatch } : {}), ...(patch.providerInstances !== undefined ? { providerInstances: patch.providerInstances } : {}), ...(projectAgentBrowserAccessOverridesPatch !== undefined ? { projectAgentBrowserAccessOverrides: mergeSettingsEntries( current.projectAgentBrowserAccessOverrides, projectAgentBrowserAccessOverridesPatch, ), } : {}), ...(projectAutoPullOverridesPatch !== undefined ? { projectAutoPullOverrides: mergeSettingsEntries( current.projectAutoPullOverrides, projectAutoPullOverridesPatch, ), } : {}), ...(patch.defaultModelSelection !== undefined ? { defaultModelSelection: patch.defaultModelSelection } : {}), ...(patch.defaultProjectScripts !== undefined ? { defaultProjectScripts: patch.defaultProjectScripts } : {}), ...(patch.projectScriptOverrides !== undefined ? { projectScriptOverrides: { ...current.projectScriptOverrides, ...patch.projectScriptOverrides, }, } : {}), ...(usageLimitSourcesPatch !== undefined ? { usageLimitSources: mergeSettingsEntries( current.usageLimitSources, usageLimitSourcesPatch, ), } : {}), ...(usagePriceOverridesPatch !== undefined ? { usagePriceOverrides: mergeSettingsEntries( current.usagePriceOverrides, usagePriceOverridesPatch, ), } : {}), ...(patch.sourceControlWriterModelSelection !== undefined ? { sourceControlWriterModelSelection: patch.sourceControlWriterModelSelection } : {}), ...(automaticGitFetchInterval !== undefined ? { automaticGitFetchInterval } : {}), ...(providerHealthRefreshInterval !== undefined ? { providerHealthRefreshInterval } : {}), }; const normalizedBackgroundActivity = normalizeBackgroundActivitySettings( nextWithReplacementsBase.backgroundActivity, ); const resolvedBackgroundActivity = resolveBackgroundActivitySettings( normalizedBackgroundActivity, ); const nextWithReplacements = { ...nextWithReplacementsBase, backgroundActivity: normalizedBackgroundActivity, automaticGitFetchInterval: resolvedBackgroundActivity.automaticGitFetchInterval, providerHealthRefreshInterval: resolvedBackgroundActivity.providerHealthRefreshInterval, backgroundActivityProfile: resolvedBackgroundActivity.profile, }; if (!selectionPatch) { return nextWithReplacements; } const instanceId = selectionPatch.instanceId ?? current.textGenerationModelSelection.instanceId; const model = selectionPatch.model ?? current.textGenerationModelSelection.model; const options = shouldReplaceTextGenerationModelSelection(selectionPatch) ? selectionPatch.options : mergeModelSelectionOptionsById({ current: current.textGenerationModelSelection.options, patch: selectionPatch.options, }); return { ...nextWithReplacements, textGenerationModelSelection: createModelSelection(instanceId, model, options), };}
import {
isProviderDriverKind,
isProviderAvailable,
resolveProviderInstanceEnabled,
type ModelSelection,
type ProjectId,
type ProviderDriverKind,
type ServerProvider,
ServerSettings,
type ServerSettingsPatch,
} from "@t3tools/contracts";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import { deepMerge } from "./Struct.ts";
import { fromLenientJson } from "./schemaJson.ts";
import { createModelSelection } from "./model.ts";
import {
getBackgroundActivityBaseProfile,
normalizeBackgroundActivitySettings,
normalizeServerBackgroundActivitySettings,
resolveBackgroundActivitySettings,
} from "./backgroundActivitySettings.ts";
const ServerSettingsJson = fromLenientJson(ServerSettings);
const decodeServerSettingsJson = Schema.decodeUnknownOption(ServerSettingsJson);
export function resolveProjectAgentBrowserAccess(
settings: Pick<ServerSettings, "enableAgentBrowserAccess" | "projectAgentBrowserAccessOverrides">,
projectId: ProjectId,
): boolean {
return (
settings.projectAgentBrowserAccessOverrides[projectId] ?? settings.enableAgentBrowserAccess
);
}
export function resolveProjectAutoPull(
settings: Pick<ServerSettings, "defaultAutoPull" | "projectAutoPullOverrides">,
projectId: ProjectId,
legacyAutoPull: boolean | undefined,
): boolean {
// Existing opt-ins stay enabled until explicitly overridden or reset.
return (
settings.projectAutoPullOverrides[projectId] ??
(legacyAutoPull === true || settings.defaultAutoPull)
);
}
type LegacyProviderSettings = ServerSettings["providers"][keyof ServerSettings["providers"]];
const getLegacyProviderSettings = (
settings: ServerSettings,
provider: ProviderDriverKind,
): LegacyProviderSettings | undefined =>
(settings.providers as Record<string, LegacyProviderSettings | undefined>)[provider];
export function isModelSelectionProviderEnabled(
settings: ServerSettings,
selection: ModelSelection,
): boolean {
const instanceConfig = settings.providerInstances[selection.instanceId];
if (instanceConfig !== undefined) {
return resolveProviderInstanceEnabled(instanceConfig);
}
return (
isProviderDriverKind(selection.instanceId) &&
getLegacyProviderSettings(settings, selection.instanceId)?.enabled === true
);
}
export function resolveSourceControlWriterModelSelection(
settings: ServerSettings,
providers?: ReadonlyArray<ServerProvider>,
): ModelSelection {
const selection = settings.sourceControlWriterModelSelection;
if (!selection || !isModelSelectionProviderEnabled(settings, selection)) {
return settings.textGenerationModelSelection;
}
if (providers === undefined) {
return selection;
}
const provider = providers.find((candidate) => candidate.instanceId === selection.instanceId);
return provider?.enabled === true && isProviderAvailable(provider)
? selection
: settings.textGenerationModelSelection;
}
export interface PersistedServerObservabilitySettings {
readonly otlpTracesUrl: string | undefined;
readonly otlpMetricsUrl: string | undefined;
}
function normalizePersistedServerSettingString(
value: string | null | undefined,
): string | undefined {
const trimmed = value?.trim();
return trimmed && trimmed.length > 0 ? trimmed : undefined;
}
function extractPersistedServerObservabilitySettings(input: {
readonly observability?: {
readonly otlpTracesUrl?: string;
readonly otlpMetricsUrl?: string;
};
}): PersistedServerObservabilitySettings {
return {
otlpTracesUrl: normalizePersistedServerSettingString(input.observability?.otlpTracesUrl),
otlpMetricsUrl: normalizePersistedServerSettingString(input.observability?.otlpMetricsUrl),
};
}
export function parsePersistedServerObservabilitySettings(
raw: string,
): PersistedServerObservabilitySettings {
const decoded = decodeServerSettingsJson(raw);
if (Option.isSome(decoded)) {
return extractPersistedServerObservabilitySettings(decoded.value);
}
return { otlpTracesUrl: undefined, otlpMetricsUrl: undefined };
}
function shouldReplaceTextGenerationModelSelection(
patch: ServerSettingsPatch["textGenerationModelSelection"] | undefined,
): boolean {
return Boolean(patch && (patch.instanceId !== undefined || patch.model !== undefined));
}
function mergeModelSelectionOptionsById(input: {
current: ReadonlyArray<{ readonly id: string; readonly value: string | boolean }> | undefined;
patch: ReadonlyArray<{ readonly id: string; readonly value: string | boolean }> | undefined;
}): Array<{ id: string; value: string | boolean }> | undefined {
if (input.patch === undefined) {
return input.current ? [...input.current] : undefined;
}
if (input.patch.length === 0) {
return undefined;
}
const merged = new Map((input.current ?? []).map((selection) => [selection.id, selection.value]));
for (const selection of input.patch) {
merged.set(selection.id, selection.value);
}
return [...merged.entries()].map(([id, value]) => ({ id, value }));
}
/** Upsert each patched entry; `null` removes it. Entries the patch omits are untouched. */
function mergeSettingsEntries<Value>(
current: Readonly<Record<string, Value>>,
patch: Readonly<Record<string, Value | null>>,
): Record<string, Value> {
const next = new Map(Object.entries(current));
for (const [id, config] of Object.entries(patch)) {
if (config === null) {
next.delete(id);
} else {
next.set(id, config);
}
}
return Object.fromEntries(next);
}
export function applyServerSettingsPatch(
current: ServerSettings,
patch: ServerSettingsPatch,
): ServerSettings {
const selectionPatch = patch.textGenerationModelSelection;
const {
automaticGitFetchInterval,
providerHealthRefreshInterval,
backgroundActivityProfile,
backgroundActivity,
// Merged per entry below; its `null` removals must not reach deepMerge.
usageLimitSources: usageLimitSourcesPatch,
usagePriceOverrides: usagePriceOverridesPatch,
projectAgentBrowserAccessOverrides: projectAgentBrowserAccessOverridesPatch,
projectAutoPullOverrides: projectAutoPullOverridesPatch,
...patchForMerge
} = patch;
const currentBackgroundActivity = normalizeServerBackgroundActivitySettings(current);
const backgroundActivityPatch =
backgroundActivityProfile !== undefined
? {
schemaVersion: 1 as const,
profile:
automaticGitFetchInterval !== undefined || providerHealthRefreshInterval !== undefined
? ("custom" as const)
: backgroundActivityProfile,
...(automaticGitFetchInterval !== undefined || providerHealthRefreshInterval !== undefined
? { baseProfile: backgroundActivityProfile }
: {}),
overrides: {
...(automaticGitFetchInterval !== undefined ? { automaticGitFetchInterval } : {}),
...(providerHealthRefreshInterval !== undefined
? { providerHealthRefreshInterval }
: {}),
},
}
: automaticGitFetchInterval !== undefined || providerHealthRefreshInterval !== undefined
? {
schemaVersion: 1 as const,
profile: "custom" as const,
baseProfile: getBackgroundActivityBaseProfile(currentBackgroundActivity),
overrides: {
...(currentBackgroundActivity.profile === "custom"
? currentBackgroundActivity.overrides
: {}),
...(automaticGitFetchInterval !== undefined ? { automaticGitFetchInterval } : {}),
...(providerHealthRefreshInterval !== undefined
? { providerHealthRefreshInterval }
: {}),
},
}
: undefined;
const next = deepMerge(current, patchForMerge);
const nextWithReplacementsBase = {
...next,
...(backgroundActivity !== undefined
? {
backgroundActivity: {
...deepMerge(currentBackgroundActivity, backgroundActivity),
...(backgroundActivity.overrides !== undefined
? { overrides: backgroundActivity.overrides }
: {}),
},
}
: { backgroundActivity: currentBackgroundActivity }),
...(backgroundActivity === undefined && backgroundActivityPatch !== undefined
? { backgroundActivity: backgroundActivityPatch }
: {}),
...(patch.providerInstances !== undefined
? { providerInstances: patch.providerInstances }
: {}),
...(projectAgentBrowserAccessOverridesPatch !== undefined
? {
projectAgentBrowserAccessOverrides: mergeSettingsEntries(
current.projectAgentBrowserAccessOverrides,
projectAgentBrowserAccessOverridesPatch,
),
}
: {}),
...(projectAutoPullOverridesPatch !== undefined
? {
projectAutoPullOverrides: mergeSettingsEntries(
current.projectAutoPullOverrides,
projectAutoPullOverridesPatch,
),
}
: {}),
...(patch.defaultModelSelection !== undefined
? { defaultModelSelection: patch.defaultModelSelection }
: {}),
...(patch.defaultProjectScripts !== undefined
? { defaultProjectScripts: patch.defaultProjectScripts }
: {}),
...(patch.projectScriptOverrides !== undefined
? {
projectScriptOverrides: {
...current.projectScriptOverrides,
...patch.projectScriptOverrides,
},
}
: {}),
...(usageLimitSourcesPatch !== undefined
? {
usageLimitSources: mergeSettingsEntries(
current.usageLimitSources,
usageLimitSourcesPatch,
),
}
: {}),
...(usagePriceOverridesPatch !== undefined
? {
usagePriceOverrides: mergeSettingsEntries(
current.usagePriceOverrides,
usagePriceOverridesPatch,
),
}
: {}),
...(patch.sourceControlWriterModelSelection !== undefined
? { sourceControlWriterModelSelection: patch.sourceControlWriterModelSelection }
: {}),
...(automaticGitFetchInterval !== undefined ? { automaticGitFetchInterval } : {}),
...(providerHealthRefreshInterval !== undefined ? { providerHealthRefreshInterval } : {}),
};
const normalizedBackgroundActivity = normalizeBackgroundActivitySettings(
nextWithReplacementsBase.backgroundActivity,
);
const resolvedBackgroundActivity = resolveBackgroundActivitySettings(
normalizedBackgroundActivity,
);
const nextWithReplacements = {
...nextWithReplacementsBase,
backgroundActivity: normalizedBackgroundActivity,
automaticGitFetchInterval: resolvedBackgroundActivity.automaticGitFetchInterval,
providerHealthRefreshInterval: resolvedBackgroundActivity.providerHealthRefreshInterval,
backgroundActivityProfile: resolvedBackgroundActivity.profile,
};
if (!selectionPatch) {
return nextWithReplacements;
}
const instanceId = selectionPatch.instanceId ?? current.textGenerationModelSelection.instanceId;
const model = selectionPatch.model ?? current.textGenerationModelSelection.model;
const options = shouldReplaceTextGenerationModelSelection(selectionPatch)
? selectionPatch.options
: mergeModelSelectionOptionsById({
current: current.textGenerationModelSelection.options,
patch: selectionPatch.options,
});
return {
...nextWithReplacements,
textGenerationModelSelection: createModelSelection(instanceId, model, options),
};
}