175 lines
5.3 KiB
TypeScript
175 lines
5.3 KiB
TypeScript
import { homedir } from "node:os";
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
|
|
const FEYNMAN_SERVICE_TIERS = [
|
|
"auto",
|
|
"default",
|
|
"flex",
|
|
"priority",
|
|
"standard_only",
|
|
] as const;
|
|
|
|
type FeynmanServiceTier = (typeof FEYNMAN_SERVICE_TIERS)[number];
|
|
|
|
const SERVICE_TIER_SET = new Set<string>(FEYNMAN_SERVICE_TIERS);
|
|
const OPENAI_SERVICE_TIERS = new Set<FeynmanServiceTier>(["auto", "default", "flex", "priority"]);
|
|
const ANTHROPIC_SERVICE_TIERS = new Set<FeynmanServiceTier>(["auto", "standard_only"]);
|
|
|
|
type CommandContext = Parameters<Parameters<ExtensionAPI["registerCommand"]>[1]["handler"]>[1];
|
|
|
|
type SelectOption<T> = {
|
|
label: string;
|
|
value: T;
|
|
};
|
|
|
|
function resolveFeynmanSettingsPath(): string {
|
|
const configured = process.env.PI_CODING_AGENT_DIR?.trim();
|
|
const agentDir = configured
|
|
? configured.startsWith("~/")
|
|
? resolve(homedir(), configured.slice(2))
|
|
: resolve(configured)
|
|
: resolve(homedir(), ".feynman", "agent");
|
|
return resolve(agentDir, "settings.json");
|
|
}
|
|
|
|
function normalizeServiceTier(value: string | undefined): FeynmanServiceTier | undefined {
|
|
if (!value) return undefined;
|
|
const normalized = value.trim().toLowerCase();
|
|
return SERVICE_TIER_SET.has(normalized) ? (normalized as FeynmanServiceTier) : undefined;
|
|
}
|
|
|
|
function getConfiguredServiceTier(settingsPath: string): FeynmanServiceTier | undefined {
|
|
try {
|
|
const parsed = JSON.parse(readFileSync(settingsPath, "utf8")) as { serviceTier?: string };
|
|
return normalizeServiceTier(parsed.serviceTier);
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function setConfiguredServiceTier(settingsPath: string, tier: FeynmanServiceTier | undefined): void {
|
|
let settings: Record<string, unknown> = {};
|
|
try {
|
|
settings = JSON.parse(readFileSync(settingsPath, "utf8")) as Record<string, unknown>;
|
|
} catch {}
|
|
|
|
if (tier) {
|
|
settings.serviceTier = tier;
|
|
} else {
|
|
delete settings.serviceTier;
|
|
}
|
|
|
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
}
|
|
|
|
function resolveActiveServiceTier(settingsPath: string): FeynmanServiceTier | undefined {
|
|
return normalizeServiceTier(process.env.FEYNMAN_SERVICE_TIER) ?? getConfiguredServiceTier(settingsPath);
|
|
}
|
|
|
|
function resolveProviderServiceTier(
|
|
provider: string | undefined,
|
|
tier: FeynmanServiceTier | undefined,
|
|
): FeynmanServiceTier | undefined {
|
|
if (!provider || !tier) return undefined;
|
|
if ((provider === "openai" || provider === "openai-codex") && OPENAI_SERVICE_TIERS.has(tier)) {
|
|
return tier;
|
|
}
|
|
if (provider === "anthropic" && ANTHROPIC_SERVICE_TIERS.has(tier)) {
|
|
return tier;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
async function selectOption<T>(
|
|
ctx: CommandContext,
|
|
title: string,
|
|
options: SelectOption<T>[],
|
|
): Promise<T | undefined> {
|
|
const selected = await ctx.ui.select(
|
|
title,
|
|
options.map((option) => option.label),
|
|
);
|
|
if (!selected) return undefined;
|
|
return options.find((option) => option.label === selected)?.value;
|
|
}
|
|
|
|
function parseRequestedTier(rawArgs: string): FeynmanServiceTier | null | undefined {
|
|
const trimmed = rawArgs.trim();
|
|
if (!trimmed) return undefined;
|
|
if (trimmed === "unset" || trimmed === "clear" || trimmed === "off") return null;
|
|
return normalizeServiceTier(trimmed);
|
|
}
|
|
|
|
export function registerServiceTierControls(pi: ExtensionAPI): void {
|
|
pi.on("before_provider_request", (event, ctx) => {
|
|
if (!ctx.model || !event.payload || typeof event.payload !== "object") {
|
|
return;
|
|
}
|
|
|
|
const activeTier = resolveActiveServiceTier(resolveFeynmanSettingsPath());
|
|
const providerTier = resolveProviderServiceTier(ctx.model.provider, activeTier);
|
|
if (!providerTier) {
|
|
return;
|
|
}
|
|
|
|
return {
|
|
...(event.payload as Record<string, unknown>),
|
|
service_tier: providerTier,
|
|
};
|
|
});
|
|
|
|
pi.registerCommand("service-tier", {
|
|
description: "View or set the provider service tier override used for supported models.",
|
|
handler: async (args, ctx) => {
|
|
const settingsPath = resolveFeynmanSettingsPath();
|
|
const requested = parseRequestedTier(args);
|
|
|
|
if (requested === undefined && !args.trim()) {
|
|
if (!ctx.hasUI) {
|
|
ctx.ui.notify(getConfiguredServiceTier(settingsPath) ?? "not set", "info");
|
|
return;
|
|
}
|
|
|
|
const current = getConfiguredServiceTier(settingsPath);
|
|
const selected = await selectOption(
|
|
ctx,
|
|
"Select service tier",
|
|
[
|
|
{ label: current ? `unset (current: ${current})` : "unset (current)", value: null },
|
|
...FEYNMAN_SERVICE_TIERS.map((tier) => ({
|
|
label: tier === current ? `${tier} (current)` : tier,
|
|
value: tier,
|
|
})),
|
|
],
|
|
);
|
|
if (selected === undefined) return;
|
|
if (selected === null) {
|
|
setConfiguredServiceTier(settingsPath, undefined);
|
|
ctx.ui.notify("Cleared service tier override.", "info");
|
|
return;
|
|
}
|
|
setConfiguredServiceTier(settingsPath, selected);
|
|
ctx.ui.notify(`Service tier set to ${selected}.`, "info");
|
|
return;
|
|
}
|
|
|
|
if (requested === null) {
|
|
setConfiguredServiceTier(settingsPath, undefined);
|
|
ctx.ui.notify("Cleared service tier override.", "info");
|
|
return;
|
|
}
|
|
|
|
if (!requested) {
|
|
ctx.ui.notify("Use auto, default, flex, priority, standard_only, or unset.", "error");
|
|
return;
|
|
}
|
|
|
|
setConfiguredServiceTier(settingsPath, requested);
|
|
ctx.ui.notify(`Service tier set to ${requested}.`, "info");
|
|
},
|
|
});
|
|
}
|