diff --git a/packages/pi-agent-core/src/agent-loop.ts b/packages/pi-agent-core/src/agent-loop.ts index fa05a0eff..0e4bdcde9 100644 --- a/packages/pi-agent-core/src/agent-loop.ts +++ b/packages/pi-agent-core/src/agent-loop.ts @@ -22,7 +22,7 @@ import type { StreamFn, } from "./types.js"; -const ZERO_USAGE = { +export const ZERO_USAGE = { input: 0, output: 0, cacheRead: 0, @@ -199,14 +199,7 @@ async function runLoop( api: config.model.api, provider: config.model.provider, model: config.model.id, - usage: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - totalTokens: 0, - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, - }, + usage: ZERO_USAGE, stopReason: signal?.aborted ? "aborted" : "error", errorMessage: errorText, timestamp: Date.now(), diff --git a/packages/pi-agent-core/src/agent.ts b/packages/pi-agent-core/src/agent.ts index 4b9711be9..32f55e30a 100644 --- a/packages/pi-agent-core/src/agent.ts +++ b/packages/pi-agent-core/src/agent.ts @@ -14,7 +14,7 @@ import { type ThinkingBudgets, type Transport, } from "@gsd/pi-ai"; -import { agentLoop, agentLoopContinue } from "./agent-loop.js"; +import { agentLoop, agentLoopContinue, ZERO_USAGE } from "./agent-loop.js"; import type { AgentContext, AgentEvent, @@ -557,14 +557,7 @@ export class Agent { api: model.api, provider: model.provider, model: model.id, - usage: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - totalTokens: 0, - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, - }, + usage: ZERO_USAGE, stopReason: this.abortController?.signal.aborted ? "aborted" : "error", errorMessage: err?.message || String(err), timestamp: Date.now(), diff --git a/packages/pi-agent-core/src/proxy.ts b/packages/pi-agent-core/src/proxy.ts index ebeb7d926..eac488950 100644 --- a/packages/pi-agent-core/src/proxy.ts +++ b/packages/pi-agent-core/src/proxy.ts @@ -15,6 +15,7 @@ import { type StopReason, type ToolCall, } from "@gsd/pi-ai"; +import { ZERO_USAGE } from "./agent-loop.js"; // Create stream class matching ProxyMessageEventStream class ProxyMessageEventStream extends EventStream { @@ -94,14 +95,7 @@ export function streamProxy(model: Model, context: Context, options: ProxyS api: model.api, provider: model.provider, model: model.id, - usage: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - totalTokens: 0, - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, - }, + usage: { ...ZERO_USAGE, cost: { ...ZERO_USAGE.cost } }, timestamp: Date.now(), }; diff --git a/packages/pi-coding-agent/src/core/package-manager.ts b/packages/pi-coding-agent/src/core/package-manager.ts index 483758203..da19d3d94 100644 --- a/packages/pi-coding-agent/src/core/package-manager.ts +++ b/packages/pi-coding-agent/src/core/package-manager.ts @@ -7,6 +7,7 @@ import ignore from "ignore"; import { minimatch } from "minimatch"; import { CONFIG_DIR_NAME } from "../config.js"; import { type GitSource, parseGitUrl } from "../utils/git.js"; +import { toPosixPath } from "../utils/path-display.js"; import type { PackageSource, SettingsManager } from "./settings-manager.js"; const NETWORK_TIMEOUT_MS = 10000; @@ -121,10 +122,6 @@ const IGNORE_FILE_NAMES = [".gitignore", ".ignore", ".fdignore"]; type IgnoreMatcher = ReturnType; -function toPosixPath(p: string): string { - return p.split(sep).join("/"); -} - function prefixIgnorePattern(line: string, prefix: string): string | null { const trimmed = line.trim(); if (!trimmed) return null; diff --git a/packages/pi-coding-agent/src/core/skills.ts b/packages/pi-coding-agent/src/core/skills.ts index 70e1aa647..aa39cf009 100644 --- a/packages/pi-coding-agent/src/core/skills.ts +++ b/packages/pi-coding-agent/src/core/skills.ts @@ -4,6 +4,7 @@ import { homedir } from "os"; import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "path"; import { CONFIG_DIR_NAME, getAgentDir } from "../config.js"; import { parseFrontmatter } from "../utils/frontmatter.js"; +import { toPosixPath } from "../utils/path-display.js"; import type { ResourceDiagnostic } from "./diagnostics.js"; /** Max name length per spec */ @@ -16,10 +17,6 @@ const IGNORE_FILE_NAMES = [".gitignore", ".ignore", ".fdignore"]; type IgnoreMatcher = ReturnType; -function toPosixPath(p: string): string { - return p.split(sep).join("/"); -} - function prefixIgnorePattern(line: string, prefix: string): string | null { const trimmed = line.trim(); if (!trimmed) return null; diff --git a/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts b/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts index f38de288f..3aeae120b 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/session-selector.ts @@ -1,7 +1,6 @@ import { spawnSync } from "node:child_process"; import { existsSync } from "node:fs"; import { unlink } from "node:fs/promises"; -import * as os from "node:os"; import { type Component, Container, @@ -17,21 +16,13 @@ import { import { KeybindingsManager } from "../../../core/keybindings.js"; import type { SessionInfo, SessionListProgress } from "../../../core/session-manager.js"; import { theme } from "../theme/theme.js"; +import { shortenPath } from "../utils/shorten-path.js"; import { DynamicBorder } from "./dynamic-border.js"; import { appKey, appKeyHint, keyHint } from "./keybinding-hints.js"; import { filterAndSortSessions, hasSessionName, type NameFilter, type SortMode } from "./session-selector-search.js"; type SessionScope = "current" | "all"; -function shortenPath(path: string): string { - const home = os.homedir(); - if (!path) return path; - if (path.startsWith(home)) { - return `~${path.slice(home.length)}`; - } - return path; -} - function formatSessionDate(date: Date): string { const now = new Date(); const diffMs = now.getTime() - date.getTime(); diff --git a/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts b/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts index 88514f3b0..80d25b0f0 100644 --- a/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +++ b/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts @@ -1,4 +1,3 @@ -import * as os from "node:os"; import { Box, Container, @@ -18,6 +17,7 @@ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "../../../core/ import { convertToPng } from "../../../utils/image-convert.js"; import { sanitizeBinaryOutput } from "../../../utils/shell.js"; import { getLanguageFromPath, highlightCode, theme } from "../theme/theme.js"; +import { shortenPath } from "../utils/shorten-path.js"; import { renderDiff } from "./diff.js"; import { keyHint } from "./keybinding-hints.js"; import { truncateToVisualLines } from "./visual-truncate.js"; @@ -28,18 +28,6 @@ const BASH_PREVIEW_LINES = 5; // to keep multiline tokenization mostly correct without re-highlighting the full file. const WRITE_PARTIAL_FULL_HIGHLIGHT_LINES = 50; -/** - * Convert absolute path to tilde notation if it's in home directory - */ -function shortenPath(path: unknown): string { - if (typeof path !== "string") return ""; - const home = os.homedir(); - if (path.startsWith(home)) { - return `~${path.slice(home.length)}`; - } - return path; -} - /** * Replace tabs with spaces for consistent rendering */ diff --git a/packages/pi-coding-agent/src/modes/interactive/utils/shorten-path.ts b/packages/pi-coding-agent/src/modes/interactive/utils/shorten-path.ts new file mode 100644 index 000000000..b82a39056 --- /dev/null +++ b/packages/pi-coding-agent/src/modes/interactive/utils/shorten-path.ts @@ -0,0 +1,14 @@ +import * as os from "node:os"; + +/** + * Convert absolute path to tilde notation if it's in home directory. + * Returns empty string for non-string or empty inputs. + */ +export function shortenPath(path: unknown): string { + if (typeof path !== "string" || !path) return ""; + const home = os.homedir(); + if (path.startsWith(home)) { + return `~${path.slice(home.length)}`; + } + return path; +}