diff --git a/src/runtime/config.ts b/src/runtime/config.ts index a76b4531..ac8fb13f 100644 --- a/src/runtime/config.ts +++ b/src/runtime/config.ts @@ -1,73 +1,86 @@ import type { ReasoningEffort } from "../generated/app-server/ReasoningEffort"; +import type { ReasoningSummary } from "../generated/app-server/ReasoningSummary"; +import type { Verbosity } from "../generated/app-server/Verbosity"; +import type { WebSearchMode } from "../generated/app-server/WebSearchMode"; import type { ConfigReadResponse } from "../generated/app-server/v2/ConfigReadResponse"; +import type { AppsConfig } from "../generated/app-server/v2/AppsConfig"; +import type { AskForApproval } from "../generated/app-server/v2/AskForApproval"; import type { ApprovalsReviewer } from "../generated/app-server/v2/ApprovalsReviewer"; +import type { Config } from "../generated/app-server/v2/Config"; +import type { ProfileV2 } from "../generated/app-server/v2/ProfileV2"; +import type { SandboxMode } from "../generated/app-server/v2/SandboxMode"; +import type { SandboxWorkspaceWrite } from "../generated/app-server/v2/SandboxWorkspaceWrite"; +import type { ToolsV2 } from "../generated/app-server/v2/ToolsV2"; import { parseServiceTier, type ServiceTier } from "../app-server/service-tier"; import { isReasoningEffort } from "./model"; export interface RuntimeConfigProjection { profile: string | null; model: string | null; - modelProvider: unknown; + modelProvider: string | null; reasoningEffort: ReasoningEffort | null; rawReasoningEffort: string | null; - reasoningSummary: unknown; - verbosity: unknown; + reasoningSummary: ReasoningSummary | null; + verbosity: Verbosity | null; serviceTier: ServiceTier | null; approvalsReviewer: ApprovalsReviewer | null; - approvalPolicy: unknown; - webSearch: unknown; + approvalPolicy: AskForApproval | null; + webSearch: WebSearchMode | null; modelContextWindow: number | null; autoCompactTokenLimit: number | null; - sandboxMode: unknown; - workspaceNetworkAccess: unknown; - writableRoots: unknown; + sandboxMode: SandboxMode | null; + workspaceNetworkAccess: SandboxWorkspaceWrite["network_access"] | null; + writableRoots: SandboxWorkspaceWrite["writable_roots"] | null; hooksEnabled: unknown; applyPatchFreeformEnabled: unknown; - toolWebSearch: unknown; - apps: Record; + toolWebSearch: ToolsV2["web_search"] | null; + apps: AppsConfig | null; } export function readRuntimeConfig(effectiveConfig: ConfigReadResponse | null): RuntimeConfigProjection { const config = configRecord(effectiveConfig); const features = asRecord(config["features"]); - const tools = asRecord(resolvedConfigValue(config, "tools")); - const workspaceWrite = asRecord(config["sandbox_workspace_write"]); + const tools = resolvedConfigValue(config, "tools") ?? null; + const workspaceWrite = config.sandbox_workspace_write ?? null; const effort = resolvedConfigValue(config, "model_reasoning_effort"); return { - profile: stringOrNull(config["profile"]), + profile: stringOrNull(config.profile), model: nonEmptyStringOrNull(resolvedConfigValue(config, "model")), - modelProvider: resolvedConfigValue(config, "model_provider"), + modelProvider: resolvedConfigValue(config, "model_provider") ?? null, reasoningEffort: isReasoningEffort(effort) ? effort : null, rawReasoningEffort: nonEmptyStringOrNull(effort), - reasoningSummary: resolvedConfigValue(config, "model_reasoning_summary"), - verbosity: resolvedConfigValue(config, "model_verbosity"), + reasoningSummary: resolvedConfigValue(config, "model_reasoning_summary") ?? null, + verbosity: resolvedConfigValue(config, "model_verbosity") ?? null, serviceTier: parseServiceTier(resolvedConfigValue(config, "service_tier")), approvalsReviewer: approvalsReviewerOrNull(resolvedConfigValue(config, "approvals_reviewer")), - approvalPolicy: resolvedConfigValue(config, "approval_policy"), - webSearch: resolvedConfigValue(config, "web_search"), - modelContextWindow: numberOrNull(config["model_context_window"]), - autoCompactTokenLimit: numberOrNull(config["model_auto_compact_token_limit"]), - sandboxMode: config["sandbox_mode"], - workspaceNetworkAccess: workspaceWrite["network_access"], - writableRoots: workspaceWrite["writable_roots"], + approvalPolicy: resolvedConfigValue(config, "approval_policy") ?? null, + webSearch: resolvedConfigValue(config, "web_search") ?? null, + modelContextWindow: numberOrNull(config.model_context_window), + autoCompactTokenLimit: numberOrNull(config.model_auto_compact_token_limit), + sandboxMode: config.sandbox_mode ?? null, + workspaceNetworkAccess: workspaceWrite?.network_access ?? null, + writableRoots: workspaceWrite?.writable_roots ?? null, hooksEnabled: features["hooks"], applyPatchFreeformEnabled: features["apply_patch_freeform"], - toolWebSearch: tools["web_search"], - apps: asRecord(config["apps"]), + toolWebSearch: tools?.web_search ?? null, + apps: config.apps ?? null, }; } -function configRecord(effectiveConfig: ConfigReadResponse | null): Record { - return fillMissingConfigValues(asRecord(effectiveConfig?.config), effectiveConfig?.layers ?? null); +type ConfigProjectionRecord = Partial & Record; +type ProfileProjectionRecord = Partial & Record; + +function configRecord(effectiveConfig: ConfigReadResponse | null): ConfigProjectionRecord { + return fillMissingConfigValues(asConfigRecord(effectiveConfig?.config), effectiveConfig?.layers ?? null); } -function selectedProfileConfig(config: Record): Record { - const profile = config["profile"]; +function selectedProfileConfig(config: ConfigProjectionRecord): ProfileProjectionRecord { + const profile = config.profile; const profileName = typeof profile === "string" && profile.length > 0 ? profile : null; - return profileName ? asRecord(asRecord(config["profiles"])[profileName]) : {}; + return profileName ? asProfileRecord(asRecord(config.profiles)[profileName]) : {}; } -function resolvedConfigValue(config: Record, key: string): unknown { +function resolvedConfigValue(config: ConfigProjectionRecord, key: K): Config[K] | undefined { const profileValue = selectedProfileConfig(config)[key]; return profileValue ?? config[key]; } @@ -80,14 +93,23 @@ function asRecord(value: unknown): Record { return value && typeof value === "object" ? (value as Record) : {}; } -function fillMissingConfigValues(config: Record, layers: ConfigReadResponse["layers"]): Record { +function asConfigRecord(value: unknown): ConfigProjectionRecord { + return asRecord(value) as ConfigProjectionRecord; +} + +function asProfileRecord(value: unknown): ProfileProjectionRecord { + return asRecord(value) as ProfileProjectionRecord; +} + +function fillMissingConfigValues(config: ConfigProjectionRecord, layers: ConfigReadResponse["layers"]): ConfigProjectionRecord { if (!layers || layers.length === 0) return config; const fallback = rawLayerConfigRecord(layers); if (Object.keys(fallback).length === 0) return config; - const merged = { ...config }; + const merged: ConfigProjectionRecord = { ...config }; + const mergedRecord = merged as Record; for (const [key, value] of Object.entries(fallback)) { - merged[key] ??= value; + mergedRecord[key] ??= value; } return merged; } diff --git a/src/runtime/view.ts b/src/runtime/view.ts index be43f4e6..17a746bc 100644 --- a/src/runtime/view.ts +++ b/src/runtime/view.ts @@ -262,13 +262,14 @@ function writableRootsLabel(value: unknown): string { return `${String(value.length)} roots`; } -function enabledAppsLabel(apps: Record): string { - const enabled = Object.entries(apps) +function enabledAppsLabel(apps: unknown): string { + const appConfig = asRecord(apps); + const enabled = Object.entries(appConfig) .filter(([key, app]) => key !== "_default" && asRecord(app)["enabled"] === true) .map(([key]) => key) .sort(); if (enabled.length > 0) return enabled.join(", "); - const defaultConfig = asRecord(apps["_default"]); + const defaultConfig = asRecord(appConfig["_default"]); return stringValue(defaultConfig["enabled"], "(from default)"); }