feat: add codex fast mode and harden subagent launch

This commit is contained in:
luckyyzh
2026-08-05 03:24:17 +08:00
parent a196c3a6b1
commit 836042b97c
12 changed files with 5950 additions and 3893 deletions
+146 -89
View File
@@ -1,123 +1,180 @@
import { stat } from "fs/promises";
import { resolve } from "path";
import { createAgentSessionServices, getAgentDir, type SettingsManager } from "@earendil-works/pi-coding-agent";
import {
createAgentSessionServices,
getAgentDir,
type SettingsManager,
} from "@earendil-works/pi-coding-agent";
import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
import { loadModelsWithCache, withModelRuntimeError, type ModelsData } from "@/lib/models-cache";
import { getAllowedFileRoots, isExistingFilePathAllowed } from "@/lib/file-access";
import {
loadModelsWithCache,
withModelRuntimeError,
type ModelsData,
} from "@/lib/models-cache";
import {
getAllowedFileRoots,
isExistingFilePathAllowed,
} from "@/lib/file-access";
import { projectTrustReloadOptions } from "@/lib/project-trust";
import { createAppSettingsManager, getAppResourceLoaderOptions } from "@/lib/app-runtime";
import {
createAppSettingsManager,
getAppResourceLoaderOptions,
} from "@/lib/app-runtime";
export const dynamic = "force-dynamic";
const modelNameCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
const modelNameCollator = new Intl.Collator(undefined, {
numeric: true,
sensitivity: "base",
});
function compareModelEntries(
a: { id: string; name: string; provider: string },
b: { id: string; name: string; provider: string }
a: { id: string; name: string; provider: string },
b: { id: string; name: string; provider: string },
): number {
return modelNameCollator.compare(a.name || a.id, b.name || b.id)
|| modelNameCollator.compare(a.provider, b.provider)
|| modelNameCollator.compare(a.id, b.id);
return (
modelNameCollator.compare(a.name || a.id, b.name || b.id) ||
modelNameCollator.compare(a.provider, b.provider) ||
modelNameCollator.compare(a.id, b.id)
);
}
const THINKING_SUFFIXES = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
const THINKING_SUFFIXES = new Set([
"off",
"minimal",
"low",
"medium",
"high",
"xhigh",
"max",
]);
function stripThinkingSuffix(modelRef: string): string {
const trimmed = modelRef.trim();
const colonIndex = trimmed.lastIndexOf(":");
if (colonIndex === -1) return trimmed;
const suffix = trimmed.substring(colonIndex + 1);
return THINKING_SUFFIXES.has(suffix) ? trimmed.substring(0, colonIndex) : trimmed;
const trimmed = modelRef.trim();
const colonIndex = trimmed.lastIndexOf(":");
if (colonIndex === -1) return trimmed;
const suffix = trimmed.substring(colonIndex + 1);
return THINKING_SUFFIXES.has(suffix)
? trimmed.substring(0, colonIndex)
: trimmed;
}
function filterByExactEnabledModels<T extends { id: string; provider: string }>(
available: readonly T[],
enabledModels: string[] | undefined,
available: readonly T[],
enabledModels: string[] | undefined,
): readonly T[] {
if (!enabledModels || enabledModels.length === 0) return available;
if (!enabledModels || enabledModels.length === 0) return available;
const refs = new Set(enabledModels.map(stripThinkingSuffix).filter(Boolean));
const visible = available.filter((m) => refs.has(`${m.provider}/${m.id}`) || refs.has(m.id));
return visible.length > 0 ? visible : available;
const refs = new Set(enabledModels.map(stripThinkingSuffix).filter(Boolean));
const visible = available.filter(
(m) => refs.has(`${m.provider}/${m.id}`) || refs.has(m.id),
);
return visible.length > 0 ? visible : available;
}
async function loadModels(cwd: string): Promise<ModelsData> {
const nameMap = new Map<string, string>();
let modelList: { id: string; name: string; provider: string }[] = [];
let defaultModel: { provider: string; modelId: string } | null = null;
const thinkingLevels: Record<string, string[]> = {};
const thinkingLevelMaps: Record<string, Record<string, string | null>> = {};
const nameMap = new Map<string, string>();
let modelList: { id: string; name: string; provider: string }[] = [];
let defaultModel: { provider: string; modelId: string } | null = null;
const thinkingLevels: Record<string, string[]> = {};
const thinkingLevelMaps: Record<string, Record<string, string | null>> = {};
const agentDir = getAgentDir();
// Gate untrusted project extensions: enumerating models still imports and
// runs a repository's .pi/extensions factories, so honor project trust here
// too (see lib/project-trust.ts, #236).
const trustReloadOptions = projectTrustReloadOptions(cwd, agentDir);
const services = await createAgentSessionServices({
cwd,
agentDir,
settingsManager: createAppSettingsManager(cwd, agentDir),
resourceLoaderOptions: getAppResourceLoaderOptions(),
...(trustReloadOptions ? { resourceLoaderReloadOptions: trustReloadOptions } : {}),
});
const available = await services.modelRuntime.getAvailable();
const modelError = services.modelRuntime.getError();
const settings: SettingsManager = services.settingsManager;
const enabledModels = settings.getEnabledModels();
const visible = filterByExactEnabledModels(available, enabledModels);
modelList = visible.map((m: { id: string; name: string; provider: string }) => ({
id: m.id,
name: m.name,
provider: m.provider,
})).sort(compareModelEntries);
for (const m of visible) {
const key = `${m.provider}:${m.id}`;
nameMap.set(key, m.name);
thinkingLevels[key] = getSupportedThinkingLevels(m);
if (m.thinkingLevelMap) thinkingLevelMaps[key] = m.thinkingLevelMap;
}
const agentDir = getAgentDir();
// Gate untrusted project extensions: enumerating models still imports and
// runs a repository's .pi/extensions factories, so honor project trust here
// too (see lib/project-trust.ts, #236).
const trustReloadOptions = projectTrustReloadOptions(cwd, agentDir);
const services = await createAgentSessionServices({
cwd,
agentDir,
settingsManager: createAppSettingsManager(cwd, agentDir),
resourceLoaderOptions: getAppResourceLoaderOptions(),
...(trustReloadOptions
? { resourceLoaderReloadOptions: trustReloadOptions }
: {}),
});
const available = await services.modelRuntime.getAvailable();
const modelError = services.modelRuntime.getError();
const settings: SettingsManager = services.settingsManager;
const enabledModels = settings.getEnabledModels();
const visible = filterByExactEnabledModels(available, enabledModels);
modelList = visible
.map(
(m: {
id: string;
name: string;
provider: string;
serviceTier?: string;
}) => ({
id: m.id,
name: m.name,
provider: m.provider,
serviceTier: m.serviceTier,
}),
)
.sort(compareModelEntries);
for (const m of visible) {
const key = `${m.provider}:${m.id}`;
nameMap.set(key, m.name);
thinkingLevels[key] = getSupportedThinkingLevels(m);
if (m.thinkingLevelMap) thinkingLevelMaps[key] = m.thinkingLevelMap;
}
const provider = settings.getDefaultProvider();
const modelId = settings.getDefaultModel();
if (provider && modelId && visible.some((m) => m.provider === provider && m.id === modelId)) {
defaultModel = { provider, modelId };
}
const provider = settings.getDefaultProvider();
const modelId = settings.getDefaultModel();
if (
provider &&
modelId &&
visible.some((m) => m.provider === provider && m.id === modelId)
) {
defaultModel = { provider, modelId };
}
return withModelRuntimeError(
{ models: Object.fromEntries(nameMap), modelList, defaultModel, thinkingLevels, thinkingLevelMaps },
modelError,
);
return withModelRuntimeError(
{
models: Object.fromEntries(nameMap),
modelList,
defaultModel,
thinkingLevels,
thinkingLevelMaps,
},
modelError,
);
}
const EMPTY_MODELS: ModelsData = {
models: {},
modelList: [],
defaultModel: null,
thinkingLevels: {},
thinkingLevelMaps: {},
models: {},
modelList: [],
defaultModel: null,
thinkingLevels: {},
thinkingLevelMaps: {},
};
export async function GET(req: Request) {
const requestedCwd = new URL(req.url).searchParams.get("cwd") || process.cwd();
const cwd = resolve(requestedCwd);
const requestedCwd =
new URL(req.url).searchParams.get("cwd") || process.cwd();
const cwd = resolve(requestedCwd);
let cwdStat;
try {
cwdStat = await stat(cwd);
} catch {
return Response.json({ error: `Directory does not exist: ${cwd}` }, { status: 400 });
}
if (!cwdStat.isDirectory()) {
return Response.json({ error: `Not a directory: ${cwd}` }, { status: 400 });
}
const allowedRoots = await getAllowedFileRoots();
if (!isExistingFilePathAllowed(cwd, allowedRoots)) {
return Response.json({ error: "Access denied" }, { status: 403 });
}
let cwdStat;
try {
cwdStat = await stat(cwd);
} catch {
return Response.json(
{ error: `Directory does not exist: ${cwd}` },
{ status: 400 },
);
}
if (!cwdStat.isDirectory()) {
return Response.json({ error: `Not a directory: ${cwd}` }, { status: 400 });
}
const allowedRoots = await getAllowedFileRoots();
if (!isExistingFilePathAllowed(cwd, allowedRoots)) {
return Response.json({ error: "Access denied" }, { status: 403 });
}
try {
return Response.json(await loadModelsWithCache(cwd, () => loadModels(cwd)));
} catch {
return Response.json(EMPTY_MODELS);
}
try {
return Response.json(await loadModelsWithCache(cwd, () => loadModels(cwd)));
} catch {
return Response.json(EMPTY_MODELS);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+70 -56
View File
@@ -1,80 +1,94 @@
export interface ModelsData {
models: Record<string, string>;
modelList: { id: string; name: string; provider: string }[];
defaultModel: { provider: string; modelId: string } | null;
thinkingLevels: Record<string, string[]>;
thinkingLevelMaps: Record<string, Record<string, string | null>>;
modelError?: string;
models: Record<string, string>;
modelList: {
id: string;
name: string;
provider: string;
serviceTier?: string;
}[];
defaultModel: { provider: string; modelId: string } | null;
thinkingLevels: Record<string, string[]>;
thinkingLevelMaps: Record<string, Record<string, string | null>>;
modelError?: string;
}
interface ModelsCacheState {
entries: Map<string, { data: ModelsData; expiresAt: number }>;
inFlight: Map<string, Promise<ModelsData>>;
generation: number;
entries: Map<string, { data: ModelsData; expiresAt: number }>;
inFlight: Map<string, Promise<ModelsData>>;
generation: number;
}
declare global {
var __piModelsCacheState: ModelsCacheState | undefined;
var __piModelsCacheState: ModelsCacheState | undefined;
}
const MODELS_CACHE_TTL_MS = 60_000;
const MAX_MODELS_CACHE_ENTRIES = 32;
function getModelsCacheState(): ModelsCacheState {
if (!globalThis.__piModelsCacheState) {
globalThis.__piModelsCacheState = {
entries: new Map(),
inFlight: new Map(),
generation: 0,
};
}
return globalThis.__piModelsCacheState;
if (!globalThis.__piModelsCacheState) {
globalThis.__piModelsCacheState = {
entries: new Map(),
inFlight: new Map(),
generation: 0,
};
}
return globalThis.__piModelsCacheState;
}
export function invalidateModelsCache(): void {
const state = getModelsCacheState();
state.generation += 1;
state.entries.clear();
state.inFlight.clear();
const state = getModelsCacheState();
state.generation += 1;
state.entries.clear();
state.inFlight.clear();
}
export function withModelRuntimeError(data: ModelsData, modelError: string | undefined): ModelsData {
return modelError ? { ...data, modelError } : data;
export function withModelRuntimeError(
data: ModelsData,
modelError: string | undefined,
): ModelsData {
return modelError ? { ...data, modelError } : data;
}
export function loadModelsWithCache(cwd: string, loader: () => Promise<ModelsData>): Promise<ModelsData> {
const state = getModelsCacheState();
const cached = state.entries.get(cwd);
if (cached) {
if (cached.expiresAt > Date.now()) return Promise.resolve(cached.data);
state.entries.delete(cwd);
}
export function loadModelsWithCache(
cwd: string,
loader: () => Promise<ModelsData>,
): Promise<ModelsData> {
const state = getModelsCacheState();
const cached = state.entries.get(cwd);
if (cached) {
if (cached.expiresAt > Date.now()) return Promise.resolve(cached.data);
state.entries.delete(cwd);
}
const existingLoad = state.inFlight.get(cwd);
if (existingLoad) return existingLoad;
const existingLoad = state.inFlight.get(cwd);
if (existingLoad) return existingLoad;
const generation = state.generation;
const loadPromise: Promise<ModelsData> = Promise.resolve()
.then(loader)
.then((data) => {
if (state.generation === generation && state.inFlight.get(cwd) === loadPromise) {
const now = Date.now();
for (const [key, entry] of state.entries) {
if (entry.expiresAt <= now) state.entries.delete(key);
}
while (state.entries.size >= MAX_MODELS_CACHE_ENTRIES) {
const oldestKey = state.entries.keys().next().value;
if (oldestKey === undefined) break;
state.entries.delete(oldestKey);
}
state.entries.set(cwd, { data, expiresAt: now + MODELS_CACHE_TTL_MS });
}
return data;
})
.finally(() => {
if (state.inFlight.get(cwd) === loadPromise) state.inFlight.delete(cwd);
});
const generation = state.generation;
const loadPromise: Promise<ModelsData> = Promise.resolve()
.then(loader)
.then((data) => {
if (
state.generation === generation &&
state.inFlight.get(cwd) === loadPromise
) {
const now = Date.now();
for (const [key, entry] of state.entries) {
if (entry.expiresAt <= now) state.entries.delete(key);
}
while (state.entries.size >= MAX_MODELS_CACHE_ENTRIES) {
const oldestKey = state.entries.keys().next().value;
if (oldestKey === undefined) break;
state.entries.delete(oldestKey);
}
state.entries.set(cwd, { data, expiresAt: now + MODELS_CACHE_TTL_MS });
}
return data;
})
.finally(() => {
if (state.inFlight.get(cwd) === loadPromise) state.inFlight.delete(cwd);
});
state.inFlight.set(cwd, loadPromise);
return loadPromise;
state.inFlight.set(cwd, loadPromise);
return loadPromise;
}