Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

126 lines
3.4 KiB
TypeScript

/**
* UI-local chrome state for the composer model menu: per-agent-target recent
* model picks and favorite toggles, persisted in browser localStorage (same
* pattern as agent rail ordering). This never enters controller state, session
* state, or durable AgentGUI node data.
*/
const COMPOSER_MODEL_RECENTS_STORAGE_PREFIX =
"agent-gui:composer-model-recents:";
const COMPOSER_MODEL_FAVORITES_STORAGE_PREFIX =
"agent-gui:composer-model-favorites:";
export const MAX_RECENT_COMPOSER_MODELS = 5;
export type ComposerModelHistoryVerdict =
| "verified"
| "rejected"
| "unverifiable";
export function composerModelRecentsStorageKey(
agentTargetId: string | null | undefined
): string {
return `${COMPOSER_MODEL_RECENTS_STORAGE_PREFIX}${normalizeTargetId(agentTargetId)}`;
}
export function composerModelFavoritesStorageKey(
agentTargetId: string | null | undefined
): string {
return `${COMPOSER_MODEL_FAVORITES_STORAGE_PREFIX}${normalizeTargetId(agentTargetId)}`;
}
export function parseComposerModelIdList(
rawValue: string | null | undefined
): readonly string[] {
if (!rawValue) {
return [];
}
try {
const parsed = JSON.parse(rawValue);
if (!Array.isArray(parsed)) {
return [];
}
return sanitizeComposerModelIdList(parsed);
} catch {
return [];
}
}
export function serializeComposerModelIdList(
modelIds: readonly string[]
): string {
return JSON.stringify(sanitizeComposerModelIdList(modelIds));
}
export function sanitizeComposerModelIdList(
modelIds: readonly unknown[]
): readonly string[] {
const seen = new Set<string>();
const sanitized: string[] = [];
for (const value of modelIds) {
if (typeof value !== "string") {
continue;
}
const modelId = value.trim();
if (!modelId || seen.has(modelId)) {
continue;
}
seen.add(modelId);
sanitized.push(modelId);
}
return sanitized;
}
/** Most-recent-first, deduplicated, capped at MAX_RECENT_COMPOSER_MODELS. */
export function recordRecentComposerModel(
currentRecentIds: readonly string[],
modelId: string
): readonly string[] {
const normalized = modelId.trim();
if (!normalized) {
return sanitizeComposerModelIdList(currentRecentIds);
}
return sanitizeComposerModelIdList([normalized, ...currentRecentIds]).slice(
0,
MAX_RECENT_COMPOSER_MODELS
);
}
/**
* Remove only models that the current catalog positively rejects. An
* unverifiable window must preserve history until stronger testimony arrives.
*/
export function reconcileRecentComposerModels(
currentRecentIds: readonly string[],
verdictForModel: (modelId: string) => ComposerModelHistoryVerdict
): readonly string[] {
return sanitizeComposerModelIdList(currentRecentIds).filter(
(modelId) => verdictForModel(modelId) !== "rejected"
);
}
export function normalizeComposerModelHistoryTargetId(
agentTargetId: string | null | undefined
): string | null {
return agentTargetId?.trim() || null;
}
export function toggleFavoriteComposerModel(
currentFavoriteIds: readonly string[],
modelId: string
): readonly string[] {
const normalized = modelId.trim();
const sanitized = sanitizeComposerModelIdList(currentFavoriteIds);
if (!normalized) {
return sanitized;
}
if (sanitized.includes(normalized)) {
return sanitized.filter((value) => value !== normalized);
}
return [...sanitized, normalized];
}
function normalizeTargetId(agentTargetId: string | null | undefined): string {
return agentTargetId?.trim() || "default";
}