fix(typebox): migrate to v1 with extension compat (#3474)

* fix(typebox): migrate to v1 with extension compat

Replace AJV-based validation with TypeBox-native validation, keep legacy extension imports working (including @sinclair/typebox/compiler), and restore coercion for serialized/plain JSON schemas.

This change closes #3112.

* fix(typebox): use canonical imports and harden coercion

Switch first-party code to canonical typebox imports while retaining legacy extension aliases in the loader.

Remove obsolete runtime codegen guards, expand serialized JSON-schema coercion coverage, and update related tests and fixtures.

Fixes #3112.

---------

Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
This commit is contained in:
Armin Ronacher
2026-04-22 19:59:33 +02:00
committed by GitHub
parent a23fab4693
commit 35ff2689ee
82 changed files with 580 additions and 264 deletions

View File

@@ -31,7 +31,7 @@ import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { StringEnum } from "@mariozechner/pi-ai";
import { type ExtensionAPI, getAgentDir, withFileMutationQueue } from "@mariozechner/pi-coding-agent";
import { type Static, Type } from "@sinclair/typebox";
import { type Static, Type } from "typebox";
const PROVIDER = "google-antigravity";

View File

@@ -8,7 +8,7 @@
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
const ECHO_PARAMS = Type.Object({
message: Type.String({ description: "Message to echo" }),

View File

@@ -6,7 +6,7 @@
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
interface OptionWithDesc {
label: string;

View File

@@ -7,7 +7,7 @@
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Editor, type EditorTheme, Key, matchesKey, Text, truncateToWidth } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
// Types
interface QuestionOption {

View File

@@ -6,7 +6,7 @@
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
// Command entrypoint for reload.

View File

@@ -6,7 +6,7 @@
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
// Register a /quit command that cleanly exits pi

View File

@@ -21,7 +21,7 @@ import type { Message } from "@mariozechner/pi-ai";
import { StringEnum } from "@mariozechner/pi-ai";
import { type ExtensionAPI, getMarkdownTheme, withFileMutationQueue } from "@mariozechner/pi-coding-agent";
import { Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js";
const MAX_PARALLEL_TASKS = 8;

View File

@@ -20,7 +20,7 @@
import { StringEnum } from "@mariozechner/pi-ai";
import type { ExtensionAPI, ExtensionContext, Theme, ToolExecutionMode } from "@mariozechner/pi-coding-agent";
import { type Component, matchesKey, Text, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
// Thrown from the tool on illegal actions. The agent runtime surfaces thrown
// errors as tool errors (isError=true) without resetting any of our state.

View File

@@ -13,7 +13,7 @@
import { StringEnum } from "@mariozechner/pi-ai";
import type { ExtensionAPI, ExtensionContext, Theme } from "@mariozechner/pi-coding-agent";
import { matchesKey, Text, truncateToWidth } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
interface Todo {
id: number;

View File

@@ -22,10 +22,10 @@
import type { TextContent } from "@mariozechner/pi-ai";
import { type ExtensionAPI, getAgentDir, withFileMutationQueue } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { constants, readFileSync } from "fs";
import { access, appendFile, readFile } from "fs/promises";
import { join, resolve } from "path";
import { Type } from "typebox";
const LOG_FILE = join(getAgentDir(), "read-access.log");

View File

@@ -25,10 +25,10 @@ import {
withFileMutationQueue,
} from "@mariozechner/pi-coding-agent";
import { Text } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import { execSync } from "child_process";
import { tmpdir } from "os";
import { join } from "path";
import { Type } from "typebox";
const RgParams = Type.Object({
pattern: Type.String({ description: "Search pattern (regex)" }),

View File

@@ -6,8 +6,8 @@
*/
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import ms from "ms";
import { Type } from "typebox";
export default function (pi: ExtensionAPI) {
// Register a tool that uses ms

View File

@@ -44,8 +44,7 @@
"@mariozechner/pi-ai": "^0.68.1",
"@mariozechner/pi-tui": "^0.68.1",
"@silvia-odwyer/photon-node": "^0.3.4",
"@sinclair/typebox": "^0.34.41",
"ajv": "^8.17.1",
"typebox": "^1.1.24",
"chalk": "^5.5.0",
"cli-highlight": "^2.1.11",
"diff": "^8.0.2",

View File

@@ -18,7 +18,9 @@ import * as _bundledPiTui from "@mariozechner/pi-tui";
// Static imports of packages that extensions may use.
// These MUST be static so Bun bundles them into the compiled binary.
// The virtualModules option then makes them available to extensions.
import * as _bundledTypebox from "@sinclair/typebox";
import * as _bundledTypebox from "typebox";
import * as _bundledTypeboxCompile from "typebox/compile";
import * as _bundledTypeboxValue from "typebox/value";
import { getAgentDir, isBunBinary } from "../../config.js";
// NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,
// avoiding a circular dependency. Extensions can import from @mariozechner/pi-coding-agent.
@@ -27,6 +29,7 @@ import { createEventBus, type EventBus } from "../event-bus.js";
import type { ExecOptions } from "../exec.js";
import { execCommand } from "../exec.js";
import { createSyntheticSourceInfo } from "../source-info.js";
import * as _bundledTypeboxCompilerCompat from "./typebox-compiler-compat.js";
import type {
Extension,
ExtensionAPI,
@@ -41,7 +44,14 @@ import type {
/** Modules available to extensions via virtualModules (for compiled Bun binary) */
const VIRTUAL_MODULES: Record<string, unknown> = {
typebox: _bundledTypebox,
"typebox/compile": _bundledTypeboxCompile,
"typebox/value": _bundledTypeboxValue,
"typebox/compiler": _bundledTypeboxCompilerCompat,
"@sinclair/typebox": _bundledTypebox,
"@sinclair/typebox/compile": _bundledTypeboxCompile,
"@sinclair/typebox/value": _bundledTypeboxValue,
"@sinclair/typebox/compiler": _bundledTypeboxCompilerCompat,
"@mariozechner/pi-agent-core": _bundledPiAgentCore,
"@mariozechner/pi-tui": _bundledPiTui,
"@mariozechner/pi-ai": _bundledPiAi,
@@ -56,14 +66,25 @@ const require = createRequire(import.meta.url);
* In Bun binary mode, virtualModules is used instead.
*/
let _aliases: Record<string, string> | null = null;
function resolveTypeboxCompilerCompatPath(baseDir: string): string {
const jsPath = path.resolve(baseDir, "typebox-compiler-compat.js");
if (fs.existsSync(jsPath)) {
return jsPath;
}
return path.resolve(baseDir, "typebox-compiler-compat.ts");
}
function getAliases(): Record<string, string> {
if (_aliases) return _aliases;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const packageIndex = path.resolve(__dirname, "../..", "index.js");
const typeboxEntry = require.resolve("@sinclair/typebox");
const typeboxRoot = typeboxEntry.replace(/[\\/]build[\\/]cjs[\\/]index\.js$/, "");
const typeboxEntry = require.resolve("typebox");
const typeboxCompileEntry = require.resolve("typebox/compile");
const typeboxValueEntry = require.resolve("typebox/value");
const typeboxCompilerCompat = resolveTypeboxCompilerCompatPath(__dirname);
const packagesRoot = path.resolve(__dirname, "../../../../");
const resolveWorkspaceOrImport = (workspaceRelativePath: string, specifier: string): string => {
@@ -80,7 +101,14 @@ function getAliases(): Record<string, string> {
"@mariozechner/pi-tui": resolveWorkspaceOrImport("tui/dist/index.js", "@mariozechner/pi-tui"),
"@mariozechner/pi-ai": resolveWorkspaceOrImport("ai/dist/index.js", "@mariozechner/pi-ai"),
"@mariozechner/pi-ai/oauth": resolveWorkspaceOrImport("ai/dist/oauth.js", "@mariozechner/pi-ai/oauth"),
"@sinclair/typebox": typeboxRoot,
typebox: typeboxEntry,
"typebox/compile": typeboxCompileEntry,
"typebox/value": typeboxValueEntry,
"typebox/compiler": typeboxCompilerCompat,
"@sinclair/typebox": typeboxEntry,
"@sinclair/typebox/compile": typeboxCompileEntry,
"@sinclair/typebox/value": typeboxValueEntry,
"@sinclair/typebox/compiler": typeboxCompilerCompat,
};
return _aliases;

View File

@@ -0,0 +1,19 @@
import { Code, Compile, Validator } from "typebox/compile";
export { Code, Compile, Validator };
// Legacy @sinclair/typebox/compiler compatibility for extensions.
export const TypeCompiler = {
Compile,
};
// In TypeBox 0.x this was a named export. Map it to the v1 Validator class.
export const TypeCheck = Validator;
export default {
Code,
Compile,
Validator,
TypeCompiler,
TypeCheck,
};

View File

@@ -39,7 +39,7 @@ import type {
OverlayOptions,
TUI,
} from "@mariozechner/pi-tui";
import type { Static, TSchema } from "@sinclair/typebox";
import type { Static, TSchema } from "typebox";
import type { Theme } from "../../modes/interactive/theme/theme.js";
import type { BashResult } from "../bash-executor.js";
import type { CompactionPreparation, CompactionResult } from "../compaction/index.js";
@@ -479,7 +479,7 @@ type AnyToolDefinition = ToolDefinition<any, any, any>;
export function defineTool<TParams extends TSchema, TDetails = unknown, TState = any>(
tool: ToolDefinition<TParams, TDetails, TState>,
): ToolDefinition<TParams, TDetails, TState> & AnyToolDefinition {
return tool;
return tool as ToolDefinition<TParams, TDetails, TState> & AnyToolDefinition;
}
// ============================================================================

View File

@@ -18,10 +18,11 @@ import {
type SimpleStreamOptions,
} from "@mariozechner/pi-ai";
import { registerOAuthProvider, resetOAuthProviders } from "@mariozechner/pi-ai/oauth";
import { type Static, Type } from "@sinclair/typebox";
import AjvModule from "ajv";
import { existsSync, readFileSync } from "fs";
import { join } from "path";
import { type Static, Type } from "typebox";
import { Compile } from "typebox/compile";
import type { TLocalizedValidationError } from "typebox/error";
import { getAgentDir } from "../config.js";
import type { AuthStorage } from "./auth-storage.js";
import {
@@ -31,9 +32,6 @@ import {
resolveHeadersOrThrow,
} from "./resolve-config-value.js";
const Ajv = (AjvModule as any).default || AjvModule;
const ajv = new Ajv();
// Schema for OpenRouter routing preferences
const PercentileCutoffsSchema = Type.Object({
p50: Type.Optional(Type.Number()),
@@ -179,10 +177,23 @@ const ModelsConfigSchema = Type.Object({
providers: Type.Record(Type.String(), ProviderConfigSchema),
});
ajv.addSchema(ModelsConfigSchema, "ModelsConfig");
const validateModelsConfig = Compile(ModelsConfigSchema);
type ModelsConfig = Static<typeof ModelsConfigSchema>;
function formatValidationPath(error: TLocalizedValidationError): string {
if (error.keyword === "required") {
const requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;
const requiredProperty = requiredProperties?.[0];
if (requiredProperty) {
const basePath = error.instancePath.replace(/^\//, "").replace(/\//g, ".");
return basePath ? `${basePath}.${requiredProperty}` : requiredProperty;
}
}
const path = error.instancePath.replace(/^\//, "").replace(/\//g, ".");
return path || "root";
}
/** Provider override config (baseUrl, compat) without request auth/headers */
interface ProviderOverride {
baseUrl?: string;
@@ -417,17 +428,19 @@ export class ModelRegistry {
try {
const content = readFileSync(modelsJsonPath, "utf-8");
const config: ModelsConfig = JSON.parse(content);
const parsed = JSON.parse(content) as unknown;
// Validate schema
const validate = ajv.getSchema("ModelsConfig")!;
if (!validate(config)) {
if (!validateModelsConfig.Check(parsed)) {
const errors =
validate.errors?.map((e: any) => ` - ${e.instancePath || "root"}: ${e.message}`).join("\n") ||
"Unknown schema error";
validateModelsConfig
.Errors(parsed)
.map((error) => ` - ${formatValidationPath(error)}: ${error.message}`)
.join("\n") || "Unknown schema error";
return emptyCustomModelsResult(`Invalid models.json schema:\n${errors}\n\nFile: ${modelsJsonPath}`);
}
const config = parsed as ModelsConfig;
// Additional validation
this.validateConfig(config);

View File

@@ -4,8 +4,8 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { Container, Text, truncateToWidth } from "@mariozechner/pi-tui";
import { type Static, Type } from "@sinclair/typebox";
import { spawn } from "child_process";
import { type Static, Type } from "typebox";
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
import { truncateToVisualLines } from "../../modes/interactive/components/visual-truncate.js";
import { theme } from "../../modes/interactive/theme/theme.js";

View File

@@ -1,8 +1,8 @@
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { Box, Container, Spacer, Text } from "@mariozechner/pi-tui";
import { type Static, Type } from "@sinclair/typebox";
import { constants } from "fs";
import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
import { type Static, Type } from "typebox";
import { renderDiff } from "../../modes/interactive/components/diff.js";
import type { ToolDefinition } from "../extensions/types.js";
import {

View File

@@ -1,10 +1,10 @@
import { createInterface } from "node:readline";
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { Text } from "@mariozechner/pi-tui";
import { type Static, Type } from "@sinclair/typebox";
import { spawn } from "child_process";
import { existsSync } from "fs";
import path from "path";
import { type Static, Type } from "typebox";
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
import { ensureTool } from "../../utils/tools-manager.js";
import type { ToolDefinition, ToolRenderResultOptions } from "../extensions/types.js";

View File

@@ -1,10 +1,10 @@
import { createInterface } from "node:readline";
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { Text } from "@mariozechner/pi-tui";
import { type Static, Type } from "@sinclair/typebox";
import { spawn } from "child_process";
import { readFileSync, statSync } from "fs";
import path from "path";
import { type Static, Type } from "typebox";
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
import { ensureTool } from "../../utils/tools-manager.js";
import type { ToolDefinition, ToolRenderResultOptions } from "../extensions/types.js";

View File

@@ -1,8 +1,8 @@
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { Text } from "@mariozechner/pi-tui";
import { type Static, Type } from "@sinclair/typebox";
import { existsSync, readdirSync, statSync } from "fs";
import nodePath from "path";
import { type Static, Type } from "typebox";
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
import type { ToolDefinition, ToolRenderResultOptions } from "../extensions/types.js";
import { resolveToCwd } from "./path-utils.js";

View File

@@ -1,9 +1,9 @@
import type { AgentTool } from "@mariozechner/pi-agent-core";
import type { Api, ImageContent, Model, TextContent } from "@mariozechner/pi-ai";
import { Text } from "@mariozechner/pi-tui";
import { type Static, Type } from "@sinclair/typebox";
import { constants } from "fs";
import { access as fsAccess, readFile as fsReadFile } from "fs/promises";
import { type Static, Type } from "typebox";
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
import { getLanguageFromPath, highlightCode } from "../../modes/interactive/theme/theme.js";
import { formatDimensionNote, resizeImage } from "../../utils/image-resize.js";

View File

@@ -1,8 +1,8 @@
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { Container, Text } from "@mariozechner/pi-tui";
import { type Static, Type } from "@sinclair/typebox";
import { mkdir as fsMkdir, writeFile as fsWriteFile } from "fs/promises";
import { dirname } from "path";
import { type Static, Type } from "typebox";
import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
import { getLanguageFromPath, highlightCode } from "../../modes/interactive/theme/theme.js";
import type { ToolDefinition, ToolRenderResultOptions } from "../extensions/types.js";

View File

@@ -1,10 +1,10 @@
import * as fs from "node:fs";
import * as path from "node:path";
import type { EditorTheme, MarkdownTheme, SelectListTheme } from "@mariozechner/pi-tui";
import { type Static, Type } from "@sinclair/typebox";
import { TypeCompiler } from "@sinclair/typebox/compiler";
import chalk from "chalk";
import { highlight, supportsLanguage } from "cli-highlight";
import { type Static, Type } from "typebox";
import { Compile } from "typebox/compile";
import { getCustomThemesDir, getThemesDir } from "../../../config.js";
import type { SourceInfo } from "../../../core/source-info.js";
@@ -94,7 +94,7 @@ const ThemeJsonSchema = Type.Object({
type ThemeJson = Static<typeof ThemeJsonSchema>;
const validateThemeJson = TypeCompiler.Compile(ThemeJsonSchema);
const validateThemeJson = Compile(ThemeJsonSchema);
export type ThemeColor =
| "accent"
@@ -515,23 +515,29 @@ export function getAvailableThemesWithPaths(): ThemeInfo[] {
function parseThemeJson(label: string, json: unknown): ThemeJson {
if (!validateThemeJson.Check(json)) {
const errors = Array.from(validateThemeJson.Errors(json));
const missingColors: string[] = [];
const missingColors = new Set<string>();
const otherErrors: string[] = [];
for (const e of errors) {
// Check for missing required color properties
const match = e.path.match(/^\/colors\/(\w+)$/);
if (match && e.message.includes("Required")) {
missingColors.push(match[1]);
} else {
otherErrors.push(` - ${e.path}: ${e.message}`);
for (const error of errors) {
if (error.keyword === "required" && error.instancePath === "/colors") {
const requiredProperties = (error.params as { requiredProperties?: string[] }).requiredProperties;
for (const requiredProperty of requiredProperties ?? []) {
missingColors.add(requiredProperty);
}
continue;
}
const path = error.instancePath || "/";
otherErrors.push(` - ${path}: ${error.message}`);
}
let errorMessage = `Invalid theme "${label}":\n`;
if (missingColors.length > 0) {
if (missingColors.size > 0) {
errorMessage += "\nMissing required color tokens:\n";
errorMessage += missingColors.map((c) => ` - ${c}`).join("\n");
errorMessage += Array.from(missingColors)
.sort()
.map((color) => ` - ${color}`)
.join("\n");
errorMessage += '\n\nPlease add these colors to your theme\'s "colors" object.';
errorMessage += "\nSee the built-in themes (dark.json, light.json) for reference values.";
}

View File

@@ -14,7 +14,7 @@ import {
type ImageContent,
type TextContent,
} from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { AgentSession } from "../src/core/agent-session.js";
import { AuthStorage } from "../src/core/auth-storage.js";

View File

@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { getModel } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { DefaultResourceLoader } from "../src/core/resource-loader.js";
import { createAgentSession } from "../src/core/sdk.js";

View File

@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { Agent, type AgentEvent, type AgentTool } from "@mariozechner/pi-agent-core";
import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { AgentSession } from "../src/core/agent-session.js";
import { AuthStorage } from "../src/core/auth-storage.js";

View File

@@ -28,7 +28,7 @@ describe("extensions discovery", () => {
`;
const extensionCodeWithTool = (toolName: string) => `
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function(pi) {
pi.registerTool({
name: "${toolName}",

View File

@@ -291,7 +291,7 @@ describe("ExtensionRunner", () => {
describe("tool collection", () => {
it("collects tools from multiple extensions", async () => {
const toolCode = (name: string) => `
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function(pi) {
pi.registerTool({
name: "${name}",
@@ -315,7 +315,7 @@ describe("ExtensionRunner", () => {
it("keeps first tool when two extensions register the same name", async () => {
const first = `
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function(pi) {
pi.registerTool({
name: "shared",
@@ -327,7 +327,7 @@ describe("ExtensionRunner", () => {
}
`;
const second = `
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function(pi) {
pi.registerTool({
name: "shared",

View File

@@ -471,7 +471,7 @@ Content`,
join(ext1Dir, "index.ts"),
`
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function(pi: ExtensionAPI) {
pi.registerTool({
name: "duplicate-tool",
@@ -486,7 +486,7 @@ export default function(pi: ExtensionAPI) {
join(ext2Dir, "index.ts"),
`
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function(pi: ExtensionAPI) {
pi.registerTool({
name: "duplicate-tool",
@@ -513,7 +513,7 @@ export default function(pi: ExtensionAPI) {
join(globalExtDir, "global.ts"),
`
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function(pi: ExtensionAPI) {
pi.registerTool({
name: "duplicate-tool",
@@ -532,7 +532,7 @@ export default function(pi: ExtensionAPI) {
explicitExtPath,
`
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
export default function(pi: ExtensionAPI) {
pi.registerTool({
name: "duplicate-tool",

View File

@@ -60,6 +60,7 @@ async function runCli(args: string[]): Promise<{ stdout: string; stderr: string;
env: {
...process.env,
[ENV_AGENT_DIR]: agentDir,
TSX_TSCONFIG_PATH: resolve(__dirname, "../../../tsconfig.json"),
},
stdio: ["ignore", "pipe", "pipe"],
});

View File

@@ -1,7 +1,7 @@
import { Buffer } from "node:buffer";
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { fauxAssistantMessage, fauxToolCall } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, describe, expect, it } from "vitest";
import type { BashOperations } from "../../src/core/tools/bash.js";
import { createHarness, type Harness } from "./harness.js";

View File

@@ -1,6 +1,6 @@
import type { AgentTool, ThinkingLevel } from "@mariozechner/pi-agent-core";
import { fauxAssistantMessage, fauxToolCall, type Model } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, describe, expect, it } from "vitest";
import type { ExtensionAPI } from "../../src/index.js";
import { createHarness, getAssistantTexts, type Harness } from "./harness.js";

View File

@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { fauxAssistantMessage, fauxToolCall, type Model } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, describe, expect, it } from "vitest";
import type { PromptTemplate } from "../../src/core/prompt-templates.js";
import { createSyntheticSourceInfo } from "../../src/core/source-info.js";

View File

@@ -1,7 +1,7 @@
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { fauxAssistantMessage, fauxToolCall } from "@mariozechner/pi-ai";
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, describe, expect, it } from "vitest";
import { createHarness, getAssistantTexts, getMessageText, getUserTexts, type Harness } from "./harness.js";

View File

@@ -1,6 +1,6 @@
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { fauxAssistantMessage, fauxThinking, fauxToolCall } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, describe, expect, it } from "vitest";
import { createHarness, type Harness } from "./harness.js";

View File

@@ -1,7 +1,7 @@
import type { AgentTool } from "@mariozechner/pi-agent-core";
import { fauxAssistantMessage, fauxToolCall } from "@mariozechner/pi-ai";
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, describe, expect, it } from "vitest";
import { createHarness, getAssistantTexts, getUserTexts, type Harness } from "../harness.js";

View File

@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { getModel } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { DefaultResourceLoader } from "../../../src/core/resource-loader.js";
import { createAgentSession } from "../../../src/core/sdk.js";

View File

@@ -5,7 +5,7 @@
import type { AgentTool } from "@mariozechner/pi-agent-core";
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { Type } from "typebox";
import { afterEach, describe, expect, it } from "vitest";
import { createHarness, createHarnessWithExtensions, type Harness } from "./test-harness.js";

View File

@@ -1,6 +1,6 @@
import { Text, type TUI } from "@mariozechner/pi-tui";
import { Type } from "@sinclair/typebox";
import stripAnsi from "strip-ansi";
import { Type } from "typebox";
import { beforeAll, describe, expect, test } from "vitest";
import type { ToolDefinition } from "../src/core/extensions/types.js";
import { type BashOperations, createBashToolDefinition } from "../src/core/tools/bash.js";

View File

@@ -7,7 +7,7 @@
"@mariozechner/pi-coding-agent/hooks": ["./src/core/hooks/index.ts"],
"@mariozechner/pi-tui": ["../tui/src/index.ts"],
"@mariozechner/pi-ai": ["../ai/src/index.ts"],
"@sinclair/typebox": ["../../node_modules/@sinclair/typebox"]
"typebox": ["../../node_modules/typebox"]
},
"skipLibCheck": true
},

View File

@@ -1,14 +1,26 @@
import { defineConfig } from 'vitest/config';
import { fileURLToPath } from "node:url";
import { defineConfig } from "vitest/config";
const aiSrcIndex = fileURLToPath(new URL("../ai/src/index.ts", import.meta.url));
const aiSrcOAuth = fileURLToPath(new URL("../ai/src/oauth.ts", import.meta.url));
const agentSrcIndex = fileURLToPath(new URL("../agent/src/index.ts", import.meta.url));
export default defineConfig({
test: {
globals: true,
environment: 'node',
testTimeout: 30000, // 30 seconds for API calls
server: {
deps: {
external: [/@silvia-odwyer\/photon-node/],
},
},
},
test: {
globals: true,
environment: "node",
testTimeout: 30000,
server: {
deps: {
external: [/@silvia-odwyer\/photon-node/],
},
},
},
resolve: {
alias: [
{ find: /^@mariozechner\/pi-ai$/, replacement: aiSrcIndex },
{ find: /^@mariozechner\/pi-ai\/oauth$/, replacement: aiSrcOAuth },
{ find: /^@mariozechner\/pi-agent-core$/, replacement: agentSrcIndex },
],
},
});