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
78 lines
2.9 KiB
TypeScript
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() ?? "");
|
|
}
|