export type CanvasAgentWritebackMode = | "legacy_canvas_media" | "operation_shadow" | "operation_authoritative"; export type CanvasAgentFeatureFlags = { operationProtocolEnabled: boolean; writebackMode: CanvasAgentWritebackMode; slotEnabled: boolean; viewOperationEnabled: boolean; annotationEditEnabled: boolean; }; export type CanvasAgentFeatureSettings = CanvasAgentFeatureFlags; export const defaultCanvasAgentFeatureSettings: CanvasAgentFeatureSettings = { operationProtocolEnabled: true, writebackMode: "operation_authoritative", slotEnabled: true, viewOperationEnabled: true, annotationEditEnabled: true, }; export function normalizeCanvasAgentFeatureSettings( value: unknown, fallback: CanvasAgentFeatureSettings = defaultCanvasAgentFeatureSettings ): CanvasAgentFeatureSettings { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Partial) : {}; return { operationProtocolEnabled: normalizeCanvasAgentBoolean( raw.operationProtocolEnabled, fallback.operationProtocolEnabled ), writebackMode: normalizeCanvasAgentWritebackMode( raw.writebackMode, fallback.writebackMode ), slotEnabled: normalizeCanvasAgentBoolean(raw.slotEnabled, fallback.slotEnabled), viewOperationEnabled: normalizeCanvasAgentBoolean( raw.viewOperationEnabled, fallback.viewOperationEnabled ), annotationEditEnabled: normalizeCanvasAgentBoolean( raw.annotationEditEnabled, fallback.annotationEditEnabled ), }; } export const CANVAS_AGENT_OPERATION_PROTOCOL_ENABLED_KEY = "canvas_agent_operation_protocol_enabled"; export const CANVAS_AGENT_WRITEBACK_MODE_KEY = "canvas_agent_writeback_mode"; export const CANVAS_AGENT_SLOT_ENABLED_KEY = "canvas_agent_slot_enabled"; export const CANVAS_AGENT_VIEW_OPERATION_ENABLED_KEY = "canvas_agent_view_operation_enabled"; export const CANVAS_AGENT_ANNOTATION_EDIT_ENABLED_KEY = "canvas_agent_annotation_edit_enabled"; export const CANVAS_AGENT_FEATURE_SETTING_KEYS = [ CANVAS_AGENT_OPERATION_PROTOCOL_ENABLED_KEY, CANVAS_AGENT_WRITEBACK_MODE_KEY, CANVAS_AGENT_SLOT_ENABLED_KEY, CANVAS_AGENT_VIEW_OPERATION_ENABLED_KEY, CANVAS_AGENT_ANNOTATION_EDIT_ENABLED_KEY, ] as const; type EnvLike = Record; export type CanvasAgentFeatureSettingKey = (typeof CANVAS_AGENT_FEATURE_SETTING_KEYS)[number]; export type CanvasAgentFeatureSettingsLike = Partial< Record >; const WRITEBACK_MODES = new Set([ "legacy_canvas_media", "operation_shadow", "operation_authoritative", ]); export function normalizeCanvasAgentBoolean( value: unknown, fallback: boolean ) { if (typeof value === "boolean") return value; if (typeof value === "number" && Number.isFinite(value)) return value !== 0; if (typeof value !== "string") return fallback; const normalized = value.trim().toLowerCase(); if (!normalized) return fallback; if (["1", "true", "yes", "on", "enabled"].includes(normalized)) return true; if (["0", "false", "no", "off", "disabled"].includes(normalized)) return false; return fallback; } export function normalizeCanvasAgentWritebackMode( value: unknown, fallback: CanvasAgentWritebackMode = "operation_authoritative" ): CanvasAgentWritebackMode { const normalized = typeof value === "string" ? value.trim() : ""; return WRITEBACK_MODES.has(normalized as CanvasAgentWritebackMode) ? (normalized as CanvasAgentWritebackMode) : fallback; } export function mergeCanvasAgentFeatureFlagsWithSettings( flags: CanvasAgentFeatureFlags, settings?: CanvasAgentFeatureSettingsLike | null ): CanvasAgentFeatureFlags { if (!settings) return flags; return { operationProtocolEnabled: normalizeCanvasAgentBoolean( settings[CANVAS_AGENT_OPERATION_PROTOCOL_ENABLED_KEY], flags.operationProtocolEnabled ), writebackMode: normalizeCanvasAgentWritebackMode( settings[CANVAS_AGENT_WRITEBACK_MODE_KEY], flags.writebackMode ), slotEnabled: normalizeCanvasAgentBoolean( settings[CANVAS_AGENT_SLOT_ENABLED_KEY], flags.slotEnabled ), viewOperationEnabled: normalizeCanvasAgentBoolean( settings[CANVAS_AGENT_VIEW_OPERATION_ENABLED_KEY], flags.viewOperationEnabled ), annotationEditEnabled: normalizeCanvasAgentBoolean( settings[CANVAS_AGENT_ANNOTATION_EDIT_ENABLED_KEY], flags.annotationEditEnabled ), }; } export function getCanvasAgentFeatureFlags( env: EnvLike = process.env, settings?: CanvasAgentFeatureSettingsLike | null ): CanvasAgentFeatureFlags { const envFlags: CanvasAgentFeatureFlags = { operationProtocolEnabled: normalizeCanvasAgentBoolean( env.CANVAS_AGENT_OPERATION_PROTOCOL_ENABLED, true ), writebackMode: normalizeCanvasAgentWritebackMode( env.CANVAS_AGENT_WRITEBACK_MODE, "operation_authoritative" ), slotEnabled: normalizeCanvasAgentBoolean( env.CANVAS_AGENT_SLOT_ENABLED, true ), viewOperationEnabled: normalizeCanvasAgentBoolean( env.CANVAS_AGENT_VIEW_OPERATION_ENABLED, true ), annotationEditEnabled: normalizeCanvasAgentBoolean( env.CANVAS_AGENT_ANNOTATION_EDIT_ENABLED, true ), }; return mergeCanvasAgentFeatureFlagsWithSettings(envFlags, settings); } export function canvasAgentFeatureFlagsToSettings( flags: CanvasAgentFeatureFlags ): CanvasAgentFeatureSettings { return { operationProtocolEnabled: flags.operationProtocolEnabled, writebackMode: flags.writebackMode, slotEnabled: flags.slotEnabled, viewOperationEnabled: flags.viewOperationEnabled, annotationEditEnabled: flags.annotationEditEnabled, }; } export function canvasAgentFeatureSettingsToSystemSettings( settings: CanvasAgentFeatureSettings ): CanvasAgentFeatureSettingsLike { return { [CANVAS_AGENT_OPERATION_PROTOCOL_ENABLED_KEY]: settings.operationProtocolEnabled, [CANVAS_AGENT_WRITEBACK_MODE_KEY]: settings.writebackMode, [CANVAS_AGENT_SLOT_ENABLED_KEY]: settings.slotEnabled, [CANVAS_AGENT_VIEW_OPERATION_ENABLED_KEY]: settings.viewOperationEnabled, [CANVAS_AGENT_ANNOTATION_EDIT_ENABLED_KEY]: settings.annotationEditEnabled, }; } export function canApplyCanvasAgentMutations(flags: CanvasAgentFeatureFlags) { return ( flags.operationProtocolEnabled && flags.writebackMode === "operation_authoritative" ); } export function shouldStreamCanvasAgentShadowOperations( flags: CanvasAgentFeatureFlags ) { return flags.operationProtocolEnabled && flags.writebackMode === "operation_shadow"; }