export type CanvasAgentWritebackMode = | "legacy_canvas_media" | "operation_shadow" | "operation_authoritative"; export type CanvasAgentFeatureFlags = { operationProtocolEnabled: boolean; writebackMode: CanvasAgentWritebackMode; slotEnabled: boolean; viewOperationEnabled: boolean; annotationEditEnabled: boolean; }; 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 canApplyCanvasAgentMutations(flags: CanvasAgentFeatureFlags) { return ( flags.operationProtocolEnabled && flags.writebackMode === "operation_authoritative" ); } export function shouldStreamCanvasAgentShadowOperations( flags: CanvasAgentFeatureFlags ) { return flags.operationProtocolEnabled && flags.writebackMode === "operation_shadow"; }