ai-chatbot/lib/canvas/agent-feature-flags.ts

205 lines
6.6 KiB
TypeScript

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<CanvasAgentFeatureSettings>)
: {};
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<string, string | undefined>;
export type CanvasAgentFeatureSettingKey =
(typeof CANVAS_AGENT_FEATURE_SETTING_KEYS)[number];
export type CanvasAgentFeatureSettingsLike = Partial<
Record<CanvasAgentFeatureSettingKey, unknown>
>;
const WRITEBACK_MODES = new Set<CanvasAgentWritebackMode>([
"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";
}