Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 142 additions & 19 deletions packages/core/src/evaluation/providers/pi-coding-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@

import { execSync } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import { accessSync, createWriteStream } from 'node:fs';
import { accessSync, createWriteStream, mkdirSync } from 'node:fs';
import type { WriteStream } from 'node:fs';
import { mkdir } from 'node:fs/promises';
import path from 'node:path';
import { createInterface } from 'node:readline';
import { fileURLToPath } from 'node:url';
import { fileURLToPath, pathToFileURL } from 'node:url';

import { getAgentvHome } from '../../paths.js';
import { recordPiLogEntry } from './pi-log-tracker.js';
import {
normalizeAzureSdkBaseUrl,
Expand Down Expand Up @@ -57,7 +58,7 @@ async function promptInstall(): Promise<boolean> {
}
}

/** Resolve agentv's own package root (where bun add should install peer deps). */
/** Resolve agentv's own package root (where npm install should add peer deps). */
function findAgentvRoot(): string {
const thisFile = fileURLToPath(import.meta.url);
let dir = path.dirname(thisFile);
Expand All @@ -78,30 +79,144 @@ function findAgentvRoot(): string {
return path.dirname(thisFile);
}

async function doLoadSdkModules(): Promise<void> {
function findManagedSdkInstallRoot(): string {
return path.join(getAgentvHome(), 'deps', 'pi-sdk');
}

function resolveGlobalNpmRoot(): string | undefined {
try {
const root = execSync('npm root -g', {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();
return root.length > 0 ? root : undefined;
} catch {
return undefined;
}
}

function buildGlobalModuleEntry(moduleName: string, globalNpmRoot: string): string {
return path.join(globalNpmRoot, ...moduleName.split('/'), 'dist', 'index.js');
}

function findAccessiblePath(paths: readonly string[]): string | undefined {
for (const candidate of paths) {
try {
accessSync(candidate);
return candidate;
} catch {}
}
return undefined;
}

async function tryImportLocalSdkModules(): Promise<boolean> {
try {
[piCodingAgentModule, piAiModule] = await Promise.all([
import('@mariozechner/pi-coding-agent'),
import('@mariozechner/pi-ai'),
]);
return true;
} catch {
if (await promptInstall()) {
const installDir = findAgentvRoot();
console.error(`Installing @mariozechner/pi-coding-agent into ${installDir}...`);
execSync('bun add @mariozechner/pi-coding-agent', {
cwd: installDir,
stdio: 'inherit',
});
[piCodingAgentModule, piAiModule] = await Promise.all([
import('@mariozechner/pi-coding-agent'),
import('@mariozechner/pi-ai'),
]);
} else {
throw new Error(
'pi-coding-agent SDK is not installed. Install it with:\n bun add @mariozechner/pi-coding-agent',
);
return false;
}
}

async function tryImportManagedSdkModules(): Promise<boolean> {
const managedRoot = findManagedSdkInstallRoot();
const piCodingAgentEntry = findAccessiblePath([
path.join(managedRoot, 'node_modules', '@mariozechner', 'pi-coding-agent', 'dist', 'index.js'),
]);
const piAiEntry = findAccessiblePath([
path.join(managedRoot, 'node_modules', '@mariozechner', 'pi-ai', 'dist', 'index.js'),
path.join(
managedRoot,
'node_modules',
'@mariozechner',
'pi-coding-agent',
'node_modules',
'@mariozechner',
'pi-ai',
'dist',
'index.js',
),
]);

if (!piCodingAgentEntry || !piAiEntry) return false;

try {
[piCodingAgentModule, piAiModule] = await Promise.all([
import(pathToFileURL(piCodingAgentEntry).href),
import(pathToFileURL(piAiEntry).href),
]);
return true;
} catch {
return false;
}
}

async function tryImportGlobalSdkModules(): Promise<boolean> {
const globalNpmRoot = resolveGlobalNpmRoot();
if (!globalNpmRoot) return false;

const piCodingAgentEntry = findAccessiblePath([
buildGlobalModuleEntry('@mariozechner/pi-coding-agent', globalNpmRoot),
]);
const piAiEntry = findAccessiblePath([
buildGlobalModuleEntry('@mariozechner/pi-ai', globalNpmRoot),
path.join(
globalNpmRoot,
'@mariozechner',
'pi-coding-agent',
'node_modules',
'@mariozechner',
'pi-ai',
'dist',
'index.js',
),
]);

if (!piCodingAgentEntry || !piAiEntry) return false;

try {
[piCodingAgentModule, piAiModule] = await Promise.all([
import(pathToFileURL(piCodingAgentEntry).href),
import(pathToFileURL(piAiEntry).href),
]);
return true;
} catch {
return false;
}
}

function installSdkModules(installDir: string): void {
console.error(`Installing @mariozechner/pi-coding-agent into ${installDir} via npm...`);
mkdirSync(installDir, { recursive: true });
execSync('npm install --no-save --no-package-lock @mariozechner/pi-coding-agent', {
cwd: installDir,
stdio: 'inherit',
});
}

async function doLoadSdkModules(): Promise<void> {
if (
(await tryImportLocalSdkModules()) ||
(await tryImportManagedSdkModules()) ||
(await tryImportGlobalSdkModules())
) {
return;
}

if (await promptInstall()) {
const installDir = findManagedSdkInstallRoot();
installSdkModules(installDir);
if (await tryImportManagedSdkModules()) {
return;
}
}

throw new Error(
'pi-coding-agent SDK is not installed. Install it with:\n npm install @mariozechner/pi-coding-agent',
);
}

async function loadSdkModules() {
Expand Down Expand Up @@ -716,3 +831,11 @@ function extractToolCalls(

return toolCalls;
}

/** @internal Exported for testing only. */
export const _internal = {
buildGlobalModuleEntry,
findAgentvRoot,
findManagedSdkInstallRoot,
resolveGlobalNpmRoot,
};
21 changes: 20 additions & 1 deletion packages/core/test/evaluation/providers/pi-coding-agent.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { describe, expect, it } from 'bun:test';

import { PiCodingAgentProvider } from '../../../src/evaluation/providers/pi-coding-agent.js';
import {
PiCodingAgentProvider,
_internal,
} from '../../../src/evaluation/providers/pi-coding-agent.js';

describe('PiCodingAgentProvider', () => {
it('has the correct kind and id', () => {
Expand Down Expand Up @@ -44,4 +47,20 @@ describe('PiCodingAgentProvider', () => {
process.env.OPENAI_BASE_URL = original;
}
});

it('builds the expected global npm module entry path', () => {
expect(
_internal.buildGlobalModuleEntry(
'@mariozechner/pi-coding-agent',
'C:\\npm-global\\node_modules',
),
).toBe('C:\\npm-global\\node_modules\\@mariozechner\\pi-coding-agent\\dist\\index.js');
expect(
_internal.buildGlobalModuleEntry('@mariozechner/pi-ai', 'C:\\npm-global\\node_modules'),
).toBe('C:\\npm-global\\node_modules\\@mariozechner\\pi-ai\\dist\\index.js');
});

it('finds the agentv package root', () => {
expect(_internal.findAgentvRoot().endsWith('packages\\core')).toBe(true);
});
});
Loading