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

78 lines
2.9 KiB
TypeScript

import type { AgentActivityModelPlanSummary } from "@tutti-os/agent-activity-core";
import type { AgentSessionComposerSettings } from "../../../shared/agentSessionTypes";
import type { AgentGUIComposerSettingOption } from "./agentGuiNodeTypes";
export interface AggregatedModelCopy {
effectNewSession: string;
effectNextCall: string;
}
/**
* Aggregate the workspace's compatible model access plans into composer model
* options. A plan contributes its models when it is enabled, past detection
* (ready or pending first use), and speaks the selected provider's protocol.
* This branch's plan summaries carry no billing/tier metadata yet, so option
* descriptions stay at plan name plus switch effect.
*/
export function aggregateCompatibleModelPlanOptions(input: {
activeSession: boolean;
copy: AggregatedModelCopy;
currentModelPlanId?: string | null;
plans: readonly AgentActivityModelPlanSummary[];
protocol: string;
}): AgentGUIComposerSettingOption[] {
const protocol = input.protocol.trim().toLowerCase();
if (!protocol) return [];
const currentModelPlanId = input.currentModelPlanId?.trim() ?? "";
return input.plans.flatMap((plan) => {
const available =
plan.enabled &&
(plan.status === "ready" || plan.status === "pending_first_use") &&
plan.protocol.trim().toLowerCase() === protocol;
if (!available) return [];
return plan.models.map((model) => {
const effect =
input.activeSession && currentModelPlanId !== plan.id.trim()
? ("new_session" as const)
: ("next_call" as const);
const metadata = [
plan.name.trim(),
effect === "new_session"
? input.copy.effectNewSession
: input.copy.effectNextCall
];
return {
value: modelPlanSelectionValue(plan.id, model.id),
effect,
label: model.name.trim() || model.id.trim(),
modelPlanId: plan.id,
sourceName: plan.name,
description: metadata.filter(Boolean).join(" · ")
};
});
});
}
export function modelPlanSelectionValue(
planId: string,
modelId: string
): string {
return `model-plan:${encodeURIComponent(planId.trim())}:${encodeURIComponent(modelId.trim())}`;
}
export function composerModelPlanRequiresNewSession(input: {
activeSettings: AgentSessionComposerSettings;
draftSettings: AgentSessionComposerSettings | null;
}): boolean {
// Staged plan decisions are always explicit: a plan id, or null for a
// native selection. Both directions across the plan boundary change the
// session's endpoint, so leaving a plan (explicit null on a plan session)
// requires a new session just like switching plans. An absent key means no
// plan decision was staged at all.
if (!input.draftSettings || input.draftSettings.modelPlanId === undefined) {
return false;
}
const draftModelPlanID = input.draftSettings.modelPlanId?.trim() ?? "";
return draftModelPlanID !== (input.activeSettings.modelPlanId?.trim() ?? "");
}