refactor: remove dead code (unused exports) (#1486)
Remove exported functions/constants/classes that are never imported anywhere else in the codebase: Fully removed (not used anywhere): - nativeAvailable (native) - getApiProviders, unregisterApiProviders (pi-ai/api-registry) - createAssistantMessageEventStream (pi-ai/event-stream) - getOverflowPatterns (pi-ai/overflow) - validateToolCall (pi-ai/validation) - getToolsDir (pi-coding-agent/config) - emitSessionShutdownEvent (extensions/runner) - syncContent, notifySaved (lsp/client) - getServerForFile, hasCapability (lsp/config) - severityToIcon, formatPosition, formatTextEdit, symbolKindToName (lsp/utils) - clearApiKeyCache (model-registry) - restoreModelFromSession (model-resolver) - isLightTheme (theme) - loadPhoton + all internal helpers (photon) - extractAnsiCode (pi-tui/utils) De-exported (used locally, not externally): - extractRetryAfterMs, inferCopilotInitiator, extractRetryDelay, buildRequest, requiresToolCallId, registerBuiltInApiProviders, streamProxy, isBunRuntime, detectInstallMethod, getPackageDir, getPackageJsonPath, ansiToHtml, DEFAULT_APP_KEYBINDINGS, DEFAULT_KEYBINDINGS, shutdownClient, sendNotification, shutdownAll, applyTextEditsToString, wrapWithLspmux, severityToString, COMPACTION_SUMMARY_PREFIX/SUFFIX, BRANCH_SUMMARY_PREFIX/SUFFIX, bashExecutionToText, defaultModelPerProvider, parseModelPattern, parseCommandArgs, substituteArgs, loadEntriesFromFile, findMostRecentSession, FileSettingsStorage, InMemorySettingsStorage, migrateAuthToAuthJson, migrateSessionsFromAgentRoot, parseSearchQuery, matchSession, compareVersions, isWaylandSession, getToolPath, wordWrapLine
This commit is contained in:
parent
c10e42b392
commit
4e29ca4544
35 changed files with 44 additions and 483 deletions
|
|
@ -151,6 +151,3 @@ export const native = loadNative() as {
|
|||
parseStreamingJson: (text: string) => unknown;
|
||||
xxHash32: (input: string, seed: number) => number;
|
||||
};
|
||||
|
||||
/** True when the native addon loaded successfully. False on unsupported platforms. */
|
||||
export const nativeAvailable = _loadedSuccessfully;
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export interface ProxyStreamOptions extends SimpleStreamOptions {
|
|||
* });
|
||||
* ```
|
||||
*/
|
||||
export function streamProxy(model: Model<any>, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream {
|
||||
function streamProxy(model: Model<any>, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream {
|
||||
const stream = new ProxyMessageEventStream();
|
||||
|
||||
(async () => {
|
||||
|
|
|
|||
|
|
@ -81,18 +81,6 @@ export function getApiProvider(api: Api): ApiProviderInternal | undefined {
|
|||
return apiProviderRegistry.get(api)?.provider;
|
||||
}
|
||||
|
||||
export function getApiProviders(): ApiProviderInternal[] {
|
||||
return Array.from(apiProviderRegistry.values(), (entry) => entry.provider);
|
||||
}
|
||||
|
||||
export function unregisterApiProviders(sourceId: string): void {
|
||||
for (const [api, entry] of apiProviderRegistry.entries()) {
|
||||
if (entry.sourceId === sourceId) {
|
||||
apiProviderRegistry.delete(api);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function clearApiProviders(): void {
|
||||
apiProviderRegistry.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ function isTransientNetworkError(error: unknown): boolean {
|
|||
* Checks: retry-after (seconds or RFC date), x-ratelimit-reset-requests, x-ratelimit-reset-tokens.
|
||||
* Returns undefined if no valid delay is found or if the delay is in the past.
|
||||
*/
|
||||
export function extractRetryAfterMs(headers: Headers | { get(name: string): string | null }, errorText = ""): number | undefined {
|
||||
function extractRetryAfterMs(headers: Headers | { get(name: string): string | null }, errorText = ""): number | undefined {
|
||||
const normalizeDelay = (ms: number): number | undefined => (ms > 0 ? Math.ceil(ms + 1000) : undefined);
|
||||
|
||||
const retryAfter = headers.get("retry-after");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { Message } from "../types.js";
|
|||
|
||||
// Copilot expects X-Initiator to indicate whether the request is user-initiated
|
||||
// or agent-initiated (e.g. follow-up after assistant/tool messages).
|
||||
export function inferCopilotInitiator(messages: Message[]): "user" | "agent" {
|
||||
function inferCopilotInitiator(messages: Message[]): "user" | "agent" {
|
||||
const last = messages[messages.length - 1];
|
||||
return last && last.role !== "user" ? "agent" : "user";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ const CLAUDE_THINKING_BETA_HEADER = "interleaved-thinking-2025-05-14";
|
|||
* - "Please retry in Xs" or "Please retry in Xms"
|
||||
* - "retryDelay": "34.074824224s" (JSON field)
|
||||
*/
|
||||
export function extractRetryDelay(errorText: string, response?: Response | Headers): number | undefined {
|
||||
function extractRetryDelay(errorText: string, response?: Response | Headers): number | undefined {
|
||||
const normalizeDelay = (ms: number): number | undefined => (ms > 0 ? Math.ceil(ms + 1000) : undefined);
|
||||
|
||||
const headers = response instanceof Headers ? response : response?.headers;
|
||||
|
|
@ -865,7 +865,7 @@ export const streamSimpleGoogleGeminiCli: StreamFunction<"google-gemini-cli", Si
|
|||
} satisfies GoogleGeminiCliOptions);
|
||||
};
|
||||
|
||||
export function buildRequest(
|
||||
function buildRequest(
|
||||
model: Model<"google-gemini-cli">,
|
||||
context: Context,
|
||||
projectId: string,
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ function resolveThoughtSignature(isSameProviderAndModel: boolean, signature: str
|
|||
/**
|
||||
* Models via Google APIs that require explicit tool call IDs in function calls/responses.
|
||||
*/
|
||||
export function requiresToolCallId(modelId: string): boolean {
|
||||
function requiresToolCallId(modelId: string): boolean {
|
||||
return modelId.startsWith("claude-") || modelId.startsWith("gpt-oss-");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ function streamSimpleBedrockLazy(
|
|||
return outer;
|
||||
}
|
||||
|
||||
export function registerBuiltInApiProviders(): void {
|
||||
function registerBuiltInApiProviders(): void {
|
||||
registerApiProvider({
|
||||
api: "anthropic-messages",
|
||||
stream: streamAnthropic,
|
||||
|
|
|
|||
|
|
@ -80,8 +80,3 @@ export class AssistantMessageEventStream extends EventStream<AssistantMessageEve
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Factory function for AssistantMessageEventStream (for use in extensions) */
|
||||
export function createAssistantMessageEventStream(): AssistantMessageEventStream {
|
||||
return new AssistantMessageEventStream();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,11 +113,4 @@ export function isContextOverflow(message: AssistantMessage, contextWindow?: num
|
|||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the overflow patterns for testing purposes.
|
||||
*/
|
||||
export function getOverflowPatterns(): RegExp[] {
|
||||
return [...OVERFLOW_PATTERNS];
|
||||
}
|
||||
}
|
||||
|
|
@ -28,21 +28,6 @@ if (!isBrowserExtension) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a tool by name and validates the tool call arguments against its TypeBox schema
|
||||
* @param tools Array of tool definitions
|
||||
* @param toolCall The tool call from the LLM
|
||||
* @returns The validated arguments
|
||||
* @throws Error if tool is not found or validation fails
|
||||
*/
|
||||
export function validateToolCall(tools: Tool[], toolCall: ToolCall): any {
|
||||
const tool = tools.find((t) => t.name === toolCall.name);
|
||||
if (!tool) {
|
||||
throw new Error(`Tool "${toolCall.name}" not found`);
|
||||
}
|
||||
return validateToolArguments(tool, toolCall);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates tool call arguments against the tool's TypeBox schema
|
||||
* @param tool The tool definition with TypeBox schema
|
||||
|
|
|
|||
|
|
@ -18,15 +18,15 @@ export const isBunBinary =
|
|||
import.meta.url.includes("$bunfs") || import.meta.url.includes("~BUN") || import.meta.url.includes("%7EBUN");
|
||||
|
||||
/** Detect if Bun is the runtime (compiled binary or bun run) */
|
||||
export const isBunRuntime = !!process.versions.bun;
|
||||
const isBunRuntime = !!process.versions.bun;
|
||||
|
||||
// =============================================================================
|
||||
// Install Method Detection
|
||||
// =============================================================================
|
||||
|
||||
export type InstallMethod = "bun-binary" | "npm" | "pnpm" | "yarn" | "bun" | "unknown";
|
||||
type InstallMethod = "bun-binary" | "npm" | "pnpm" | "yarn" | "bun" | "unknown";
|
||||
|
||||
export function detectInstallMethod(): InstallMethod {
|
||||
function detectInstallMethod(): InstallMethod {
|
||||
if (isBunBinary) {
|
||||
return "bun-binary";
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ export function getUpdateInstruction(packageName: string): string {
|
|||
*/
|
||||
let _cachedPackageDir: string | undefined;
|
||||
|
||||
export function getPackageDir(): string {
|
||||
function getPackageDir(): string {
|
||||
if (_cachedPackageDir !== undefined) return _cachedPackageDir;
|
||||
|
||||
// Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)
|
||||
|
|
@ -138,7 +138,7 @@ export function getExportTemplateDir(): string {
|
|||
}
|
||||
|
||||
/** Get path to package.json */
|
||||
export function getPackageJsonPath(): string {
|
||||
function getPackageJsonPath(): string {
|
||||
return join(getPackageDir(), "package.json");
|
||||
}
|
||||
|
||||
|
|
@ -219,11 +219,6 @@ export function getSettingsPath(): string {
|
|||
return join(getAgentDir(), "settings.json");
|
||||
}
|
||||
|
||||
/** Get path to tools directory */
|
||||
export function getToolsDir(): string {
|
||||
return join(getAgentDir(), "tools");
|
||||
}
|
||||
|
||||
/** Get path to managed binaries directory (fd, rg) */
|
||||
export function getBinDir(): string {
|
||||
return join(getAgentDir(), "bin");
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ const ANSI_REGEX = /\x1b\[([\d;]*)m/g;
|
|||
/**
|
||||
* Convert ANSI-escaped text to HTML with inline styles.
|
||||
*/
|
||||
export function ansiToHtml(text: string): string {
|
||||
function ansiToHtml(text: string): string {
|
||||
const style = createEmptyStyle();
|
||||
let result = "";
|
||||
let lastIndex = 0;
|
||||
|
|
|
|||
|
|
@ -153,19 +153,6 @@ export type ReloadHandler = () => Promise<void>;
|
|||
|
||||
export type ShutdownHandler = () => void;
|
||||
|
||||
/**
|
||||
* Helper function to emit session_shutdown event to extensions.
|
||||
* Returns true if the event was emitted, false if there were no handlers.
|
||||
*/
|
||||
export async function emitSessionShutdownEvent(extensionRunner: ExtensionRunner | undefined): Promise<boolean> {
|
||||
if (extensionRunner?.hasHandlers("session_shutdown")) {
|
||||
await extensionRunner.emit({
|
||||
type: "session_shutdown",
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const noOpUIContext: ExtensionUIContext = {
|
||||
select: async () => undefined,
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export type KeybindingsConfig = {
|
|||
/**
|
||||
* Default application keybindings.
|
||||
*/
|
||||
export const DEFAULT_APP_KEYBINDINGS: Record<AppAction, KeyId | KeyId[]> = {
|
||||
const DEFAULT_APP_KEYBINDINGS: Record<AppAction, KeyId | KeyId[]> = {
|
||||
interrupt: "escape",
|
||||
clear: "ctrl+c",
|
||||
exit: "ctrl+d",
|
||||
|
|
@ -75,7 +75,7 @@ export const DEFAULT_APP_KEYBINDINGS: Record<AppAction, KeyId | KeyId[]> = {
|
|||
/**
|
||||
* All default keybindings (app + editor).
|
||||
*/
|
||||
export const DEFAULT_KEYBINDINGS: Required<KeybindingsConfig> = {
|
||||
const DEFAULT_KEYBINDINGS: Required<KeybindingsConfig> = {
|
||||
...DEFAULT_EDITOR_KEYBINDINGS,
|
||||
...DEFAULT_APP_KEYBINDINGS,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -618,76 +618,6 @@ export async function ensureFileOpen(client: LspClient, filePath: string, signal
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync in-memory content to the LSP client without reading from disk.
|
||||
*/
|
||||
export async function syncContent(
|
||||
client: LspClient,
|
||||
filePath: string,
|
||||
content: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<void> {
|
||||
const uri = fileToUri(filePath);
|
||||
const lockKey = `${client.name}:${uri}`;
|
||||
throwIfAborted(signal);
|
||||
|
||||
const existingLock = fileOperationLocks.get(lockKey);
|
||||
if (existingLock) {
|
||||
await untilAborted(signal, () => existingLock);
|
||||
}
|
||||
|
||||
const syncPromise = (async () => {
|
||||
client.diagnostics.delete(uri);
|
||||
|
||||
const info = client.openFiles.get(uri);
|
||||
|
||||
if (!info) {
|
||||
const languageId = detectLanguageId(filePath);
|
||||
throwIfAborted(signal);
|
||||
await sendNotification(client, "textDocument/didOpen", {
|
||||
textDocument: {
|
||||
uri,
|
||||
languageId,
|
||||
version: 1,
|
||||
text: content,
|
||||
},
|
||||
});
|
||||
client.openFiles.set(uri, { version: 1, languageId });
|
||||
client.lastActivity = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
const version = ++info.version;
|
||||
throwIfAborted(signal);
|
||||
await sendNotification(client, "textDocument/didChange", {
|
||||
textDocument: { uri, version },
|
||||
contentChanges: [{ text: content }],
|
||||
});
|
||||
client.lastActivity = Date.now();
|
||||
})();
|
||||
|
||||
fileOperationLocks.set(lockKey, syncPromise);
|
||||
try {
|
||||
await syncPromise;
|
||||
} finally {
|
||||
fileOperationLocks.delete(lockKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify LSP that a file was saved.
|
||||
*/
|
||||
export async function notifySaved(client: LspClient, filePath: string, signal?: AbortSignal): Promise<void> {
|
||||
const uri = fileToUri(filePath);
|
||||
const info = client.openFiles.get(uri);
|
||||
if (!info) return;
|
||||
|
||||
throwIfAborted(signal);
|
||||
await sendNotification(client, "textDocument/didSave", {
|
||||
textDocument: { uri },
|
||||
});
|
||||
client.lastActivity = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh a file in the LSP client.
|
||||
|
|
@ -761,7 +691,7 @@ export function notifyFileChanged(filePath: string): void {
|
|||
/**
|
||||
* Shutdown a specific client by key.
|
||||
*/
|
||||
export function shutdownClient(key: string): void {
|
||||
function shutdownClient(key: string): void {
|
||||
const client = clients.get(key);
|
||||
if (!client) return;
|
||||
|
||||
|
|
@ -865,7 +795,7 @@ export async function sendRequest(
|
|||
return promise;
|
||||
}
|
||||
|
||||
export async function sendNotification(client: LspClient, method: string, params: unknown): Promise<void> {
|
||||
async function sendNotification(client: LspClient, method: string, params: unknown): Promise<void> {
|
||||
const notification: LspJsonRpcNotification = {
|
||||
jsonrpc: "2.0",
|
||||
method,
|
||||
|
|
@ -889,7 +819,7 @@ export async function sendNotification(client: LspClient, method: string, params
|
|||
/**
|
||||
* Shutdown all LSP clients.
|
||||
*/
|
||||
export function shutdownAll(): void {
|
||||
function shutdownAll(): void {
|
||||
const clientsToShutdown = Array.from(clients.values());
|
||||
clients.clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -319,14 +319,3 @@ export function getServersForFile(config: LspConfig, filePath: string): Array<[s
|
|||
});
|
||||
}
|
||||
|
||||
export function getServerForFile(config: LspConfig, filePath: string): [string, ServerConfig] | null {
|
||||
const servers = getServersForFile(config, filePath);
|
||||
return servers.length > 0 ? servers[0] : null;
|
||||
}
|
||||
|
||||
export function hasCapability(
|
||||
config: ServerConfig,
|
||||
capability: keyof NonNullable<ServerConfig["capabilities"]>,
|
||||
): boolean {
|
||||
return config.capabilities?.[capability] === true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { uriToFile } from "./utils.js";
|
|||
* Apply text edits to a string in-memory.
|
||||
* Edits are applied in reverse order (bottom-to-top) to preserve line/character indices.
|
||||
*/
|
||||
export function applyTextEditsToString(content: string, edits: TextEdit[]): string {
|
||||
function applyTextEditsToString(content: string, edits: TextEdit[]): string {
|
||||
const lines = content.split("\n");
|
||||
|
||||
// Sort edits in reverse order (bottom-to-top, right-to-left)
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ export interface LspmuxWrappedCommand {
|
|||
env?: Record<string, string>;
|
||||
}
|
||||
|
||||
export function wrapWithLspmux(
|
||||
function wrapWithLspmux(
|
||||
originalCommand: string,
|
||||
originalArgs: string[] | undefined,
|
||||
state: LspmuxState,
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ const SEVERITY_NAMES: Record<DiagnosticSeverity, string> = {
|
|||
4: "hint",
|
||||
};
|
||||
|
||||
export function severityToString(severity?: DiagnosticSeverity): string {
|
||||
function severityToString(severity?: DiagnosticSeverity): string {
|
||||
return SEVERITY_NAMES[severity ?? 1] ?? "unknown";
|
||||
}
|
||||
|
||||
|
|
@ -211,21 +211,6 @@ export function sortDiagnostics(diagnostics: Diagnostic[]): Diagnostic[] {
|
|||
});
|
||||
}
|
||||
|
||||
export function severityToIcon(severity?: DiagnosticSeverity): string {
|
||||
switch (severity ?? 1) {
|
||||
case 1:
|
||||
return "[E]";
|
||||
case 2:
|
||||
return "[W]";
|
||||
case 3:
|
||||
return "[I]";
|
||||
case 4:
|
||||
return "[H]";
|
||||
default:
|
||||
return "[E]";
|
||||
}
|
||||
}
|
||||
|
||||
function stripDiagnosticNoise(message: string): string {
|
||||
return message
|
||||
.split("\n")
|
||||
|
|
@ -353,10 +338,6 @@ export function formatLocation(location: Location, cwd: string): string {
|
|||
return `${file}:${line}:${col}`;
|
||||
}
|
||||
|
||||
export function formatPosition(line: number, col: number): string {
|
||||
return `${line}:${col}`;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// WorkspaceEdit Formatting
|
||||
// =============================================================================
|
||||
|
|
@ -397,15 +378,6 @@ export function formatWorkspaceEdit(edit: WorkspaceEdit, cwd: string): string[]
|
|||
return results;
|
||||
}
|
||||
|
||||
export function formatTextEdit(edit: TextEdit, maxLength = 50): string {
|
||||
const range = `${edit.range.start.line + 1}:${edit.range.start.character + 1}`;
|
||||
const preview =
|
||||
edit.newText.length > maxLength
|
||||
? `${edit.newText.slice(0, maxLength).replace(/\n/g, "\\n")}…`
|
||||
: edit.newText.replace(/\n/g, "\\n");
|
||||
return `line ${range} -> "${preview}"`;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Symbol Formatting
|
||||
// =============================================================================
|
||||
|
|
@ -443,10 +415,6 @@ export function symbolKindToIcon(kind: SymbolKind): string {
|
|||
return `[${SYMBOL_KIND_LABELS[kind] ?? "?"}]`;
|
||||
}
|
||||
|
||||
export function symbolKindToName(kind: SymbolKind): string {
|
||||
return SYMBOL_KIND_LABELS[kind] ?? "Unknown";
|
||||
}
|
||||
|
||||
export function formatDocumentSymbol(symbol: DocumentSymbol, indent = 0): string[] {
|
||||
const prefix = " ".repeat(indent);
|
||||
const icon = symbolKindToIcon(symbol.kind);
|
||||
|
|
|
|||
|
|
@ -8,20 +8,20 @@
|
|||
import type { AgentMessage } from "@gsd/pi-agent-core";
|
||||
import type { ImageContent, Message, TextContent } from "@gsd/pi-ai";
|
||||
|
||||
export const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:
|
||||
const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:
|
||||
|
||||
<summary>
|
||||
`;
|
||||
|
||||
export const COMPACTION_SUMMARY_SUFFIX = `
|
||||
const COMPACTION_SUMMARY_SUFFIX = `
|
||||
</summary>`;
|
||||
|
||||
export const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch that this conversation came back from:
|
||||
const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch that this conversation came back from:
|
||||
|
||||
<summary>
|
||||
`;
|
||||
|
||||
export const BRANCH_SUMMARY_SUFFIX = `</summary>`;
|
||||
const BRANCH_SUMMARY_SUFFIX = `</summary>`;
|
||||
|
||||
/**
|
||||
* Message type for bash executions via the ! command.
|
||||
|
|
@ -79,7 +79,7 @@ declare module "@gsd/pi-agent-core" {
|
|||
/**
|
||||
* Convert a BashExecutionMessage to user message text for LLM context.
|
||||
*/
|
||||
export function bashExecutionToText(msg: BashExecutionMessage): string {
|
||||
function bashExecutionToText(msg: BashExecutionMessage): string {
|
||||
let text = `Ran \`${msg.command}\`\n`;
|
||||
if (msg.output) {
|
||||
text += `\`\`\`\n${msg.output}\n\`\`\``;
|
||||
|
|
|
|||
|
|
@ -216,8 +216,6 @@ function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<A
|
|||
return result;
|
||||
}
|
||||
|
||||
/** Clear the config value command cache. Exported for testing. */
|
||||
export const clearApiKeyCache = clearConfigValueCache;
|
||||
|
||||
/**
|
||||
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|||
import type { ModelRegistry } from "./model-registry.js";
|
||||
|
||||
/** Default model IDs for each known provider */
|
||||
export const defaultModelPerProvider: Record<KnownProvider, string> = {
|
||||
const defaultModelPerProvider: Record<KnownProvider, string> = {
|
||||
"amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
|
||||
anthropic: "claude-opus-4-6[1m]",
|
||||
openai: "gpt-5.4",
|
||||
|
|
@ -143,9 +143,9 @@ function buildFallbackModel(provider: string, modelId: string, availableModels:
|
|||
* - If suffix is valid thinking level, use it and recurse on prefix
|
||||
* - If suffix is invalid, warn and recurse on prefix with "off"
|
||||
*
|
||||
* @internal Exported for testing
|
||||
* @internal
|
||||
*/
|
||||
export function parseModelPattern(
|
||||
function parseModelPattern(
|
||||
pattern: string,
|
||||
availableModels: Model<Api>[],
|
||||
options?: { allowInvalidThinkingLevelFallback?: boolean },
|
||||
|
|
@ -546,77 +546,3 @@ export async function findInitialModel(options: {
|
|||
// 5. No model found
|
||||
return { model: undefined, thinkingLevel: DEFAULT_THINKING_LEVEL, fallbackMessage: undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore model from session, with fallback to available models
|
||||
*/
|
||||
export async function restoreModelFromSession(
|
||||
savedProvider: string,
|
||||
savedModelId: string,
|
||||
currentModel: Model<Api> | undefined,
|
||||
shouldPrintMessages: boolean,
|
||||
modelRegistry: ModelRegistry,
|
||||
): Promise<{ model: Model<Api> | undefined; fallbackMessage: string | undefined }> {
|
||||
const restoredModel = modelRegistry.find(savedProvider, savedModelId);
|
||||
|
||||
// Check if restored model exists and has a valid API key
|
||||
const hasApiKey = restoredModel ? !!(await modelRegistry.getApiKey(restoredModel)) : false;
|
||||
|
||||
if (restoredModel && hasApiKey) {
|
||||
if (shouldPrintMessages) {
|
||||
console.log(chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`));
|
||||
}
|
||||
return { model: restoredModel, fallbackMessage: undefined };
|
||||
}
|
||||
|
||||
// Model not found or no API key - fall back
|
||||
const reason = !restoredModel ? "model no longer exists" : "no API key available";
|
||||
|
||||
if (shouldPrintMessages) {
|
||||
console.error(chalk.yellow(`Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`));
|
||||
}
|
||||
|
||||
// If we already have a model, use it as fallback
|
||||
if (currentModel) {
|
||||
if (shouldPrintMessages) {
|
||||
console.log(chalk.dim(`Falling back to: ${currentModel.provider}/${currentModel.id}`));
|
||||
}
|
||||
return {
|
||||
model: currentModel,
|
||||
fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`,
|
||||
};
|
||||
}
|
||||
|
||||
// Try to find any available model
|
||||
const availableModels = await modelRegistry.getAvailable();
|
||||
|
||||
if (availableModels.length > 0) {
|
||||
// Try to find a default model from known providers
|
||||
let fallbackModel: Model<Api> | undefined;
|
||||
for (const provider of Object.keys(defaultModelPerProvider) as KnownProvider[]) {
|
||||
const defaultId = defaultModelPerProvider[provider];
|
||||
const match = availableModels.find((m) => m.provider === provider && m.id === defaultId);
|
||||
if (match) {
|
||||
fallbackModel = match;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no default found, use first available
|
||||
if (!fallbackModel) {
|
||||
fallbackModel = availableModels[0];
|
||||
}
|
||||
|
||||
if (shouldPrintMessages) {
|
||||
console.log(chalk.dim(`Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`));
|
||||
}
|
||||
|
||||
return {
|
||||
model: fallbackModel,
|
||||
fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`,
|
||||
};
|
||||
}
|
||||
|
||||
// No models available
|
||||
return { model: undefined, fallbackMessage: undefined };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export interface PromptTemplate {
|
|||
* Parse command arguments respecting quoted strings (bash-style)
|
||||
* Returns array of arguments
|
||||
*/
|
||||
export function parseCommandArgs(argsString: string): string[] {
|
||||
function parseCommandArgs(argsString: string): string[] {
|
||||
const args: string[] = [];
|
||||
let current = "";
|
||||
let inQuote: string | null = null;
|
||||
|
|
@ -63,7 +63,7 @@ export function parseCommandArgs(argsString: string): string[] {
|
|||
* Note: Replacement happens on the template string only. Argument values
|
||||
* containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.
|
||||
*/
|
||||
export function substituteArgs(content: string, args: string[]): string {
|
||||
function substituteArgs(content: string, args: string[]): string {
|
||||
let result = content;
|
||||
|
||||
// Replace $1, $2, etc. with positional args FIRST (before wildcards)
|
||||
|
|
|
|||
|
|
@ -577,8 +577,7 @@ function resolveBlobRefsInEntries(entries: FileEntry[], blobStore: BlobStore): v
|
|||
}
|
||||
}
|
||||
|
||||
/** Exported for testing */
|
||||
export function loadEntriesFromFile(filePath: string): FileEntry[] {
|
||||
function loadEntriesFromFile(filePath: string): FileEntry[] {
|
||||
if (!existsSync(filePath)) return [];
|
||||
|
||||
const content = readFileSync(filePath, "utf8");
|
||||
|
|
@ -620,8 +619,7 @@ function isValidSessionFile(filePath: string): boolean {
|
|||
}
|
||||
}
|
||||
|
||||
/** Exported for testing */
|
||||
export function findMostRecentSession(sessionDir: string): string | null {
|
||||
function findMostRecentSession(sessionDir: string): string | null {
|
||||
try {
|
||||
const files = readdirSync(sessionDir)
|
||||
.filter((f) => f.endsWith(".jsonl"))
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ export interface SettingsError {
|
|||
error: Error;
|
||||
}
|
||||
|
||||
export class FileSettingsStorage implements SettingsStorage {
|
||||
class FileSettingsStorage implements SettingsStorage {
|
||||
private globalSettingsPath: string;
|
||||
private projectSettingsPath: string;
|
||||
|
||||
|
|
@ -262,7 +262,7 @@ export class FileSettingsStorage implements SettingsStorage {
|
|||
}
|
||||
}
|
||||
|
||||
export class InMemorySettingsStorage implements SettingsStorage {
|
||||
class InMemorySettingsStorage implements SettingsStorage {
|
||||
private global: string | undefined;
|
||||
private project: string | undefined;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const EXTENSIONS_DOC_URL = "https://github.com/badlogic/pi-mono/blob/main/packag
|
|||
*
|
||||
* @returns Array of provider names that were migrated
|
||||
*/
|
||||
export function migrateAuthToAuthJson(): string[] {
|
||||
function migrateAuthToAuthJson(): string[] {
|
||||
const agentDir = getAgentDir();
|
||||
const authPath = join(agentDir, "auth.json");
|
||||
const oauthPath = join(agentDir, "oauth.json");
|
||||
|
|
@ -79,7 +79,7 @@ export function migrateAuthToAuthJson(): string[] {
|
|||
*
|
||||
* See: https://github.com/badlogic/pi-mono/issues/320
|
||||
*/
|
||||
export function migrateSessionsFromAgentRoot(): void {
|
||||
function migrateSessionsFromAgentRoot(): void {
|
||||
const agentDir = getAgentDir();
|
||||
|
||||
// Find all .jsonl files directly in agentDir (not in subdirectories)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ function matchesNameFilter(session: SessionInfo, filter: NameFilter): boolean {
|
|||
return hasSessionName(session);
|
||||
}
|
||||
|
||||
export function parseSearchQuery(query: string): ParsedSearchQuery {
|
||||
function parseSearchQuery(query: string): ParsedSearchQuery {
|
||||
const trimmed = query.trim();
|
||||
if (!trimmed) {
|
||||
return { mode: "tokens", tokens: [], regex: null };
|
||||
|
|
@ -113,7 +113,7 @@ export function parseSearchQuery(query: string): ParsedSearchQuery {
|
|||
return { mode: "tokens", tokens, regex: null };
|
||||
}
|
||||
|
||||
export function matchSession(session: SessionInfo, parsed: ParsedSearchQuery): MatchResult {
|
||||
function matchSession(session: SessionInfo, parsed: ParsedSearchQuery): MatchResult {
|
||||
const text = getSessionSearchText(session);
|
||||
|
||||
if (parsed.mode === "regex") {
|
||||
|
|
|
|||
|
|
@ -880,14 +880,6 @@ export function getResolvedThemeColors(themeName?: string): Record<string, strin
|
|||
return cssColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a theme is a "light" theme (for CSS that needs light/dark variants).
|
||||
*/
|
||||
export function isLightTheme(themeName?: string): boolean {
|
||||
// Currently just check the name - could be extended to analyze colors
|
||||
return themeName === "light";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get explicit export colors from theme JSON, if specified.
|
||||
* Returns undefined for each color that isn't explicitly set.
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export function parseChangelog(changelogPath: string): ChangelogEntry[] {
|
|||
/**
|
||||
* Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
|
||||
*/
|
||||
export function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number {
|
||||
function compareVersions(v1: ChangelogEntry, v2: ChangelogEntry): number {
|
||||
if (v1.major !== v2.major) return v1.major - v2.major;
|
||||
if (v1.minor !== v2.minor) return v1.minor - v2.minor;
|
||||
return v1.patch - v2.patch;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const DEFAULT_LIST_TIMEOUT_MS = 1000;
|
|||
const DEFAULT_READ_TIMEOUT_MS = 3000;
|
||||
const DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
||||
|
||||
export function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
return Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === "wayland";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,139 +1,2 @@
|
|||
/**
|
||||
* Photon image processing wrapper.
|
||||
*
|
||||
* This module provides a unified interface to @silvia-odwyer/photon-node that works in:
|
||||
* 1. Node.js (development, npm run build)
|
||||
* 2. Bun compiled binaries (standalone distribution)
|
||||
*
|
||||
* The challenge: photon-node's CJS entry uses fs.readFileSync(__dirname + '/photon_rs_bg.wasm')
|
||||
* which bakes the build machine's absolute path into Bun compiled binaries.
|
||||
*
|
||||
* Solution:
|
||||
* 1. Patch fs.readFileSync to redirect missing photon_rs_bg.wasm reads
|
||||
* 2. Copy photon_rs_bg.wasm next to the executable in build:binary
|
||||
*/
|
||||
|
||||
import type { PathOrFileDescriptor } from "fs";
|
||||
import { createRequire } from "module";
|
||||
import * as path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const fs = require("fs") as typeof import("fs");
|
||||
|
||||
// Re-export types from the main package
|
||||
export type { PhotonImage as PhotonImageType } from "@silvia-odwyer/photon-node";
|
||||
|
||||
type ReadFileSync = typeof fs.readFileSync;
|
||||
|
||||
const WASM_FILENAME = "photon_rs_bg.wasm";
|
||||
|
||||
// Lazy-loaded photon module
|
||||
let photonModule: typeof import("@silvia-odwyer/photon-node") | null = null;
|
||||
let loadPromise: Promise<typeof import("@silvia-odwyer/photon-node") | null> | null = null;
|
||||
|
||||
function pathOrNull(file: PathOrFileDescriptor): string | null {
|
||||
if (typeof file === "string") {
|
||||
return file;
|
||||
}
|
||||
if (file instanceof URL) {
|
||||
return fileURLToPath(file);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getFallbackWasmPaths(): string[] {
|
||||
const execDir = path.dirname(process.execPath);
|
||||
return [
|
||||
path.join(execDir, WASM_FILENAME),
|
||||
path.join(execDir, "photon", WASM_FILENAME),
|
||||
path.join(process.cwd(), WASM_FILENAME),
|
||||
];
|
||||
}
|
||||
|
||||
function patchPhotonWasmRead(): () => void {
|
||||
const originalReadFileSync: ReadFileSync = fs.readFileSync.bind(fs);
|
||||
const fallbackPaths = getFallbackWasmPaths();
|
||||
const mutableFs = fs as { readFileSync: ReadFileSync };
|
||||
|
||||
const patchedReadFileSync: ReadFileSync = ((...args: Parameters<ReadFileSync>) => {
|
||||
const [file, options] = args;
|
||||
const resolvedPath = pathOrNull(file);
|
||||
|
||||
if (resolvedPath?.endsWith(WASM_FILENAME)) {
|
||||
try {
|
||||
return originalReadFileSync(...args);
|
||||
} catch (error) {
|
||||
const err = error as NodeJS.ErrnoException;
|
||||
if (err?.code && err.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
|
||||
for (const fallbackPath of fallbackPaths) {
|
||||
if (!fs.existsSync(fallbackPath)) {
|
||||
continue;
|
||||
}
|
||||
if (options === undefined) {
|
||||
return originalReadFileSync(fallbackPath);
|
||||
}
|
||||
return originalReadFileSync(fallbackPath, options);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return originalReadFileSync(...args);
|
||||
}) as ReadFileSync;
|
||||
|
||||
try {
|
||||
mutableFs.readFileSync = patchedReadFileSync;
|
||||
} catch {
|
||||
Object.defineProperty(fs, "readFileSync", {
|
||||
value: patchedReadFileSync,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
try {
|
||||
mutableFs.readFileSync = originalReadFileSync;
|
||||
} catch {
|
||||
Object.defineProperty(fs, "readFileSync", {
|
||||
value: originalReadFileSync,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the photon module asynchronously.
|
||||
* Returns cached module on subsequent calls.
|
||||
*/
|
||||
export async function loadPhoton(): Promise<typeof import("@silvia-odwyer/photon-node") | null> {
|
||||
if (photonModule) {
|
||||
return photonModule;
|
||||
}
|
||||
|
||||
if (loadPromise) {
|
||||
return loadPromise;
|
||||
}
|
||||
|
||||
loadPromise = (async () => {
|
||||
const restoreReadFileSync = patchPhotonWasmRead();
|
||||
try {
|
||||
photonModule = await import("@silvia-odwyer/photon-node");
|
||||
return photonModule;
|
||||
} catch {
|
||||
photonModule = null;
|
||||
return photonModule;
|
||||
} finally {
|
||||
restoreReadFileSync();
|
||||
}
|
||||
})();
|
||||
|
||||
return loadPromise;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ function commandExists(cmd: string): boolean {
|
|||
}
|
||||
|
||||
// Get the path to a tool (system-wide or in our tools dir)
|
||||
export function getToolPath(tool: "fd" | "rg"): string | null {
|
||||
function getToolPath(tool: "fd" | "rg"): string | null {
|
||||
const config = TOOLS[tool];
|
||||
if (!config) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export interface TextChunk {
|
|||
* @param maxWidth - Maximum visible width per chunk
|
||||
* @returns Array of chunks with text and position information
|
||||
*/
|
||||
export function wordWrapLine(line: string, maxWidth: number): TextChunk[] {
|
||||
function wordWrapLine(line: string, maxWidth: number): TextChunk[] {
|
||||
if (!line || maxWidth <= 0) {
|
||||
return [{ text: "", startIndex: 0, endIndex: 0 }];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,49 +33,6 @@ export function isPunctuationChar(char: string): boolean {
|
|||
return PUNCTUATION_REGEX.test(char);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract ANSI escape sequences from a string at the given position.
|
||||
*/
|
||||
export function extractAnsiCode(str: string, pos: number): { code: string; length: number } | null {
|
||||
if (pos >= str.length || str[pos] !== "\x1b") return null;
|
||||
|
||||
const next = str[pos + 1];
|
||||
|
||||
// CSI sequence: ESC [ ... m/G/K/H/J
|
||||
if (next === "[") {
|
||||
let j = pos + 2;
|
||||
while (j < str.length && !/[mGKHJ]/.test(str[j]!)) j++;
|
||||
if (j < str.length) return { code: str.substring(pos, j + 1), length: j + 1 - pos };
|
||||
return null;
|
||||
}
|
||||
|
||||
// OSC sequence: ESC ] ... BEL or ESC ] ... ST (ESC \)
|
||||
// Used for hyperlinks (OSC 8), window titles, etc.
|
||||
if (next === "]") {
|
||||
let j = pos + 2;
|
||||
while (j < str.length) {
|
||||
if (str[j] === "\x07") return { code: str.substring(pos, j + 1), length: j + 1 - pos };
|
||||
if (str[j] === "\x1b" && str[j + 1] === "\\") return { code: str.substring(pos, j + 2), length: j + 2 - pos };
|
||||
j++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// APC sequence: ESC _ ... BEL or ESC _ ... ST (ESC \)
|
||||
// Used for cursor marker and application-specific commands
|
||||
if (next === "_") {
|
||||
let j = pos + 2;
|
||||
while (j < str.length) {
|
||||
if (str[j] === "\x07") return { code: str.substring(pos, j + 1), length: j + 1 - pos };
|
||||
if (str[j] === "\x1b" && str[j + 1] === "\\") return { code: str.substring(pos, j + 2), length: j + 2 - pos };
|
||||
j++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Native text module wrappers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue