Finish backlog cleanup for Pi integration
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
import { registerAlphaTools } from "./research-tools/alpha.js";
|
||||
import { registerDiscoveryCommands } from "./research-tools/discovery.js";
|
||||
import { registerFeynmanModelCommand } from "./research-tools/feynman-model.js";
|
||||
import { installFeynmanHeader } from "./research-tools/header.js";
|
||||
import { registerHelpCommand } from "./research-tools/help.js";
|
||||
import { registerInitCommand, registerOutputsCommand } from "./research-tools/project.js";
|
||||
import { registerServiceTierControls } from "./research-tools/service-tier.js";
|
||||
|
||||
export default function researchTools(pi: ExtensionAPI): void {
|
||||
const cache: { agentSummaryPromise?: Promise<{ agents: string[]; chains: string[] }> } = {};
|
||||
@@ -18,8 +20,10 @@ export default function researchTools(pi: ExtensionAPI): void {
|
||||
});
|
||||
|
||||
registerAlphaTools(pi);
|
||||
registerDiscoveryCommands(pi);
|
||||
registerFeynmanModelCommand(pi);
|
||||
registerHelpCommand(pi);
|
||||
registerInitCommand(pi);
|
||||
registerOutputsCommand(pi);
|
||||
registerServiceTierControls(pi);
|
||||
}
|
||||
|
||||
130
extensions/research-tools/discovery.ts
Normal file
130
extensions/research-tools/discovery.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import type { ExtensionAPI, SlashCommandInfo, ToolInfo } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
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 readConfiguredPackages(): string[] {
|
||||
const settingsPath = resolveFeynmanSettingsPath();
|
||||
if (!existsSync(settingsPath)) return [];
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(settingsPath, "utf8")) as { packages?: unknown[] };
|
||||
return Array.isArray(parsed.packages)
|
||||
? parsed.packages
|
||||
.map((entry) => {
|
||||
if (typeof entry === "string") return entry;
|
||||
if (!entry || typeof entry !== "object") return undefined;
|
||||
const record = entry as { source?: unknown };
|
||||
return typeof record.source === "string" ? record.source : undefined;
|
||||
})
|
||||
.filter((entry): entry is string => Boolean(entry))
|
||||
: [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function formatSourceLabel(sourceInfo: { source: string; path: string }): string {
|
||||
if (sourceInfo.source === "local") {
|
||||
if (sourceInfo.path.includes("/prompts/")) return "workflow";
|
||||
if (sourceInfo.path.includes("/extensions/")) return "extension";
|
||||
return "local";
|
||||
}
|
||||
return sourceInfo.source.replace(/^npm:/, "").replace(/^git:/, "");
|
||||
}
|
||||
|
||||
function formatCommandLine(command: SlashCommandInfo): string {
|
||||
const source = formatSourceLabel(command.sourceInfo);
|
||||
return `/${command.name} — ${command.description ?? ""} [${source}]`;
|
||||
}
|
||||
|
||||
function summarizeToolParameters(tool: ToolInfo): string {
|
||||
const properties =
|
||||
tool.parameters &&
|
||||
typeof tool.parameters === "object" &&
|
||||
"properties" in tool.parameters &&
|
||||
tool.parameters.properties &&
|
||||
typeof tool.parameters.properties === "object"
|
||||
? Object.keys(tool.parameters.properties as Record<string, unknown>)
|
||||
: [];
|
||||
return properties.length > 0 ? properties.join(", ") : "no parameters";
|
||||
}
|
||||
|
||||
function formatToolLine(tool: ToolInfo): string {
|
||||
const source = formatSourceLabel(tool.sourceInfo);
|
||||
return `${tool.name} — ${tool.description ?? ""} [${source}]`;
|
||||
}
|
||||
|
||||
export function registerDiscoveryCommands(pi: ExtensionAPI): void {
|
||||
pi.registerCommand("commands", {
|
||||
description: "Browse all available slash commands, including package and built-in commands.",
|
||||
handler: async (_args, ctx) => {
|
||||
const commands = pi
|
||||
.getCommands()
|
||||
.slice()
|
||||
.sort((left, right) => left.name.localeCompare(right.name));
|
||||
const items = commands.map((command) => formatCommandLine(command));
|
||||
const selected = await ctx.ui.select("Slash Commands", items);
|
||||
if (!selected) return;
|
||||
ctx.ui.setEditorText(selected.split(" — ")[0] ?? "");
|
||||
ctx.ui.notify(`Prefilled ${selected.split(" — ")[0]}`, "info");
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerCommand("tools", {
|
||||
description: "Browse all callable tools with their source and parameter summary.",
|
||||
handler: async (_args, ctx) => {
|
||||
const tools = pi
|
||||
.getAllTools()
|
||||
.slice()
|
||||
.sort((left, right) => left.name.localeCompare(right.name));
|
||||
const selected = await ctx.ui.select("Tools", tools.map((tool) => formatToolLine(tool)));
|
||||
if (!selected) return;
|
||||
|
||||
const toolName = selected.split(" — ")[0] ?? selected;
|
||||
const tool = tools.find((entry) => entry.name === toolName);
|
||||
if (!tool) return;
|
||||
ctx.ui.notify(`${tool.name}: ${summarizeToolParameters(tool)}`, "info");
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerCommand("capabilities", {
|
||||
description: "Show installed packages, discovery entrypoints, and high-level runtime capability counts.",
|
||||
handler: async (_args, ctx) => {
|
||||
const commands = pi.getCommands();
|
||||
const tools = pi.getAllTools();
|
||||
const workflows = commands.filter((command) => formatSourceLabel(command.sourceInfo) === "workflow");
|
||||
const packages = readConfiguredPackages();
|
||||
const items = [
|
||||
`Commands: ${commands.length}`,
|
||||
`Workflows: ${workflows.length}`,
|
||||
`Tools: ${tools.length}`,
|
||||
`Packages: ${packages.length}`,
|
||||
"--- Discovery ---",
|
||||
"/commands — browse slash commands",
|
||||
"/tools — inspect callable tools",
|
||||
"/hotkeys — view keyboard shortcuts",
|
||||
"/service-tier — set request tier for supported providers",
|
||||
"--- Installed Packages ---",
|
||||
...packages.map((pkg) => pkg),
|
||||
];
|
||||
const selected = await ctx.ui.select("Capabilities", items);
|
||||
if (!selected || selected.startsWith("---")) return;
|
||||
if (selected.startsWith("/")) {
|
||||
ctx.ui.setEditorText(selected.split(" — ")[0] ?? selected);
|
||||
ctx.ui.notify(`Prefilled ${selected.split(" — ")[0]}`, "info");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
174
extensions/research-tools/service-tier.ts
Normal file
174
extensions/research-tools/service-tier.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
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");
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user