Use Pi runtime hooks for research context hygiene

This commit is contained in:
Advait Paliwal
2026-04-17 10:38:42 -07:00
parent 9841342866
commit f0bbb25910
18 changed files with 480 additions and 25 deletions

View File

@@ -127,6 +127,19 @@ export function normalizeFeynmanSettings(
settings.theme = "feynman";
settings.quietStartup = true;
settings.collapseChangelog = true;
settings.compaction = {
enabled: true,
reserveTokens: 16384,
keepRecentTokens: 20000,
...(settings.compaction && typeof settings.compaction === "object" ? settings.compaction : {}),
};
settings.retry = {
enabled: true,
maxRetries: 3,
baseDelayMs: 2000,
maxDelayMs: 60000,
...(settings.retry && typeof settings.retry === "object" ? settings.retry : {}),
};
const supportedCorePackages = filterPackageSourcesForCurrentNode(CORE_PACKAGE_SOURCES);
if (!Array.isArray(settings.packages) || settings.packages.length === 0) {
settings.packages = supportedCorePackages;

View File

@@ -12,6 +12,11 @@ import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSuppo
import { createModelRegistry, getModelsJsonPath } from "../model/registry.js";
import { getConfiguredServiceTier } from "../model/service-tier.js";
type ContextRiskSummary = {
level: "low" | "medium" | "high" | "unknown";
lines: string[];
};
function findProvidersMissingApiKey(modelsJsonPath: string): string[] {
try {
const raw = readFileSync(modelsJsonPath, "utf8").trim();
@@ -35,6 +40,50 @@ function findProvidersMissingApiKey(modelsJsonPath: string): string[] {
}
}
function numberSetting(settings: Record<string, unknown>, path: string[], fallback: number): number {
let value: unknown = settings;
for (const key of path) {
if (!value || typeof value !== "object") return fallback;
value = (value as Record<string, unknown>)[key];
}
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
}
export function buildContextRiskSummary(
settings: Record<string, unknown>,
model: { provider: string; id: string; contextWindow: number; maxTokens: number; reasoning: boolean } | undefined,
): ContextRiskSummary {
if (!model) {
return {
level: "unknown",
lines: ["context risk: unknown (no active model)"],
};
}
const reserveTokens = numberSetting(settings, ["compaction", "reserveTokens"], 16384);
const keepRecentTokens = numberSetting(settings, ["compaction", "keepRecentTokens"], 20000);
const retryMax = numberSetting(settings, ["retry", "maxRetries"], 3);
const usableWindow = Math.max(0, model.contextWindow - reserveTokens);
const level = model.contextWindow < 64_000
? "high"
: model.contextWindow < 128_000
? "medium"
: "low";
return {
level,
lines: [
`context risk: ${level}`,
` model: ${model.provider}/${model.id}`,
` context window: ${model.contextWindow}`,
` usable before Pi compaction reserve: ${usableWindow}`,
` Pi compaction: reserve=${reserveTokens}, keepRecent=${keepRecentTokens}`,
` Pi retry: maxRetries=${retryMax}`,
` reasoning: ${model.reasoning ? "supported" : "off/not supported"}`,
],
};
}
export type DoctorOptions = {
settingsPath: string;
authPath: string;
@@ -164,6 +213,10 @@ export function runDoctor(options: DoctorOptions): void {
: "not set"}`,
);
const modelStatus = collectStatusSnapshot(options);
const currentModel = typeof settings.defaultProvider === "string" && typeof settings.defaultModel === "string"
? modelRegistry.find(settings.defaultProvider, settings.defaultModel)
: undefined;
const contextRisk = buildContextRiskSummary(settings, currentModel);
console.log(`default model valid: ${modelStatus.modelValid ? "yes" : "no"}`);
console.log(`authenticated providers: ${modelStatus.authenticatedProviderCount}`);
console.log(`authenticated models: ${modelStatus.authenticatedModelCount}`);
@@ -172,6 +225,9 @@ export function runDoctor(options: DoctorOptions): void {
if (modelStatus.recommendedModelReason) {
console.log(` why: ${modelStatus.recommendedModelReason}`);
}
for (const line of contextRisk.lines) {
console.log(line);
}
const modelsError = modelRegistry.getError();
if (modelsError) {
console.log("models.json: error");