@oh-my-pi/pi-coding-agent 12.17.2 → 12.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,9 +18,6 @@ import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md
18
18
  import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
19
19
  import type { ToolName } from "./tools";
20
20
 
21
- /** Conditional startup debug prints (stderr) when PI_DEBUG_STARTUP is set */
22
- const debugStartup = $env.PI_DEBUG_STARTUP ? (stage: string) => process.stderr.write(`[startup] ${stage}\n`) : () => {};
23
-
24
21
  interface GitContext {
25
22
  isRepo: boolean;
26
23
  currentBranch: string;
@@ -355,21 +352,15 @@ async function saveGpuCache(info: GpuCache): Promise<void> {
355
352
  }
356
353
 
357
354
  async function getCachedGpu(): Promise<string | undefined> {
358
- debugStartup("system-prompt:getEnvironmentInfo:getCachedGpu:start");
359
- const cached = await loadGpuCache();
355
+ const cached = await logger.timeAsync("getCachedGpu:loadGpuCache", loadGpuCache);
360
356
  if (cached) return cached.gpu;
361
- debugStartup("system-prompt:getEnvironmentInfo:getGpuModel");
362
- const gpu = await getGpuModel();
363
- debugStartup("system-prompt:getEnvironmentInfo:saveGpuCache");
364
- if (gpu) await saveGpuCache({ gpu });
357
+ const gpu = await logger.timeAsync("getCachedGpu:getGpuModel", getGpuModel);
358
+ if (gpu) await logger.timeAsync("getCachedGpu:saveGpuCache", saveGpuCache, { gpu });
365
359
  return gpu ?? undefined;
366
360
  }
367
361
  async function getEnvironmentInfo(): Promise<Array<{ label: string; value: string }>> {
368
- debugStartup("system-prompt:getEnvironmentInfo:getCachedGpu");
369
- const gpu = await getCachedGpu();
370
- debugStartup("system-prompt:getEnvironmentInfo:getCpuInfo");
362
+ const gpu = await logger.timeAsync("getEnvironmentInfo:getCachedGpu", getCachedGpu);
371
363
  const cpus = os.cpus();
372
- debugStartup("system-prompt:getEnvironmentInfo:buildEntries");
373
364
  const entries: Array<{ label: string; value: string | undefined }> = [
374
365
  { label: "OS", value: `${os.platform()} ${os.release()}` },
375
366
  { label: "Distro", value: os.type() },
@@ -381,7 +372,6 @@ async function getEnvironmentInfo(): Promise<Array<{ label: string; value: strin
381
372
  { label: "DE", value: getDesktopEnvironment() },
382
373
  { label: "WM", value: getWindowManager() },
383
374
  ];
384
- debugStartup("system-prompt:getEnvironmentInfo:done");
385
375
  return entries.filter((e): e is { label: string; value: string } => e.value != null && e.value !== "unknown");
386
376
  }
387
377
 
@@ -512,33 +502,23 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
512
502
  const preloadedSkills = providedPreloadedSkills;
513
503
 
514
504
  const prepPromise = (async () => {
515
- const systemPromptCustomizationPromise = (async () => {
516
- const customization = await loadSystemPromptFiles({ cwd: resolvedCwd });
517
- debugStartup("system-prompt:loadSystemPromptFiles:done");
518
- return customization;
519
- })();
505
+ const systemPromptCustomizationPromise = logger.timeAsync("loadSystemPromptFiles", loadSystemPromptFiles, {
506
+ cwd: resolvedCwd,
507
+ });
520
508
  const contextFilesPromise = providedContextFiles
521
509
  ? Promise.resolve(providedContextFiles)
522
- : loadProjectContextFiles({ cwd: resolvedCwd });
523
- const agentsMdSearchPromise = buildAgentsMdSearch(resolvedCwd);
510
+ : logger.timeAsync("loadProjectContextFiles", loadProjectContextFiles, { cwd: resolvedCwd });
511
+ const agentsMdSearchPromise = logger.timeAsync("buildAgentsMdSearch", buildAgentsMdSearch, resolvedCwd);
524
512
  const skillsPromise: Promise<Skill[]> =
525
513
  providedSkills !== undefined
526
514
  ? Promise.resolve(providedSkills)
527
515
  : skillsSettings?.enabled !== false
528
516
  ? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).then(result => result.skills)
529
517
  : Promise.resolve([]);
530
- const preloadedSkillContentsPromise = (async () => {
531
- debugStartup("system-prompt:loadPreloadedSkills:start");
532
- const loaded = preloadedSkills ? await loadPreloadedSkillContents(preloadedSkills) : [];
533
- debugStartup("system-prompt:loadPreloadedSkills:done");
534
- return loaded;
535
- })();
536
- const gitPromise = (async () => {
537
- debugStartup("system-prompt:loadGitContext:start");
538
- const loaded = await loadGitContext(resolvedCwd);
539
- debugStartup("system-prompt:loadGitContext:done");
540
- return loaded;
541
- })();
518
+ const preloadedSkillContentsPromise = preloadedSkills
519
+ ? await logger.timeAsync("loadPreloadedSkills", loadPreloadedSkillContents, preloadedSkills)
520
+ : [];
521
+ const gitPromise = logger.timeAsync("loadGitContext", loadGitContext, resolvedCwd);
542
522
 
543
523
  const [
544
524
  resolvedCustomPrompt,
@@ -678,9 +658,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
678
658
  });
679
659
  }
680
660
 
681
- debugStartup("system-prompt:getEnvironmentInfo:start");
682
- const environment = await getEnvironmentInfo();
683
- debugStartup("system-prompt:getEnvironmentInfo:done");
661
+ const environment = await logger.timeAsync("getEnvironmentInfo", getEnvironmentInfo);
684
662
  return renderPromptTemplate(systemPromptTemplate, {
685
663
  tools: toolNamesArray,
686
664
  toolDescriptions,
@@ -276,6 +276,50 @@ class BashInteractiveOverlayComponent implements Component {
276
276
  }
277
277
  }
278
278
 
279
+ const NO_PAGER_ENV = {
280
+ // Disable pagers so commands don't block on interactive views.
281
+ PAGER: "cat",
282
+ GIT_PAGER: "cat",
283
+ MANPAGER: "cat",
284
+ SYSTEMD_PAGER: "cat",
285
+ BAT_PAGER: "cat",
286
+ DELTA_PAGER: "cat",
287
+ GH_PAGER: "cat",
288
+ GLAB_PAGER: "cat",
289
+ PSQL_PAGER: "cat",
290
+ MYSQL_PAGER: "cat",
291
+ AWS_PAGER: "",
292
+ HOMEBREW_PAGER: "cat",
293
+ LESS: "FRX",
294
+ // Disable editor and terminal credential prompts.
295
+ GIT_EDITOR: "true",
296
+ VISUAL: "true",
297
+ EDITOR: "true",
298
+ GIT_TERMINAL_PROMPT: "0",
299
+ SSH_ASKPASS: "/usr/bin/false",
300
+ CI: "1",
301
+ // Package manager defaults for unattended execution.
302
+ npm_config_yes: "true",
303
+ npm_config_update_notifier: "false",
304
+ npm_config_fund: "false",
305
+ npm_config_audit: "false",
306
+ npm_config_progress: "false",
307
+ PNPM_DISABLE_SELF_UPDATE_CHECK: "true",
308
+ PNPM_UPDATE_NOTIFIER: "false",
309
+ YARN_ENABLE_TELEMETRY: "0",
310
+ YARN_ENABLE_PROGRESS_BARS: "0",
311
+ // Cross-language/tooling non-interactive defaults.
312
+ CARGO_TERM_PROGRESS_WHEN: "never",
313
+ DEBIAN_FRONTEND: "noninteractive",
314
+ PIP_NO_INPUT: "1",
315
+ PIP_DISABLE_PIP_VERSION_CHECK: "1",
316
+ TF_INPUT: "0",
317
+ TF_IN_AUTOMATION: "1",
318
+ GH_PROMPT_DISABLED: "1",
319
+ COMPOSER_NO_INTERACTION: "1",
320
+ CLOUDSDK_CORE_DISABLE_PROMPTS: "1",
321
+ };
322
+
279
323
  export async function runInteractiveBashPty(
280
324
  ui: NonNullable<AgentToolContext["ui"]>,
281
325
  options: {
@@ -346,54 +390,14 @@ export async function runInteractiveBashPty(
346
390
  timeoutMs: options.timeoutMs,
347
391
  env: {
348
392
  ...options.env,
349
- // Disable pagers so commands don't block on interactive views.
350
- PAGER: "cat",
351
- GIT_PAGER: "cat",
352
- MANPAGER: "cat",
353
- SYSTEMD_PAGER: "cat",
354
- BAT_PAGER: "cat",
355
- DELTA_PAGER: "cat",
356
- GH_PAGER: "cat",
357
- GLAB_PAGER: "cat",
358
- PSQL_PAGER: "cat",
359
- MYSQL_PAGER: "cat",
360
- AWS_PAGER: "",
361
- HOMEBREW_PAGER: "cat",
362
- LESS: "FRX",
363
- // Disable editor and terminal credential prompts.
364
- GIT_EDITOR: "true",
365
- VISUAL: "true",
366
- EDITOR: "true",
367
- GIT_TERMINAL_PROMPT: "0",
368
- SSH_ASKPASS: "/usr/bin/false",
369
- CI: "1",
370
- // Package manager defaults for unattended execution.
371
- npm_config_yes: "true",
372
- npm_config_update_notifier: "false",
373
- npm_config_fund: "false",
374
- npm_config_audit: "false",
375
- npm_config_progress: "false",
376
- PNPM_DISABLE_SELF_UPDATE_CHECK: "true",
377
- PNPM_UPDATE_NOTIFIER: "false",
378
- YARN_ENABLE_TELEMETRY: "0",
379
- YARN_ENABLE_PROGRESS_BARS: "0",
380
- // Cross-language/tooling non-interactive defaults.
381
- CARGO_TERM_PROGRESS_WHEN: "never",
382
- DEBIAN_FRONTEND: "noninteractive",
383
- PIP_NO_INPUT: "1",
384
- PIP_DISABLE_PIP_VERSION_CHECK: "1",
385
- TF_INPUT: "0",
386
- TF_IN_AUTOMATION: "1",
387
- GH_PROMPT_DISABLED: "1",
388
- COMPOSER_NO_INTERACTION: "1",
389
- CLOUDSDK_CORE_DISABLE_PROMPTS: "1",
393
+ ...NO_PAGER_ENV,
390
394
  },
391
395
  signal: options.signal,
392
396
  cols,
393
397
  rows,
394
398
  },
395
399
  (err, chunk) => {
396
- if (err || !chunk) return;
400
+ if (finished || err || !chunk) return;
397
401
  component.appendOutput(chunk);
398
402
  const normalizedChunk = normalizeCaptureChunk(chunk);
399
403
  pendingChunks = pendingChunks.then(() => sink.push(normalizedChunk)).catch(() => {});
@@ -1,6 +1,6 @@
1
1
  import * as os from "node:os";
2
2
  import * as path from "node:path";
3
- import { getEnvApiKey, StringEnum } from "@oh-my-pi/pi-ai";
3
+ import { getAntigravityHeaders, getEnvApiKey, StringEnum } from "@oh-my-pi/pi-ai";
4
4
  import { $env, ptree, readSseJson, Snowflake, untilAborted } from "@oh-my-pi/pi-utils";
5
5
  import { type Static, Type } from "@sinclair/typebox";
6
6
  import type { ModelRegistry } from "../config/model-registry";
@@ -17,15 +17,6 @@ const DEFAULT_TIMEOUT_SECONDS = 120;
17
17
  const MAX_IMAGE_SIZE = 20 * 1024 * 1024;
18
18
 
19
19
  const ANTIGRAVITY_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
20
- const ANTIGRAVITY_HEADERS = {
21
- "User-Agent": "antigravity/1.11.5 darwin/arm64",
22
- "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
23
- "Client-Metadata": JSON.stringify({
24
- ideType: "IDE_UNSPECIFIED",
25
- platform: "PLATFORM_UNSPECIFIED",
26
- pluginType: "GEMINI",
27
- }),
28
- };
29
20
  const IMAGE_SYSTEM_INSTRUCTION =
30
21
  "You are an AI image generator. Generate images based on user descriptions. Focus on creating high-quality, visually appealing images that match the user's request.";
31
22
 
@@ -678,7 +669,7 @@ export const geminiImageTool: CustomTool<typeof geminiImageSchema, GeminiImageTo
678
669
  Authorization: `Bearer ${apiKey.apiKey}`,
679
670
  "Content-Type": "application/json",
680
671
  Accept: "text/event-stream",
681
- ...ANTIGRAVITY_HEADERS,
672
+ ...getAntigravityHeaders(),
682
673
  },
683
674
  body: JSON.stringify(requestBody),
684
675
  signal: requestSignal,
@@ -13,7 +13,6 @@ import type { ArtifactManager } from "../session/artifacts";
13
13
  import { TaskTool } from "../task";
14
14
  import type { AgentOutputManager } from "../task/output-manager";
15
15
  import type { EventBus } from "../utils/event-bus";
16
- import { time } from "../utils/timings";
17
16
  import { SearchTool } from "../web/search";
18
17
  import { AskTool } from "./ask";
19
18
  import { BashTool } from "./bash";
@@ -225,7 +224,6 @@ function getPythonModeFromEnv(): PythonToolMode | null {
225
224
  * Create tools from BUILTIN_TOOLS registry.
226
225
  */
227
226
  export async function createTools(session: ToolSession, toolNames?: string[]): Promise<Tool[]> {
228
- time("createTools:start");
229
227
  const includeSubmitResult = session.requireSubmitResultTool === true;
230
228
  const enableLsp = session.enableLsp ?? true;
231
229
  const requestedTools = toolNames && toolNames.length > 0 ? [...new Set(toolNames)] : undefined;
@@ -242,8 +240,11 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
242
240
  const isTestEnv = Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
243
241
  const skipPythonWarm = isTestEnv || $env.PI_PYTHON_SKIP_CHECK === "1";
244
242
  if (shouldCheckPython) {
245
- const availability = await checkPythonKernelAvailability(session.cwd);
246
- time("createTools:pythonCheck");
243
+ const availability = await logger.timeAsync(
244
+ "createTools:pythonCheck",
245
+ checkPythonKernelAvailability,
246
+ session.cwd,
247
+ );
247
248
  pythonAvailable = availability.ok;
248
249
  if (!availability.ok) {
249
250
  logger.warn("Python kernel unavailable, falling back to bash", {
@@ -253,8 +254,13 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
253
254
  const sessionFile = session.getSessionFile?.() ?? undefined;
254
255
  const warmSessionId = sessionFile ? `session:${sessionFile}:cwd:${session.cwd}` : `cwd:${session.cwd}`;
255
256
  try {
256
- await warmPythonEnvironment(session.cwd, warmSessionId, session.settings.get("python.sharedGateway"));
257
- time("createTools:warmPython");
257
+ await logger.timeAsync(
258
+ "createTools:warmPython",
259
+ warmPythonEnvironment,
260
+ session.cwd,
261
+ warmSessionId,
262
+ session.settings.get("python.sharedGateway"),
263
+ );
258
264
  } catch (err) {
259
265
  logger.warn("Failed to warm Python environment", {
260
266
  error: err instanceof Error ? err.message : String(err),
@@ -310,30 +316,15 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
310
316
  ...(includeSubmitResult ? ([["submit_result", HIDDEN_TOOLS.submit_result]] as const) : []),
311
317
  ...([["exit_plan_mode", HIDDEN_TOOLS.exit_plan_mode]] as const),
312
318
  ];
313
- time("createTools:beforeFactories");
314
- const slowTools: Array<{ name: string; ms: number }> = [];
319
+
315
320
  const results = await Promise.all(
316
321
  entries.map(async ([name, factory]) => {
317
- const start = Bun.nanoseconds();
318
- const tool = await factory(session);
319
- const elapsed = (Bun.nanoseconds() - start) / 1e6;
320
- if (elapsed > 5) {
321
- slowTools.push({ name, ms: Math.round(elapsed) });
322
+ if (filteredRequestedTools && !filteredRequestedTools.includes(name)) {
323
+ return null;
322
324
  }
323
- return { name, tool };
325
+ const tool = await logger.timeAsync(`createTools:${name}`, factory, session);
326
+ return tool ? wrapToolWithMetaNotice(tool) : null;
324
327
  }),
325
328
  );
326
- time("createTools:afterFactories");
327
- if (slowTools.length > 0 && $env.PI_TIMING === "1") {
328
- logger.debug("Tool factory timings", { slowTools });
329
- }
330
- const tools = results.filter(r => r.tool !== null).map(r => r.tool as Tool);
331
- const wrappedTools = tools.map(wrapToolWithMetaNotice);
332
-
333
- if (filteredRequestedTools !== undefined) {
334
- const allowed = new Set(filteredRequestedTools);
335
- return wrappedTools.filter(tool => allowed.has(tool.name));
336
- }
337
-
338
- return wrappedTools;
329
+ return results.filter((r): r is Tool => r !== null);
339
330
  }
@@ -5,7 +5,7 @@
5
5
  * Requires OAuth credentials stored in agent.db for provider "google-gemini-cli" or "google-antigravity".
6
6
  * Returns synthesized answers with citations and source metadata from grounding chunks.
7
7
  */
8
- import { refreshGoogleCloudToken } from "@oh-my-pi/pi-ai";
8
+ import { getAntigravityHeaders, getGeminiCliHeaders, refreshGoogleCloudToken } from "@oh-my-pi/pi-ai";
9
9
  import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs";
10
10
  import { AgentStorage } from "../../../session/agent-storage";
11
11
  import type { SearchCitation, SearchResponse, SearchSource } from "../../../web/search/types";
@@ -17,28 +17,6 @@ const DEFAULT_ENDPOINT = "https://cloudcode-pa.googleapis.com";
17
17
  const ANTIGRAVITY_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
18
18
  const DEFAULT_MODEL = "gemini-2.5-flash";
19
19
 
20
- // Headers for Gemini CLI (prod endpoint)
21
- const GEMINI_CLI_HEADERS = {
22
- "User-Agent": "google-cloud-sdk vscode_cloudshelleditor/0.1",
23
- "X-Goog-Api-Client": "gl-node/22.17.0",
24
- "Client-Metadata": JSON.stringify({
25
- ideType: "IDE_UNSPECIFIED",
26
- platform: "PLATFORM_UNSPECIFIED",
27
- pluginType: "GEMINI",
28
- }),
29
- };
30
-
31
- // Headers for Antigravity (sandbox endpoint)
32
- const ANTIGRAVITY_HEADERS = {
33
- "User-Agent": "antigravity/1.11.5 darwin/arm64",
34
- "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
35
- "Client-Metadata": JSON.stringify({
36
- ideType: "IDE_UNSPECIFIED",
37
- platform: "PLATFORM_UNSPECIFIED",
38
- pluginType: "GEMINI",
39
- }),
40
- };
41
-
42
20
  export interface GeminiSearchParams {
43
21
  query: string;
44
22
  system_prompt?: string;
@@ -212,7 +190,7 @@ async function callGeminiSearch(
212
190
  }> {
213
191
  const endpoint = auth.isAntigravity ? ANTIGRAVITY_ENDPOINT : DEFAULT_ENDPOINT;
214
192
  const url = `${endpoint}/v1internal:streamGenerateContent?alt=sse`;
215
- const headers = auth.isAntigravity ? ANTIGRAVITY_HEADERS : GEMINI_CLI_HEADERS;
193
+ const headers = auth.isAntigravity ? getAntigravityHeaders() : getGeminiCliHeaders();
216
194
 
217
195
  const requestBody: Record<string, unknown> = {
218
196
  project: auth.projectId,
@@ -1,26 +0,0 @@
1
- /**
2
- * Central timing instrumentation for startup profiling.
3
- * Enable with PI_TIMING=1 or PI_TIMING=1 environment variable.
4
- */
5
- import { $env } from "@oh-my-pi/pi-utils";
6
-
7
- const ENABLED = $env.PI_TIMING === "1";
8
- const timings: Array<{ label: string; ms: number }> = [];
9
- let lastTime = Date.now();
10
-
11
- export function time(label: string): void {
12
- if (!ENABLED) return;
13
- const now = Date.now();
14
- timings.push({ label, ms: now - lastTime });
15
- lastTime = now;
16
- }
17
-
18
- export function printTimings(): void {
19
- if (!ENABLED || timings.length === 0) return;
20
- console.error("\n--- Startup Timings ---");
21
- for (const t of timings) {
22
- console.error(` ${t.label}: ${t.ms}ms`);
23
- }
24
- console.error(` TOTAL: ${timings.reduce((a, b) => a + b.ms, 0)}ms`);
25
- console.error("------------------------\n");
26
- }