Model app-server runtime config snapshots

This commit is contained in:
murashit 2026-06-10 11:04:30 +09:00
parent 0df65090ca
commit 01d1a855fc
27 changed files with 359 additions and 231 deletions

View file

@ -112,9 +112,6 @@ const generatedAppServerImportLegacyFiles = [
"src/features/chat/inbound/routing.ts",
"src/features/chat/requests/approval.ts",
"src/features/chat/requests/user-input.ts",
"src/features/chat/runtime/config.ts",
"src/features/chat/runtime/effective-settings.ts",
"src/features/chat/runtime/state.ts",
"src/features/chat/session/connection-controller.ts",
"src/features/chat/threads/thread-goal-actions.ts",
"src/features/chat/threads/thread-history-controller.ts",

View file

@ -0,0 +1,200 @@
import type { ConfigReadResponse as AppServerConfigReadResponse } from "../generated/app-server/v2/ConfigReadResponse";
import type { Config as AppServerConfig } from "../generated/app-server/v2/Config";
import {
appServerApprovalsReviewerOrNull,
parseServiceTier,
type ApprovalPolicy,
type ApprovalsReviewer,
type ServiceTier,
} from "./thread-settings";
import { isReasoningEffort, type ReasoningEffort } from "../domain/catalog/metadata";
type ReasoningSummary = "auto" | "concise" | "detailed" | "none";
type Verbosity = "low" | "medium" | "high";
type WebSearchMode = "disabled" | "cached" | "live";
type SandboxMode = "read-only" | "workspace-write" | "danger-full-access";
export interface ActivePermissionProfile {
id: string;
extends: string | null;
}
export interface RuntimeConfigSnapshot {
profile: string | null;
model: string | null;
modelProvider: string | null;
reasoningEffort: ReasoningEffort | null;
rawReasoningEffort: string | null;
reasoningSummary: ReasoningSummary | null;
verbosity: Verbosity | null;
serviceTier: ServiceTier | null;
approvalsReviewer: ApprovalsReviewer | null;
approvalPolicy: ApprovalPolicy | null;
webSearch: WebSearchMode | null;
modelContextWindow: number | null;
autoCompactTokenLimit: number | null;
sandboxMode: SandboxMode | null;
workspaceNetworkAccess: boolean | null;
writableRoots: readonly string[] | null;
toolWebSearch: unknown;
apps: unknown;
}
export function emptyRuntimeConfigSnapshot(): RuntimeConfigSnapshot {
return {
profile: null,
model: null,
modelProvider: null,
reasoningEffort: null,
rawReasoningEffort: null,
reasoningSummary: null,
verbosity: null,
serviceTier: null,
approvalsReviewer: null,
approvalPolicy: null,
webSearch: null,
modelContextWindow: null,
autoCompactTokenLimit: null,
sandboxMode: null,
workspaceNetworkAccess: null,
writableRoots: null,
toolWebSearch: null,
apps: null,
};
}
export function runtimeConfigSnapshotFromAppServerConfig(response: AppServerConfigReadResponse): RuntimeConfigSnapshot {
const config = asConfigRecord(response.config);
const tools = asRecordOrNull(config.tools);
const workspaceWrite = asRecordOrNull(config.sandbox_workspace_write);
const effort = config.model_reasoning_effort;
return {
profile: selectedConfigProfile(response.layers),
model: nonEmptyStringOrNull(config.model),
modelProvider: nonEmptyStringOrNull(config.model_provider),
reasoningEffort: isReasoningEffort(effort) ? effort : null,
rawReasoningEffort: nonEmptyStringOrNull(effort),
reasoningSummary: reasoningSummaryOrNull(config.model_reasoning_summary),
verbosity: verbosityOrNull(config.model_verbosity),
serviceTier: parseServiceTier(config.service_tier),
approvalsReviewer: appServerApprovalsReviewerOrNull(config.approvals_reviewer),
approvalPolicy: approvalPolicyOrNull(config.approval_policy),
webSearch: webSearchModeOrNull(config.web_search),
modelContextWindow: numberOrNull(config.model_context_window),
autoCompactTokenLimit: numberOrNull(config.model_auto_compact_token_limit),
sandboxMode: sandboxModeOrNull(config.sandbox_mode),
workspaceNetworkAccess: booleanOrNull(workspaceWrite?.["network_access"]),
writableRoots: stringArrayOrNull(workspaceWrite?.["writable_roots"]),
toolWebSearch: cloneJsonLike(asRecordOrNull(tools?.["web_search"])),
apps: cloneJsonLike(asRecordOrNull(config.apps)),
};
}
export function cloneRuntimeConfigSnapshot(config: RuntimeConfigSnapshot): RuntimeConfigSnapshot {
return {
...config,
approvalPolicy: cloneApprovalPolicy(config.approvalPolicy),
writableRoots: config.writableRoots ? [...config.writableRoots] : null,
toolWebSearch: cloneJsonLike(config.toolWebSearch),
apps: cloneJsonLike(config.apps),
};
}
type ConfigProjectionRecord = Partial<AppServerConfig> & Record<string, unknown>;
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
}
function asRecordOrNull(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object" ? (value as Record<string, unknown>) : null;
}
function asConfigRecord(value: unknown): ConfigProjectionRecord {
return asRecord(value) as ConfigProjectionRecord;
}
function selectedConfigProfile(layers: AppServerConfigReadResponse["layers"]): string | null {
let selected: string | null = null;
if (!layers) return null;
for (const layer of layers) {
const name = layer.name;
if (name.type === "user" && typeof name.profile === "string" && name.profile.length > 0) {
selected = name.profile;
}
}
return selected;
}
function nonEmptyStringOrNull(value: unknown): string | null {
return typeof value === "string" && value.length > 0 ? value : null;
}
function numberOrNull(value: unknown): number | null {
if (typeof value === "number") return Number.isFinite(value) ? value : null;
if (typeof value === "bigint") return Number(value);
return null;
}
function booleanOrNull(value: unknown): boolean | null {
return typeof value === "boolean" ? value : null;
}
function stringArrayOrNull(value: unknown): string[] | null {
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : null;
}
function reasoningSummaryOrNull(value: unknown): ReasoningSummary | null {
return value === "auto" || value === "concise" || value === "detailed" || value === "none" ? value : null;
}
function verbosityOrNull(value: unknown): Verbosity | null {
return value === "low" || value === "medium" || value === "high" ? value : null;
}
function webSearchModeOrNull(value: unknown): WebSearchMode | null {
return value === "disabled" || value === "cached" || value === "live" ? value : null;
}
function sandboxModeOrNull(value: unknown): SandboxMode | null {
return value === "read-only" || value === "workspace-write" || value === "danger-full-access" ? value : null;
}
function approvalPolicyOrNull(value: unknown): ApprovalPolicy | null {
if (value === "untrusted" || value === "on-failure" || value === "on-request" || value === "never") return value;
const granular = asRecordOrNull(asRecordOrNull(value)?.["granular"]);
if (!granular) return null;
const sandboxApproval = granular["sandbox_approval"];
const rules = granular["rules"];
const skillApproval = granular["skill_approval"];
const requestPermissions = granular["request_permissions"];
const mcpElicitations = granular["mcp_elicitations"];
if (
typeof sandboxApproval !== "boolean" ||
typeof rules !== "boolean" ||
typeof skillApproval !== "boolean" ||
typeof requestPermissions !== "boolean" ||
typeof mcpElicitations !== "boolean"
) {
return null;
}
return {
granular: {
sandbox_approval: sandboxApproval,
rules,
skill_approval: skillApproval,
request_permissions: requestPermissions,
mcp_elicitations: mcpElicitations,
},
};
}
function cloneApprovalPolicy(value: ApprovalPolicy | null): ApprovalPolicy | null {
return value && typeof value === "object" ? { granular: { ...value.granular } } : value;
}
function cloneJsonLike(value: unknown): unknown {
if (Array.isArray(value)) return value.map(cloneJsonLike);
if (!value || typeof value !== "object") return value;
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneJsonLike(item)]));
}

View file

@ -1,11 +1,11 @@
import type { Diagnostics } from "./diagnostics";
import type { ConfigReadResponse } from "../generated/app-server/v2/ConfigReadResponse";
import type { RateLimitSnapshot } from "./runtime-metrics";
import { cloneRuntimeConfigSnapshot, type RuntimeConfigSnapshot } from "./runtime-config";
import type { Thread } from "../domain/threads/model";
import type { ModelMetadata, SkillMetadata } from "../domain/catalog/metadata";
export interface SharedAppServerMetadata {
effectiveConfig: ConfigReadResponse | null;
runtimeConfig: RuntimeConfigSnapshot | null;
availableModels: readonly ModelMetadata[];
availableSkills: readonly SkillMetadata[];
rateLimit: RateLimitSnapshot | null;
@ -72,6 +72,7 @@ export function cachedSharedModels(state: SharedAppServerState): ModelMetadata[]
function cloneSharedAppServerMetadata(metadata: SharedAppServerMetadata): SharedAppServerMetadata {
return {
...metadata,
runtimeConfig: metadata.runtimeConfig ? cloneRuntimeConfigSnapshot(metadata.runtimeConfig) : null,
availableModels: cloneModelMetadata(metadata.availableModels),
availableSkills: cloneSkillMetadata(metadata.availableSkills),
appServerDiagnostics: {

View file

@ -3,16 +3,29 @@ import type { ModeKind } from "../generated/app-server/ModeKind";
import type { Personality } from "../generated/app-server/Personality";
import type { ReasoningEffort } from "../generated/app-server/ReasoningEffort";
import type { ReasoningSummary } from "../generated/app-server/ReasoningSummary";
import type { AskForApproval } from "../generated/app-server/v2/AskForApproval";
import type { SandboxPolicy } from "../generated/app-server/v2/SandboxPolicy";
export type ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent";
export type ApprovalPolicy =
| "untrusted"
| "on-failure"
| "on-request"
| {
granular: {
sandbox_approval: boolean;
rules: boolean;
skill_approval: boolean;
request_permissions: boolean;
mcp_elicitations: boolean;
};
}
| "never";
export type ServiceTier = string;
export type ServiceTierRequest = string | null | undefined;
export interface ThreadSettingsUpdate {
cwd?: string | null;
approvalPolicy?: AskForApproval | null;
approvalPolicy?: ApprovalPolicy | null;
approvalsReviewer?: ApprovalsReviewer | null;
sandboxPolicy?: SandboxPolicy | null;
permissions?: string | null;

View file

@ -1,12 +1,12 @@
import type { InitializeResponse } from "../../generated/app-server/InitializeResponse";
import type { ReasoningEffort } from "../../domain/catalog/metadata";
import type { ConfigReadResponse } from "../../generated/app-server/v2/ConfigReadResponse";
import type { Thread } from "../../domain/threads/model";
import type { ModelMetadata, SkillMetadata } from "../../domain/catalog/metadata";
import type { ThreadGoal } from "../../generated/app-server/v2/ThreadGoal";
import type { ThreadSettingsUpdate } from "../../app-server/thread-settings";
import type { Diagnostics } from "../../app-server/diagnostics";
import { createAppServerDiagnostics } from "../../app-server/diagnostics";
import type { RuntimeConfigSnapshot } from "../../app-server/runtime-config";
import type { RateLimitSnapshot, ThreadTokenUsage } from "../../app-server/runtime-metrics";
import type { ApprovalsReviewer } from "./runtime/approvals";
import type { CollaborationMode } from "./runtime/collaboration";
@ -68,7 +68,7 @@ export type { ChatTranscriptState } from "./transcript-state";
interface ChatConnectionState {
status: string;
effectiveConfig: ConfigReadResponse | null;
runtimeConfig: RuntimeConfigSnapshot | null;
initializeResponse: InitializeResponse | null;
rateLimit: RateLimitSnapshot | null;
appServerDiagnostics: Diagnostics;
@ -125,7 +125,7 @@ type ConnectionAction =
| ConnectionInitializedAction
| {
type: "connection/metadata-applied";
effectiveConfig?: ConfigReadResponse | null;
runtimeConfig?: RuntimeConfigSnapshot | null;
availableModels?: readonly ModelMetadata[];
availableSkills?: readonly SkillMetadata[];
rateLimit?: RateLimitSnapshot | null;
@ -512,7 +512,7 @@ function reduceConnectionSlice(state: ChatConnectionState, action: ChatSliceActi
return patchObject(state, { initializeResponse: action.initializeResponse });
case "connection/metadata-applied":
return patchObject(state, {
...definedPatch("effectiveConfig", action.effectiveConfig),
...definedPatch("runtimeConfig", action.runtimeConfig),
...definedPatch("availableModels", action.availableModels),
...definedPatch("availableSkills", action.availableSkills),
...definedPatch("rateLimit", action.rateLimit),
@ -643,7 +643,7 @@ function clearDisconnectedConnectionState(state: ChatState): ChatState {
function initialConnectionState(): ChatConnectionState {
return {
status: "Idle",
effectiveConfig: null,
runtimeConfig: null,
initializeResponse: null,
appServerDiagnostics: createAppServerDiagnostics(),
rateLimit: null,

View file

@ -1,4 +1,4 @@
import { readRuntimeConfig } from "../../runtime/config";
import { runtimeConfigOrDefault } from "../../runtime/config";
import { autoReviewActive, currentModel, currentReasoningEffort, fastModeActive } from "../../runtime/effective-settings";
import { compactReasoningEffortLabel } from "../../runtime/override-commands";
import { contextSummary } from "../../runtime/status-summary";
@ -24,7 +24,7 @@ export function composerMetaViewModel(state: ChatState, snapshot: ReturnType<typ
};
}
const config = readRuntimeConfig(state.connection.effectiveConfig);
const config = runtimeConfigOrDefault(state.connection.runtimeConfig);
const context = contextSummary(snapshot);
const model = currentModel(snapshot, config);
const effort = currentReasoningEffort(snapshot, config);

View file

@ -1,4 +1,4 @@
import { readRuntimeConfig } from "../../runtime/config";
import { runtimeConfigOrDefault } from "../../runtime/config";
import {
currentModel,
currentReasoningEffort,
@ -19,7 +19,7 @@ import type {
export function runtimeSnapshotForChatSlices(input: RuntimeSnapshotInput) {
return {
effectiveConfig: input.effectiveConfig,
runtimeConfig: input.runtimeConfig,
activeThreadId: input.activeThread.id,
activeModel: input.runtime.activeModel,
activeReasoningEffort: input.runtime.activeReasoningEffort,
@ -44,7 +44,7 @@ export function runtimeComposerChoices(input: RuntimeComposerChoicesInput): {
modelChoices: RuntimeChoice[];
effortChoices: RuntimeChoice[];
} {
const config = readRuntimeConfig(input.state.connection.effectiveConfig);
const config = runtimeConfigOrDefault(input.state.connection.runtimeConfig);
const activeModel = currentModel(input.snapshot, config);
const models = sortedModelMetadata(input.state.connection.availableModels);
const modelChoices: RuntimeChoice[] = models.slice(0, 12).map((model) => ({
@ -86,7 +86,7 @@ export function statusSummaryLines(input: StatusSummaryLinesInput): string[] {
}
export function modelStatusLines(input: ModelStatusLinesInput): string[] {
const config = readRuntimeConfig(input.effectiveConfig);
const config = runtimeConfigOrDefault(input.runtimeConfig);
return [
`Model: ${currentModel(input.snapshot, config) ?? "(Codex default)"}`,
`Override: ${pendingRuntimeSettingLabel(input.requestedModel)}`,
@ -98,7 +98,7 @@ export function modelStatusLines(input: ModelStatusLinesInput): string[] {
}
export function effortStatusLines(input: EffortStatusLinesInput): string[] {
const config = readRuntimeConfig(input.effectiveConfig);
const config = runtimeConfigOrDefault(input.runtimeConfig);
return [
`Effort: ${currentReasoningEffort(input.snapshot, config) ?? "(Codex default)"}`,
`Override: ${pendingRuntimeSettingLabel(input.requestedReasoningEffort)}`,

View file

@ -1,6 +1,6 @@
import type { Thread } from "../../../../domain/threads/model";
import { getThreadTitle } from "../../../../domain/threads/model";
import { effectiveConfigSections, rateLimitSummary } from "../../runtime/status-summary";
import { runtimeConfigSections, rateLimitSummary } from "../../runtime/status-summary";
import { connectionDiagnosticSections } from "../../diagnostics";
import type { ConnectionDiagnosticsModelInput, ToolbarThreadRow, ToolbarViewModel, ToolbarViewModelInput } from "./types";
@ -16,7 +16,7 @@ export function toolbarViewModel(input: ToolbarViewModelInput): ToolbarViewModel
historyOpen,
statusPanelOpen,
rateLimit: limit,
configSections: effectiveConfigSections(snapshot, input.vaultPath),
configSections: runtimeConfigSections(snapshot, input.vaultPath),
openPanel: historyOpen ? "history" : chatActionsOpen ? "chat-actions" : statusPanelOpen ? "status" : null,
threads: toolbarThreadRows({
threads: state.threadList.listedThreads,

View file

@ -1,10 +1,10 @@
import type { ReasoningEffort } from "../../../../domain/catalog/metadata";
import type { RuntimeSnapshot } from "../../runtime/effective-settings";
import type { EffectiveConfigSection, RateLimitSummary } from "../../runtime/status-summary";
import type { RuntimeConfigSection, RateLimitSummary } from "../../runtime/status-summary";
import type { ChatState } from "../../chat-state";
export interface RuntimeSnapshotInput {
effectiveConfig: ChatState["connection"]["effectiveConfig"];
runtimeConfig: ChatState["connection"]["runtimeConfig"];
activeThread: Pick<ChatState["activeThread"], "id" | "tokenUsage">;
runtime: ChatState["runtime"];
rateLimit: ChatState["connection"]["rateLimit"];
@ -73,7 +73,7 @@ export interface ToolbarViewModel {
historyOpen: boolean;
statusPanelOpen: boolean;
rateLimit: RateLimitSummary | null;
configSections: EffectiveConfigSection[];
configSections: RuntimeConfigSection[];
openPanel: "history" | "chat-actions" | "status" | null;
threads: ToolbarThreadRow[];
connectLabel: string;
@ -111,14 +111,14 @@ export interface StatusSummaryLinesInput {
}
export interface ModelStatusLinesInput {
effectiveConfig: ChatState["connection"]["effectiveConfig"];
runtimeConfig: ChatState["connection"]["runtimeConfig"];
requestedModel: ChatState["runtime"]["requestedModel"];
snapshot: RuntimeSnapshot;
collaborationModeLabel: string;
}
export interface EffortStatusLinesInput {
effectiveConfig: ChatState["connection"]["effectiveConfig"];
runtimeConfig: ChatState["connection"]["runtimeConfig"];
requestedReasoningEffort: ChatState["runtime"]["requestedReasoningEffort"];
snapshot: RuntimeSnapshot;
}

View file

@ -1,6 +1,6 @@
import type { Thread } from "../../../domain/threads/model";
import type { OpenCodexPanelSnapshot } from "../../../workspace/open-panel-snapshot";
import { readRuntimeConfig } from "../runtime/config";
import { runtimeConfigOrDefault } from "../runtime/config";
import { currentModel } from "../runtime/effective-settings";
import { activeTurnId, chatTurnBusy, type ChatState } from "../chat-state";
import type { DisplayItem } from "../display/types";
@ -41,7 +41,7 @@ export function toolbarSlotSnapshot(state: ChatState, connected: boolean): ChatP
state.threadList.threadsLoaded,
threadListSignature(state.threadList.listedThreads),
modelsSignature(state.connection.availableModels),
state.connection.effectiveConfig,
state.connection.runtimeConfig,
state.connection.rateLimit,
state.activeThread.tokenUsage,
state.connection.appServerDiagnostics,
@ -87,17 +87,17 @@ export function composerSlotSnapshot(state: ChatState, activeComposerThreadName:
state.runtime.requestedModel,
state.runtime.requestedReasoningEffort,
state.activeThread.tokenUsage,
state.connection.effectiveConfig,
state.connection.runtimeConfig,
currentModel(
runtimeSnapshotForChatSlices({
effectiveConfig: state.connection.effectiveConfig,
runtimeConfig: state.connection.runtimeConfig,
activeThread: state.activeThread,
runtime: state.runtime,
rateLimit: state.connection.rateLimit,
displayItems: state.transcript.displayItems,
availableModels: state.connection.availableModels,
}),
readRuntimeConfig(state.connection.effectiveConfig),
runtimeConfigOrDefault(state.connection.runtimeConfig),
),
state.connection.availableSkills.length,
skillsSignature(state.connection.availableSkills),

View file

@ -1,93 +1,7 @@
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 { Config } from "../../../generated/app-server/v2/Config";
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/thread-settings";
import { isReasoningEffort, type ReasoningEffort } from "../../../domain/catalog/metadata";
import { approvalsReviewerOrNull, type ApprovalsReviewer } from "./approvals";
import { cloneRuntimeConfigSnapshot, emptyRuntimeConfigSnapshot, type RuntimeConfigSnapshot } from "../../../app-server/runtime-config";
export interface RuntimeConfigProjection {
profile: string | null;
model: string | null;
modelProvider: string | null;
reasoningEffort: ReasoningEffort | null;
rawReasoningEffort: string | null;
reasoningSummary: ReasoningSummary | null;
verbosity: Verbosity | null;
serviceTier: ServiceTier | null;
approvalsReviewer: ApprovalsReviewer | null;
approvalPolicy: AskForApproval | null;
webSearch: WebSearchMode | null;
modelContextWindow: number | null;
autoCompactTokenLimit: number | null;
sandboxMode: SandboxMode | null;
workspaceNetworkAccess: SandboxWorkspaceWrite["network_access"] | null;
writableRoots: SandboxWorkspaceWrite["writable_roots"] | null;
toolWebSearch: ToolsV2["web_search"] | null;
apps: AppsConfig | null;
}
export function readRuntimeConfig(effectiveConfig: ConfigReadResponse | null): RuntimeConfigProjection {
const config = asConfigRecord(effectiveConfig?.config);
const tools = config.tools ?? null;
const workspaceWrite = config.sandbox_workspace_write ?? null;
const effort = config.model_reasoning_effort;
return {
profile: selectedConfigProfile(effectiveConfig?.layers ?? null),
model: nonEmptyStringOrNull(config.model),
modelProvider: config.model_provider ?? null,
reasoningEffort: isReasoningEffort(effort) ? effort : null,
rawReasoningEffort: nonEmptyStringOrNull(effort),
reasoningSummary: config.model_reasoning_summary ?? null,
verbosity: config.model_verbosity ?? null,
serviceTier: parseServiceTier(config.service_tier),
approvalsReviewer: approvalsReviewerOrNull(config.approvals_reviewer),
approvalPolicy: config.approval_policy ?? null,
webSearch: 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,
toolWebSearch: tools?.web_search ?? null,
apps: config.apps ?? null,
};
}
type ConfigProjectionRecord = Partial<Config> & Record<string, unknown>;
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
}
function asConfigRecord(value: unknown): ConfigProjectionRecord {
return asRecord(value) as ConfigProjectionRecord;
}
function selectedConfigProfile(layers: ConfigReadResponse["layers"]): string | null {
let selected: string | null = null;
if (!layers) return null;
for (const layer of layers) {
const name = layer.name;
if (name.type === "user" && typeof name.profile === "string" && name.profile.length > 0) {
selected = name.profile;
}
}
return selected;
}
function nonEmptyStringOrNull(value: unknown): string | null {
return typeof value === "string" && value.length > 0 ? value : null;
}
function numberOrNull(value: unknown): number | null {
if (typeof value === "number") return Number.isFinite(value) ? value : null;
if (typeof value === "bigint") return Number(value);
return null;
export type RuntimeConfigProjection = RuntimeConfigSnapshot;
export function runtimeConfigOrDefault(runtimeConfig: RuntimeConfigSnapshot | null): RuntimeConfigProjection {
return runtimeConfig ? cloneRuntimeConfigSnapshot(runtimeConfig) : emptyRuntimeConfigSnapshot();
}

View file

@ -1,6 +1,4 @@
import type { ActivePermissionProfile } from "../../../generated/app-server/v2/ActivePermissionProfile";
import type { AskForApproval } from "../../../generated/app-server/v2/AskForApproval";
import type { ConfigReadResponse } from "../../../generated/app-server/v2/ConfigReadResponse";
import type { ActivePermissionProfile, RuntimeConfigSnapshot } from "../../../app-server/runtime-config";
import type { RateLimitSnapshot, ThreadTokenUsage } from "../../../app-server/runtime-metrics";
import { findModelMetadataByIdOrName, type ModelMetadata } from "../../../domain/catalog/metadata";
import {
@ -9,9 +7,10 @@ import {
serviceTierRequestValue,
type ServiceTier,
type ServiceTierRequest,
type ApprovalPolicy,
} from "../../../app-server/thread-settings";
import { supportedEffortsForModelMetadata, type ReasoningEffort } from "../../../domain/catalog/metadata";
import { readRuntimeConfig, type RuntimeConfigProjection } from "./config";
import { runtimeConfigOrDefault, type RuntimeConfigProjection } from "./config";
import type { CollaborationMode } from "./collaboration";
import { isAutoReviewReviewer, type ApprovalsReviewer } from "./approvals";
import { isFastServiceTier, type RequestedServiceTier } from "./service-tier-state";
@ -19,13 +18,13 @@ import { isFastServiceTier, type RequestedServiceTier } from "./service-tier-sta
export type PendingRuntimeSetting<T> = { kind: "unchanged" } | { kind: "set"; value: T } | { kind: "resetToConfig" };
export interface RuntimeSnapshot {
effectiveConfig: ConfigReadResponse | null;
runtimeConfig: RuntimeConfigSnapshot | null;
activeThreadId: string | null;
activeModel: string | null;
activeReasoningEffort: ReasoningEffort | null;
activeCollaborationMode: CollaborationMode;
activeServiceTier: ServiceTier | null;
activeApprovalPolicy: AskForApproval | null;
activeApprovalPolicy: ApprovalPolicy | null;
activeApprovalsReviewer: ApprovalsReviewer | null;
activePermissionProfile: ActivePermissionProfile | null;
requestedModel: PendingRuntimeSetting<string>;
@ -41,7 +40,7 @@ export interface RuntimeSnapshot {
export function currentServiceTier(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): string | null {
if (snapshot.requestedServiceTier.kind === "set" && snapshot.requestedServiceTier.value === "fast") return "fast";
if (snapshot.requestedServiceTier.kind === "set" && snapshot.requestedServiceTier.value === "off") return null;
@ -51,7 +50,7 @@ export function currentServiceTier(
export function requestedOrConfiguredServiceTier(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): ServiceTierRequest {
if (snapshot.requestedServiceTier.kind === "set") {
return snapshot.requestedServiceTier.value === "fast"
@ -66,7 +65,7 @@ export function requestedOrConfiguredServiceTier(
export function currentModel(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): string | null {
const configModel = config.model;
if (snapshot.requestedModel.kind === "set") return snapshot.requestedModel.value;
@ -76,7 +75,7 @@ export function currentModel(
export function currentReasoningEffort(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): ReasoningEffort | null {
if (snapshot.requestedReasoningEffort.kind === "set") return snapshot.requestedReasoningEffort.value;
if (snapshot.requestedReasoningEffort.kind === "resetToConfig") return config.reasoningEffort;
@ -85,7 +84,7 @@ export function currentReasoningEffort(
export function currentApprovalsReviewer(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): ApprovalsReviewer | null {
if (snapshot.requestedApprovalsReviewer.kind === "set") return snapshot.requestedApprovalsReviewer.value;
if (snapshot.requestedApprovalsReviewer.kind === "resetToConfig") return config.approvalsReviewer;
@ -94,14 +93,14 @@ export function currentApprovalsReviewer(
export function currentApprovalPolicy(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
): AskForApproval | null {
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): ApprovalPolicy | null {
return snapshot.activeApprovalPolicy ?? config.approvalPolicy;
}
export function autoReviewActive(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): boolean {
return isAutoReviewReviewer(currentApprovalsReviewer(snapshot, config));
}
@ -113,21 +112,21 @@ export function supportedReasoningEfforts(snapshot: RuntimeSnapshot): ReasoningE
export function serviceTierLabel(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): string {
return currentServiceTier(snapshot, config) ?? "(Codex default)";
}
export function fastModeActive(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): boolean {
return isFastServiceTier(currentServiceTier(snapshot, config), currentModelServiceTiers(snapshot, config));
}
export function fastModeLabel(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): string {
if (snapshot.requestedServiceTier.kind === "set" && snapshot.requestedServiceTier.value === "off") return "off";
if (fastModeActive(snapshot, config)) return "on";
@ -137,7 +136,7 @@ export function fastModeLabel(
export function fastServiceTierRequestValue(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): string {
return currentModelServiceTiers(snapshot, config).find((tier) => tier.name.trim().toLowerCase() === "fast")?.id ?? "fast";
}
@ -168,7 +167,7 @@ export function pendingRuntimeSettingLabel<T>(setting: PendingRuntimeSetting<T>)
function currentModelServiceTiers(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): ModelMetadata["serviceTiers"] {
return findModelMetadataByIdOrName(snapshot.availableModels, currentModel(snapshot, config))?.serviceTiers ?? [];
}

View file

@ -3,7 +3,7 @@ import type { ReasoningEffort } from "../../../domain/catalog/metadata";
import { autoReviewReviewerForState, autoReviewToggleMessage, nextAutoReviewState } from "./approvals";
import type { CollaborationMode } from "./collaboration";
import { collaborationModeToggleMessage, nextCollaborationMode } from "./collaboration";
import { readRuntimeConfig } from "./config";
import { runtimeConfigOrDefault } from "./config";
import { autoReviewActive, fastModeActive, type RuntimeSnapshot } from "./effective-settings";
import { pendingThreadSettingsUpdate as buildPendingThreadSettingsUpdate, type TurnCollaborationModeWarning } from "./turn-settings";
import type { ThreadSettingsUpdate } from "../../../app-server/thread-settings";
@ -106,7 +106,7 @@ async function setRequestedReasoningEffortFromUi(host: RuntimeSettingsActionsHos
async function toggleFastMode(host: RuntimeSettingsActionsHost): Promise<void> {
const snapshot = host.runtimeSnapshot();
const config = readRuntimeConfig(state(host).connection.effectiveConfig);
const config = runtimeConfigOrDefault(state(host).connection.runtimeConfig);
const next: RequestedServiceTier = fastModeActive(snapshot, config) ? "off" : "fast";
dispatch(host, { type: "runtime/requested-service-tier-set", serviceTier: next });
dispatch(host, { type: "ui/panel-set", panel: null });
@ -129,7 +129,7 @@ async function setCollaborationMode(host: RuntimeSettingsActionsHost, collaborat
async function toggleAutoReview(host: RuntimeSettingsActionsHost): Promise<void> {
const nextState = nextAutoReviewState(
autoReviewActive(host.runtimeSnapshot(), readRuntimeConfig(state(host).connection.effectiveConfig)),
autoReviewActive(host.runtimeSnapshot(), runtimeConfigOrDefault(state(host).connection.runtimeConfig)),
);
dispatch(host, { type: "runtime/requested-approvals-reviewer-set", approvalsReviewer: autoReviewReviewerForState(nextState) });
dispatch(host, { type: "ui/panel-set", panel: null });

View file

@ -1,6 +1,5 @@
import type { ActivePermissionProfile } from "../../../generated/app-server/v2/ActivePermissionProfile";
import type { AskForApproval } from "../../../generated/app-server/v2/AskForApproval";
import { parseServiceTier, type ServiceTier, type ThreadSettingsUpdate } from "../../../app-server/thread-settings";
import type { ActivePermissionProfile } from "../../../app-server/runtime-config";
import { parseServiceTier, type ApprovalPolicy, type ServiceTier, type ThreadSettingsUpdate } from "../../../app-server/thread-settings";
import type { CollaborationMode } from "./collaboration";
import type { ReasoningEffort } from "../../../domain/catalog/metadata";
import type { ApprovalsReviewer } from "./approvals";
@ -17,7 +16,7 @@ export interface ChatRuntimeState {
activeReasoningEffort: ReasoningEffort | null;
activeCollaborationMode: CollaborationMode;
activeServiceTier: ServiceTier | null;
activeApprovalPolicy: AskForApproval | null;
activeApprovalPolicy: ApprovalPolicy | null;
activeApprovalsReviewer: ApprovalsReviewer | null;
activePermissionProfile: ActivePermissionProfile | null;
requestedModel: PendingRuntimeSetting<string>;

View file

@ -2,7 +2,7 @@ import type { RateLimitWindow, SpendControlLimitSnapshot, ThreadTokenUsage } fro
import { jsonPreview } from "../../../utils";
import { sortedModelMetadata } from "../../../domain/catalog/metadata";
import { defaultEffortForModelMetadata } from "../../../domain/catalog/metadata";
import { readRuntimeConfig, type RuntimeConfigProjection } from "./config";
import { runtimeConfigOrDefault, type RuntimeConfigProjection } from "./config";
import {
currentApprovalsReviewer,
currentApprovalPolicy,
@ -38,7 +38,7 @@ interface RateLimitSummaryRow {
level: "ok" | "warn" | "danger";
}
export interface EffectiveConfigSection {
export interface RuntimeConfigSection {
title: string;
rows: { key: string; value: string }[];
}
@ -48,7 +48,7 @@ const NOT_REPORTED_LABEL = "(not reported)";
export function contextSummary(snapshot: RuntimeSnapshot): ContextSummary | null {
const usage = snapshot.tokenUsage;
const config = readRuntimeConfig(snapshot.effectiveConfig);
const config = runtimeConfigOrDefault(snapshot.runtimeConfig);
const contextWindow = usage?.modelContextWindow ?? config.modelContextWindow;
if (!usage) {
if (!snapshot.activeThreadId) return null;
@ -107,8 +107,8 @@ export function rateLimitSummary(snapshot: RuntimeSnapshot, nowMs = Date.now()):
};
}
export function effectiveConfigSections(snapshot: RuntimeSnapshot, vaultPath: string): EffectiveConfigSection[] {
const config = readRuntimeConfig(snapshot.effectiveConfig);
export function runtimeConfigSections(snapshot: RuntimeSnapshot, vaultPath: string): RuntimeConfigSection[] {
const config = runtimeConfigOrDefault(snapshot.runtimeConfig);
return [
{
title: "Scope",

View file

@ -1,5 +1,5 @@
import { applyThreadSettingsValue, appServerCollaborationMode, type ThreadSettingsUpdate } from "../../../app-server/thread-settings";
import { readRuntimeConfig, type RuntimeConfigProjection } from "./config";
import { runtimeConfigOrDefault, type RuntimeConfigProjection } from "./config";
import {
currentModel,
currentReasoningEffort,
@ -33,7 +33,7 @@ export function requestedTurnCollaborationModeSettings(snapshot: RuntimeSnapshot
export function pendingThreadSettingsUpdate(
snapshot: RuntimeSnapshot,
config: RuntimeConfigProjection = readRuntimeConfig(snapshot.effectiveConfig),
config: RuntimeConfigProjection = runtimeConfigOrDefault(snapshot.runtimeConfig),
): PendingThreadSettingsUpdate {
const update: ThreadSettingsUpdate = {};
const collaborationModeSettings = requestedTurnCollaborationModeSettings(snapshot);

View file

@ -1,5 +1,6 @@
import { type Diagnostics, diagnosticProbeError, diagnosticProbeOk } from "../../../app-server/diagnostics";
import { listModelMetadata, listSkillCatalog } from "../../../app-server/resource-operations";
import { runtimeConfigSnapshotFromAppServerConfig } from "../../../app-server/runtime-config";
import { rateLimitSnapshotFromAppServerSnapshot } from "../../../app-server/runtime-metrics";
import type { SharedAppServerMetadata } from "../../../app-server/shared-cache-state";
import type { ModelMetadata, SkillMetadata } from "../../../domain/catalog/metadata";
@ -57,7 +58,7 @@ export function createChatServerMetadataActions(host: ChatServerMetadataActionsH
function serverMetadataSnapshot(host: ChatServerMetadataActionsHost): SharedAppServerMetadata {
const state = host.stateStore.getState();
return {
effectiveConfig: state.connection.effectiveConfig,
runtimeConfig: state.connection.runtimeConfig,
availableModels: state.connection.availableModels,
availableSkills: state.connection.availableSkills,
rateLimit: state.connection.rateLimit,
@ -68,7 +69,7 @@ function serverMetadataSnapshot(host: ChatServerMetadataActionsHost): SharedAppS
function applyAppServerMetadata(host: ChatServerMetadataActionsHost, metadata: SharedAppServerMetadata): void {
host.stateStore.dispatch({
type: "connection/metadata-applied",
effectiveConfig: metadata.effectiveConfig,
runtimeConfig: metadata.runtimeConfig,
availableModels: metadata.availableModels,
availableSkills: metadata.availableSkills,
rateLimit: metadata.rateLimit,
@ -79,14 +80,14 @@ function applyAppServerMetadata(host: ChatServerMetadataActionsHost, metadata: S
async function loadAppServerMetadata(host: ChatServerMetadataActionsHost): Promise<SharedAppServerMetadata | null> {
const client = host.currentClient();
if (!client) return null;
const effectiveConfig = await client.readEffectiveConfig(host.vaultPath);
const runtimeConfig = runtimeConfigSnapshotFromAppServerConfig(await client.readEffectiveConfig(host.vaultPath));
const [models, skills, rateLimit] = await Promise.all([loadModels(host), loadSkills(host), loadRateLimit(host)]);
const diagnostics = cloneAppServerDiagnostics(host.stateStore.getState().connection.appServerDiagnostics);
diagnostics.probes["model/list"] = models.probe;
diagnostics.probes["skills/list"] = skills.probe;
diagnostics.probes["account/rateLimits/read"] = rateLimit.probe;
return {
effectiveConfig,
runtimeConfig,
availableModels: models.data,
availableSkills: skills.data,
rateLimit: rateLimit.data,

View file

@ -1,8 +1,7 @@
import { parseServiceTier } from "../../../app-server/thread-settings";
import { parseServiceTier, type ApprovalPolicy } from "../../../app-server/thread-settings";
import { upsertThread } from "../../../domain/threads/model";
import { threadFromAppServerThread } from "../../../app-server/thread-model";
import type { ReasoningEffort } from "../../../domain/catalog/metadata";
import type { AskForApproval } from "../../../generated/app-server/v2/AskForApproval";
import type { Thread } from "../../../domain/threads/model";
import type { ThreadStartResponse } from "../../../generated/app-server/v2/ThreadStartResponse";
import type { ThreadResumeResponse } from "../../../generated/app-server/v2/ThreadResumeResponse";
@ -15,7 +14,7 @@ interface ThreadActivationResponse {
cwd: string;
model: string | null;
serviceTier: string | null;
approvalPolicy: AskForApproval | null;
approvalPolicy: ApprovalPolicy | null;
approvalsReviewer: ChatRuntimeState["activeApprovalsReviewer"];
activePermissionProfile: ChatRuntimeState["activePermissionProfile"];
reasoningEffort: ReasoningEffort | null;

View file

@ -1,7 +1,7 @@
import type { ButtonHTMLAttributes, ComponentChild as UiNode, TargetedKeyboardEvent } from "preact";
import { useLayoutEffect, useRef } from "preact/hooks";
import type { EffectiveConfigSection, RateLimitSummary } from "../runtime/status-summary";
import type { RuntimeConfigSection, RateLimitSummary } from "../runtime/status-summary";
import { IconButton } from "../../../shared/ui/components";
import { renderUiRoot } from "../../../shared/ui/ui-root";
import type { ToolbarDiagnosticSection, ToolbarThreadRow, ToolbarViewModel } from "../panel/model/types";
@ -139,7 +139,7 @@ function StatusPanel({ model, actions }: { model: ToolbarViewModel; actions: Too
</div>
<RateLimitPanel rateLimit={model.rateLimit} />
<ConnectionDiagnostics sections={model.diagnostics} />
<EffectiveConfigPanel sections={model.configSections} />
<RuntimeConfigPanel sections={model.configSections} />
</>
);
}
@ -208,7 +208,7 @@ function DiagnosticSection({ section }: { section: ToolbarDiagnosticSection }):
);
}
function EffectiveConfigPanel({ sections }: { sections: EffectiveConfigSection[] }): UiNode {
function RuntimeConfigPanel({ sections }: { sections: RuntimeConfigSection[] }): UiNode {
return (
<div className="codex-panel__config">
<div className="codex-panel__config-title">Effective Codex config</div>
@ -221,7 +221,7 @@ function EffectiveConfigPanel({ sections }: { sections: EffectiveConfigSection[]
);
}
function FragmentedConfigSection({ section }: { section: EffectiveConfigSection }): UiNode {
function FragmentedConfigSection({ section }: { section: RuntimeConfigSection }): UiNode {
return (
<>
<div className="codex-panel__config-section">{section.title}</div>

View file

@ -608,7 +608,7 @@ export class CodexChatView extends ItemView {
private modelStatusLines(): string[] {
return buildModelStatusLines({
effectiveConfig: this.state.connection.effectiveConfig,
runtimeConfig: this.state.connection.runtimeConfig,
requestedModel: this.state.runtime.requestedModel,
snapshot: this.runtimeSnapshot(),
collaborationModeLabel: this.collaborationModeLabel(),
@ -617,7 +617,7 @@ export class CodexChatView extends ItemView {
private effortStatusLines(): string[] {
return buildEffortStatusLines({
effectiveConfig: this.state.connection.effectiveConfig,
runtimeConfig: this.state.connection.runtimeConfig,
requestedReasoningEffort: this.state.runtime.requestedReasoningEffort,
snapshot: this.runtimeSnapshot(),
});
@ -648,7 +648,7 @@ export class CodexChatView extends ItemView {
private runtimeSnapshotForState(state: ChatState): RuntimeSnapshot {
return runtimeSnapshotForChatSlices({
effectiveConfig: state.connection.effectiveConfig,
runtimeConfig: state.connection.runtimeConfig,
activeThread: state.activeThread,
runtime: state.runtime,
rateLimit: state.connection.rateLimit,

View file

@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import { createAppServerDiagnostics } from "../../src/app-server/diagnostics";
import { emptyRuntimeConfigSnapshot } from "../../src/app-server/runtime-config";
import {
applySharedAppServerMetadata,
applySharedModels,
@ -35,7 +36,7 @@ describe("shared app-server cache state", () => {
expect(cachedSharedModels(modelState)[0]?.supportedReasoningEfforts).toEqual([]);
const metadataState = applySharedAppServerMetadata(createSharedAppServerState(), {
effectiveConfig: null,
runtimeConfig: emptyRuntimeConfigSnapshot(),
availableModels: sourceModels,
availableSkills: [{ name: "skill", description: "", path: "/tmp/skill", enabled: true }],
rateLimit: null,

View file

@ -40,7 +40,7 @@ describe("chat server actions", () => {
stateStore,
vaultPath: "/vault",
currentClient: () => client,
runtimeSnapshot: () => ({ requestedServiceTier: { kind: "unchanged" }, effectiveConfig: null }) as never,
runtimeSnapshot: () => ({ requestedServiceTier: { kind: "unchanged" }, runtimeConfig: null }) as never,
publishThreadList,
syncThreadGoal,
});
@ -73,7 +73,7 @@ describe("chat server actions", () => {
stateStore,
vaultPath: "/vault",
currentClient: () => client,
runtimeSnapshot: () => ({ requestedServiceTier: { kind: "unchanged" }, effectiveConfig: null }) as never,
runtimeSnapshot: () => ({ requestedServiceTier: { kind: "unchanged" }, runtimeConfig: null }) as never,
publishThreadList: vi.fn(),
syncThreadGoal,
});
@ -104,7 +104,7 @@ describe("chat server actions", () => {
stateStore,
vaultPath: "/vault",
currentClient: () => client,
runtimeSnapshot: () => ({ requestedServiceTier: { kind: "unchanged" }, effectiveConfig: null }) as never,
runtimeSnapshot: () => ({ requestedServiceTier: { kind: "unchanged" }, runtimeConfig: null }) as never,
publishThreadList,
syncThreadGoal: () => undefined,
});

View file

@ -130,7 +130,7 @@ function clientFixture(
function runtimeSnapshotFixture(state: ChatState) {
return runtimeSnapshotForChatSlices({
effectiveConfig: state.connection.effectiveConfig,
runtimeConfig: state.connection.runtimeConfig,
activeThread: state.activeThread,
runtime: state.runtime,
rateLimit: state.connection.rateLimit,

View file

@ -1,6 +1,7 @@
import { describe, expect, it, vi } from "vitest";
import type { AppServerClient } from "../../../../src/app-server/client";
import { emptyRuntimeConfigSnapshot } from "../../../../src/app-server/runtime-config";
import { createChatState, createChatStateStore } from "../../../../src/features/chat/chat-state";
import {
ChatConnectionController,
@ -99,7 +100,7 @@ describe("ChatConnectionController", () => {
it("clears disconnected connection state on server exit while keeping last startup metadata", () => {
const { controller, host, stateStore } = createController({ connected: true });
const initializeResponse = { codexHome: "/codex", platformFamily: "unix", platformOs: "macos", userAgent: "test" } as const;
const effectiveConfig = { config: { model: "gpt-5.1" }, layers: [] } as never;
const runtimeConfig = { ...emptyRuntimeConfigSnapshot(), model: "gpt-5.1" };
stateStore.dispatch({ type: "connection/initialized", initializeResponse });
stateStore.dispatch({
type: "thread-list/applied",
@ -110,7 +111,7 @@ describe("ChatConnectionController", () => {
type: "connection/metadata-applied",
availableModels: [{ id: "model-1" } as never],
availableSkills: [{ name: "skill-1" } as never],
effectiveConfig,
runtimeConfig,
});
controller.handleExit();
@ -129,7 +130,7 @@ describe("ChatConnectionController", () => {
connection: {
availableModels: [],
availableSkills: [],
effectiveConfig,
runtimeConfig,
initializeResponse,
},
});

View file

@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { DEFAULT_SETTINGS } from "../../../src/settings/model";
import type { CodexChatHost } from "../../../src/features/chat/chat-host";
import { createAppServerDiagnostics } from "../../../src/app-server/diagnostics";
import { emptyRuntimeConfigSnapshot } from "../../../src/app-server/runtime-config";
import { threadFromAppServerThread } from "../../../src/app-server/thread-model";
import { createChatState, type ChatState } from "../../../src/features/chat/chat-state";
import { composerSlotSnapshot } from "../../../src/features/chat/panel/snapshot";
@ -174,7 +175,7 @@ describe("CodexChatView connection lifecycle", () => {
expect(publishAppServerMetadata).toHaveBeenCalledWith(
expect.objectContaining({
effectiveConfig: {},
runtimeConfig: expect.any(Object),
availableModels: [],
availableSkills: [],
}),
@ -405,7 +406,7 @@ describe("CodexChatView connection lifecycle", () => {
cachedAppServerMetadata: vi.fn(
() =>
({
effectiveConfig: { config: { model: "gpt-cached" }, origins: {}, layers: [] },
runtimeConfig: { ...emptyRuntimeConfigSnapshot(), model: "gpt-cached" },
availableModels: [],
availableSkills: [{ name: "writer", enabled: true }],
rateLimit: null,
@ -421,12 +422,12 @@ describe("CodexChatView connection lifecycle", () => {
view as unknown as {
state: {
threadList: { listedThreads: unknown[] };
connection: { effectiveConfig: unknown; availableModels: unknown[]; availableSkills: unknown[] };
connection: { runtimeConfig: unknown; availableModels: unknown[]; availableSkills: unknown[] };
};
}
).state;
expect(state.threadList.listedThreads).toEqual([cachedThread]);
expect(state.connection.effectiveConfig).toEqual({ config: { model: "gpt-cached" }, origins: {}, layers: [] });
expect(state.connection.runtimeConfig).toEqual({ ...emptyRuntimeConfigSnapshot(), model: "gpt-cached" });
expect(state.connection.availableModels).toEqual([]);
expect(state.connection.availableSkills).toEqual([{ name: "writer", enabled: true }]);
});
@ -434,13 +435,13 @@ describe("CodexChatView connection lifecycle", () => {
it("tracks composer slot dependencies for model and skill suggestions", async () => {
await chatView();
const state = createChatState();
state.connection.effectiveConfig = effectiveConfig("gpt-configured");
state.connection.runtimeConfig = runtimeConfig("gpt-configured");
state.connection.availableSkills = [skillFixture("writer")];
const base = composerSlotSnapshot(state, null);
expect(
composerSlotSnapshot({ ...state, connection: { ...state.connection, effectiveConfig: effectiveConfig("gpt-updated") } }, null),
composerSlotSnapshot({ ...state, connection: { ...state.connection, runtimeConfig: runtimeConfig("gpt-updated") } }, null),
).not.toBe(base);
expect(
composerSlotSnapshot({ ...state, runtime: { ...state.runtime, requestedModel: { kind: "set", value: "gpt-requested" } } }, null),
@ -1094,8 +1095,8 @@ function threadFixture(threadId: string): Thread {
};
}
function effectiveConfig(model: string): ChatState["connection"]["effectiveConfig"] {
return { config: { model }, origins: {}, layers: [] } as never;
function runtimeConfig(model: string): ChatState["connection"]["runtimeConfig"] {
return { ...emptyRuntimeConfigSnapshot(), model };
}
function skillFixture(name: string): ChatState["connection"]["availableSkills"][number] {

View file

@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import { createAppServerDiagnostics } from "../../../src/app-server/diagnostics";
import { runtimeConfigSnapshotFromAppServerConfig, type RuntimeConfigSnapshot } from "../../../src/app-server/runtime-config";
import { createChatState } from "../../../src/features/chat/chat-state";
import {
activeComposerThreadName,
@ -27,7 +28,7 @@ describe("chat view model", () => {
state.threadList.listedThreads = [threadFixture("thread-1", "Active"), threadFixture("thread-2", "Other")];
state.turn.lifecycle = { kind: "running", turnId: "turn" };
state.ui.toolbarPanel = "history";
state.connection.effectiveConfig = effectiveConfigFixture({ model: "gpt-5.5", model_reasoning_effort: "high" });
state.connection.runtimeConfig = runtimeConfigFixture({ model: "gpt-5.5", model_reasoning_effort: "high" });
state.connection.appServerDiagnostics = createAppServerDiagnostics();
const model = toolbarViewModel({
@ -54,7 +55,7 @@ describe("chat view model", () => {
const state = createChatState();
state.activeThread.id = "thread-1";
state.runtime.selectedCollaborationMode = "plan";
state.connection.effectiveConfig = effectiveConfigFixture({
state.connection.runtimeConfig = runtimeConfigFixture({
model: "gpt-5.5",
model_reasoning_effort: "high",
approvals_reviewer: "auto_review",
@ -100,7 +101,7 @@ describe("chat view model", () => {
role: "assistant",
},
];
state.connection.effectiveConfig = effectiveConfigFixture({ model: "gpt-5.5" });
state.connection.runtimeConfig = runtimeConfigFixture({ model: "gpt-5.5" });
expect(composerMetaViewModel(state, runtimeSnapshotFixture(state))).toMatchObject({
fatal: null,
@ -169,7 +170,7 @@ describe("chat view model", () => {
it("builds slash-command status lines from chat state", () => {
const state = createChatState();
state.activeThread.id = "thread-1";
state.connection.effectiveConfig = effectiveConfigFixture({
state.connection.runtimeConfig = runtimeConfigFixture({
model: "gpt-5.5",
model_provider: "openai",
model_reasoning_effort: "high",
@ -181,7 +182,7 @@ describe("chat view model", () => {
expect(statusSummaryLines({ activeThreadId: state.activeThread.id, snapshot })[1]).toBe("Thread: thread-1");
expect(
modelStatusLines({
effectiveConfig: state.connection.effectiveConfig,
runtimeConfig: state.connection.runtimeConfig,
requestedModel: state.runtime.requestedModel,
snapshot,
collaborationModeLabel: "Default",
@ -189,7 +190,7 @@ describe("chat view model", () => {
).toContain("Model: gpt-5.5");
expect(
modelStatusLines({
effectiveConfig: state.connection.effectiveConfig,
runtimeConfig: state.connection.runtimeConfig,
requestedModel: state.runtime.requestedModel,
snapshot,
collaborationModeLabel: "Default",
@ -197,7 +198,7 @@ describe("chat view model", () => {
).toContain("Mode: Default");
expect(
effortStatusLines({
effectiveConfig: state.connection.effectiveConfig,
runtimeConfig: state.connection.runtimeConfig,
requestedReasoningEffort: state.runtime.requestedReasoningEffort,
snapshot,
}),
@ -206,7 +207,7 @@ describe("chat view model", () => {
it("builds runtime composer choices from immutable chat state snapshots", () => {
const state = createChatState();
state.connection.effectiveConfig = effectiveConfigFixture({ model: "gpt-5.5", model_reasoning_effort: "high" });
state.connection.runtimeConfig = runtimeConfigFixture({ model: "gpt-5.5", model_reasoning_effort: "high" });
state.connection.availableModels = [modelFixture("gpt-5.5"), modelFixture("gpt-5-mini")];
const selectedModels: (string | null)[] = [];
const selectedEfforts: string[] = [];
@ -250,17 +251,17 @@ describe("chat view model", () => {
});
});
function effectiveConfigFixture(config: Record<string, unknown>): ConfigReadResponse {
return {
function runtimeConfigFixture(config: Record<string, unknown>): RuntimeConfigSnapshot {
return runtimeConfigSnapshotFromAppServerConfig({
config: config as ConfigReadResponse["config"],
origins: {},
layers: null,
};
});
}
function runtimeSnapshotFixture(state: ChatState) {
return runtimeSnapshotForChatSlices({
effectiveConfig: state.connection.effectiveConfig,
runtimeConfig: state.connection.runtimeConfig,
activeThread: state.activeThread,
runtime: state.runtime,
rateLimit: state.connection.rateLimit,

View file

@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import { runtimeConfigSnapshotFromAppServerConfig, type RuntimeConfigSnapshot } from "../../src/app-server/runtime-config";
import type { ConfigReadResponse } from "../../src/generated/app-server/v2/ConfigReadResponse";
import type { ModelMetadata } from "../../src/domain/catalog/metadata";
import {
@ -27,8 +28,8 @@ import {
type RuntimeSnapshot,
} from "../../src/features/chat/runtime/effective-settings";
import { requestedTurnCollaborationModeSettings } from "../../src/features/chat/runtime/turn-settings";
import { readRuntimeConfig } from "../../src/features/chat/runtime/config";
import { contextSummary, effectiveConfigSections, rateLimitSummary } from "../../src/features/chat/runtime/status-summary";
import { runtimeConfigOrDefault } from "../../src/features/chat/runtime/config";
import { contextSummary, runtimeConfigSections, rateLimitSummary } from "../../src/features/chat/runtime/status-summary";
describe("runtime settings", () => {
it("parses model overrides", () => {
@ -117,7 +118,7 @@ describe("runtime settings", () => {
runtimeSnapshot({
requestedApprovalsReviewer: setPendingRuntimeSetting("user"),
activeApprovalsReviewer: "auto_review",
effectiveConfig: effectiveConfigFixture({ approvals_reviewer: "guardian_subagent" }),
runtimeConfig: runtimeConfigFixture({ approvals_reviewer: "guardian_subagent" }),
}),
),
).toBe("user");
@ -125,14 +126,14 @@ describe("runtime settings", () => {
currentApprovalsReviewer(
runtimeSnapshot({
activeApprovalsReviewer: "auto_review",
effectiveConfig: effectiveConfigFixture({ approvals_reviewer: "guardian_subagent" }),
runtimeConfig: runtimeConfigFixture({ approvals_reviewer: "guardian_subagent" }),
}),
),
).toBe("auto_review");
expect(
currentApprovalsReviewer(
runtimeSnapshot({
effectiveConfig: effectiveConfigFixture({ approvals_reviewer: "guardian_subagent" }),
runtimeConfig: runtimeConfigFixture({ approvals_reviewer: "guardian_subagent" }),
}),
),
).toBe("guardian_subagent");
@ -141,7 +142,7 @@ describe("runtime settings", () => {
it("uses the active reviewer before configured reviewer", () => {
const snapshot = runtimeSnapshot({
activeApprovalsReviewer: "user",
effectiveConfig: effectiveConfigFixture({ approvals_reviewer: "auto_review" }),
runtimeConfig: runtimeConfigFixture({ approvals_reviewer: "auto_review" }),
});
expect(currentApprovalsReviewer(snapshot)).toBe("user");
@ -161,7 +162,7 @@ describe("runtime settings", () => {
const snapshot = runtimeSnapshot({
requestedApprovalsReviewer: setPendingRuntimeSetting("user"),
activeApprovalsReviewer: "user",
effectiveConfig: effectiveConfigFixture({ approvals_reviewer: "auto_review" }),
runtimeConfig: runtimeConfigFixture({ approvals_reviewer: "auto_review" }),
});
expect(currentApprovalsReviewer(snapshot)).toBe("user");
@ -169,22 +170,22 @@ describe("runtime settings", () => {
});
it("uses effective approval reviewer values and reports selected profile metadata", () => {
const effectiveConfig = effectiveConfigFixture({ approvals_reviewer: "auto_review" }, [
const runtimeConfig = runtimeConfigFixture({ approvals_reviewer: "auto_review" }, [
configLayer({}, null),
configLayer({ approvals_reviewer: "auto_review" }, "auto"),
]);
const snapshot = runtimeSnapshot({
effectiveConfig,
runtimeConfig,
});
expect(readRuntimeConfig(effectiveConfig).profile).toBe("auto");
expect(runtimeConfigOrDefault(runtimeConfig).profile).toBe("auto");
expect(currentApprovalsReviewer(snapshot)).toBe("auto_review");
expect(autoReviewActive(snapshot)).toBe(true);
});
it("uses effective model, effort, and fast mode config values", () => {
const snapshot = runtimeSnapshot({
effectiveConfig: effectiveConfigFixture(
runtimeConfig: runtimeConfigFixture(
{
model: "gpt-profile",
model_reasoning_effort: "high",
@ -208,7 +209,7 @@ describe("runtime settings", () => {
it("uses active service tier before configured service tier", () => {
const snapshot = runtimeSnapshot({
activeServiceTier: "flex",
effectiveConfig: effectiveConfigFixture({ service_tier: "fast" }),
runtimeConfig: runtimeConfigFixture({ service_tier: "fast" }),
});
expect(currentServiceTier(snapshot)).toBe("flex");
@ -222,7 +223,7 @@ describe("runtime settings", () => {
const snapshot = runtimeSnapshot({
activeModel: "gpt-5.5",
activeServiceTier: "priority",
effectiveConfig: effectiveConfigFixture({ model: "gpt-5.5" }),
runtimeConfig: runtimeConfigFixture({ model: "gpt-5.5" }),
availableModels: [model],
});
@ -239,7 +240,7 @@ describe("runtime settings", () => {
const snapshot = runtimeSnapshot({
activeModel: "gpt-5.5",
activeServiceTier: "default",
effectiveConfig: effectiveConfigFixture({ model: "gpt-5.5" }),
runtimeConfig: runtimeConfigFixture({ model: "gpt-5.5" }),
availableModels: [model],
});
@ -253,7 +254,7 @@ describe("runtime settings", () => {
const snapshot = runtimeSnapshot({
requestedServiceTier: setPendingRuntimeSetting("off"),
activeServiceTier: "flex",
effectiveConfig: effectiveConfigFixture({ service_tier: "fast" }),
runtimeConfig: runtimeConfigFixture({ service_tier: "fast" }),
});
expect(currentServiceTier(snapshot)).toBeNull();
@ -272,7 +273,7 @@ describe("runtime settings", () => {
const snapshot = runtimeSnapshot({
activeModel: "gpt-5-active",
activeServiceTier: "fast",
effectiveConfig: effectiveConfigFixture({}),
runtimeConfig: runtimeConfigFixture({}),
});
expect(currentModel(snapshot)).toBe("gpt-5-active");
@ -288,7 +289,7 @@ describe("runtime settings", () => {
});
it("separates effective runtime, config defaults, and pending changes in status details", () => {
const sections = effectiveConfigSections(
const sections = runtimeConfigSections(
runtimeSnapshot({
activeModel: "gpt-5-active",
activeReasoningEffort: "low",
@ -318,7 +319,7 @@ describe("runtime settings", () => {
"requested service tier": "(none)",
});
const resetSections = effectiveConfigSections(
const resetSections = runtimeConfigSections(
runtimeSnapshot({
requestedReasoningEffort: resetRuntimeSettingToConfig(),
}),
@ -331,9 +332,9 @@ describe("runtime settings", () => {
});
it("labels unset config values as Codex defaults when no concrete value is reported", () => {
const sections = effectiveConfigSections(
const sections = runtimeConfigSections(
runtimeSnapshot({
effectiveConfig: effectiveConfigFixture({}),
runtimeConfig: runtimeConfigFixture({}),
}),
"/vault",
);
@ -371,9 +372,9 @@ describe("runtime settings", () => {
it("does not treat unknown catalog default reasoning efforts as runtime settings", () => {
const model = { ...modelFixture("gpt-catalog-default"), isDefault: true, defaultReasoningEffort: "extreme" };
const sections = effectiveConfigSections(
const sections = runtimeConfigSections(
runtimeSnapshot({
effectiveConfig: effectiveConfigFixture({}),
runtimeConfig: runtimeConfigFixture({}),
availableModels: [model],
}),
"/vault",
@ -398,9 +399,9 @@ describe("runtime settings", () => {
web_search: "live",
service_tier: "fast",
};
const sections = effectiveConfigSections(
const sections = runtimeConfigSections(
runtimeSnapshot({
effectiveConfig: effectiveConfigFixture(profileConfig, [configLayer({}, null), configLayer(profileConfig, "auto")]),
runtimeConfig: runtimeConfigFixture(profileConfig, [configLayer({}, null), configLayer(profileConfig, "auto")]),
}),
"/vault",
);
@ -446,12 +447,12 @@ describe("runtime settings", () => {
});
it("shows active and configured reviewer inputs in status details", () => {
const sections = effectiveConfigSections(
const sections = runtimeConfigSections(
runtimeSnapshot({
activeApprovalsReviewer: "guardian_subagent",
activeApprovalPolicy: "never",
activePermissionProfile: { id: ":workspace", extends: ":default" },
effectiveConfig: effectiveConfigFixture({ approvals_reviewer: "auto_review" }),
runtimeConfig: runtimeConfigFixture({ approvals_reviewer: "auto_review" }),
}),
"/vault",
);
@ -510,7 +511,7 @@ describe("runtime settings", () => {
it("serializes explicit fast off as a null service tier request", () => {
const snapshot = runtimeSnapshot({
effectiveConfig: effectiveConfigFixture({ service_tier: "fast" }),
runtimeConfig: runtimeConfigFixture({ service_tier: "fast" }),
requestedServiceTier: setPendingRuntimeSetting("off"),
});
@ -524,7 +525,7 @@ describe("runtime settings", () => {
model.serviceTiers = [{ id: "priority", name: "Fast" }];
const snapshot = runtimeSnapshot({
requestedServiceTier: setPendingRuntimeSetting("fast"),
effectiveConfig: effectiveConfigFixture({ model: "gpt-5.5" }),
runtimeConfig: runtimeConfigFixture({ model: "gpt-5.5" }),
availableModels: [model],
});
@ -533,11 +534,11 @@ describe("runtime settings", () => {
});
it("omits service tier when neither config nor override selects one", () => {
expect(requestedOrConfiguredServiceTier(runtimeSnapshot({ effectiveConfig: effectiveConfigFixture({}) }))).toBeUndefined();
expect(requestedOrConfiguredServiceTier(runtimeSnapshot({ runtimeConfig: runtimeConfigFixture({}) }))).toBeUndefined();
});
it("passes through configured non-fast service tier ids", () => {
const snapshot = runtimeSnapshot({ effectiveConfig: effectiveConfigFixture({ service_tier: "flex" }) });
const snapshot = runtimeSnapshot({ runtimeConfig: runtimeConfigFixture({ service_tier: "flex" }) });
expect(serviceTierLabel(snapshot)).toBe("flex");
expect(fastModeLabel(snapshot)).toBe("off");
@ -644,7 +645,7 @@ describe("runtime settings", () => {
function runtimeSnapshot(overrides: Partial<RuntimeSnapshot> = {}): RuntimeSnapshot {
return {
effectiveConfig: effectiveConfigFixture({
runtimeConfig: runtimeConfigFixture({
model: "gpt-5.5",
model_reasoning_effort: "high",
service_tier: "flex",
@ -671,12 +672,12 @@ function runtimeSnapshot(overrides: Partial<RuntimeSnapshot> = {}): RuntimeSnaps
};
}
function effectiveConfigFixture(config: Record<string, unknown>, layers: ConfigReadResponse["layers"] = null): ConfigReadResponse {
return {
function runtimeConfigFixture(config: Record<string, unknown>, layers: ConfigReadResponse["layers"] = null): RuntimeConfigSnapshot {
return runtimeConfigSnapshotFromAppServerConfig({
config: config as ConfigReadResponse["config"],
origins: {},
layers,
};
});
}
function configLayer(config: Record<string, unknown>, profile: string | null): NonNullable<ConfigReadResponse["layers"]>[number] {