fix: extension loading in Bun binary (#681)

- Remove loader exports from index.ts to break circular dependency
- Static import of index.js in loader.ts for virtualModules
- Add @mariozechner/pi-agent-core to virtualModules
- Update @mariozechner/jiti to 2.6.5 (bundles babel for Bun binary)
- Update SDK docs to use correct extension terminology
This commit is contained in:
Mario Zechner 2026-01-13 21:19:41 +01:00
parent 53d9e61bac
commit 843f235251
7 changed files with 37 additions and 67 deletions

7
package-lock.json generated
View file

@ -13,6 +13,7 @@
"packages/coding-agent/examples/extensions/with-deps"
],
"dependencies": {
"@mariozechner/jiti": "^2.6.5",
"@mariozechner/pi-coding-agent": "^0.30.2",
"get-east-asian-width": "^1.4.0"
},
@ -1781,9 +1782,9 @@
}
},
"node_modules/@mariozechner/jiti": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.2.tgz",
"integrity": "sha512-CcFowm/fDWcEMH/F47DQcdawpLQb0nw+WR+hZOv8mgAeACFJxE9uo3cXjUk/5Cl3j23t/oxvtxxUtlBCUIGeQg==",
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/@mariozechner/jiti/-/jiti-2.6.5.tgz",
"integrity": "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw==",
"license": "MIT",
"dependencies": {
"std-env": "^3.10.0",

View file

@ -40,6 +40,7 @@
},
"version": "0.0.3",
"dependencies": {
"@mariozechner/jiti": "^2.6.5",
"@mariozechner/pi-coding-agent": "^0.30.2",
"get-east-asian-width": "^1.4.0"
}

View file

@ -19,6 +19,7 @@
- Fix API key resolution after model switches by using provider argument ([#691](https://github.com/badlogic/pi-mono/pull/691) by [@joshp123](https://github.com/joshp123))
- Fixed z.ai thinking/reasoning: thinking toggle now correctly enables/disables thinking for z.ai models ([#688](https://github.com/badlogic/pi-mono/issues/688))
- Fixed extension loading in compiled Bun binary: extensions with local file imports now work correctly. Updated `@mariozechner/jiti` to v2.6.5 which bundles babel for Bun binary compatibility. ([#681](https://github.com/badlogic/pi-mono/issues/681))
## [0.45.3] - 2026-01-13

View file

@ -735,12 +735,12 @@ import {
discoverAuthStorage,
discoverModels,
discoverSkills,
discoverHooks,
discoverCustomTools,
discoverExtensions,
discoverContextFiles,
discoverPromptTemplates,
loadSettings,
buildSystemPrompt,
createEventBus,
} from "@mariozechner/pi-coding-agent";
// Auth and Models
@ -754,19 +754,16 @@ const builtIn = getModel("anthropic", "claude-opus-4-5"); // Built-in only
// Skills
const { skills, warnings } = discoverSkills(cwd, agentDir, skillsSettings);
// Hooks (async - loads TypeScript)
// Pass eventBus to share pi.events across hooks/tools
// Extensions (async - loads TypeScript)
// Pass eventBus to share pi.events across extensions
const eventBus = createEventBus();
const hooks = await discoverHooks(eventBus, cwd, agentDir);
// Custom tools (async - loads TypeScript)
const tools = await discoverCustomTools(eventBus, cwd, agentDir);
const { extensions, errors } = await discoverExtensions(eventBus, cwd, agentDir);
// Context files
const contextFiles = discoverContextFiles(cwd, agentDir);
// Prompt templates
const commands = discoverPromptTemplates(cwd, agentDir);
const templates = discoverPromptTemplates(cwd, agentDir);
// Settings (global + project merged)
const settings = loadSettings(cwd, agentDir);
@ -816,8 +813,8 @@ import {
SettingsManager,
readTool,
bashTool,
type HookFactory,
type CustomTool,
type ExtensionFactory,
type ToolDefinition,
} from "@mariozechner/pi-coding-agent";
// Set up auth storage (custom location)
@ -831,16 +828,16 @@ if (process.env.MY_KEY) {
// Model registry (no custom models.json)
const modelRegistry = new ModelRegistry(authStorage);
// Inline hook
const auditHook: HookFactory = (api) => {
api.on("tool_call", async (event) => {
// Inline extension
const auditExtension: ExtensionFactory = (pi) => {
pi.on("tool_call", async (event) => {
console.log(`[Audit] ${event.toolName}`);
return undefined;
});
};
// Inline tool
const statusTool: CustomTool = {
const statusTool: ToolDefinition = {
name: "status",
label: "Status",
description: "Get system status",
@ -872,8 +869,8 @@ const { session } = await createAgentSession({
systemPrompt: "You are a minimal assistant. Be concise.",
tools: [readTool, bashTool],
customTools: [{ tool: statusTool }],
hooks: [{ factory: auditHook }],
customTools: [statusTool],
extensions: [auditExtension],
skills: [],
contextFiles: [],
promptTemplates: [],
@ -961,7 +958,7 @@ The SDK is preferred when:
- You want type safety
- You're in the same Node.js process
- You need direct access to agent state
- You want to customize tools/hooks programmatically
- You want to customize tools/extensions programmatically
RPC mode is preferred when:
- You're integrating from another language
@ -984,12 +981,11 @@ discoverModels
// Discovery
discoverSkills
discoverHooks
discoverCustomTools
discoverExtensions
discoverContextFiles
discoverPromptTemplates
// Event Bus (for shared hook/tool communication)
// Event Bus (for shared extension communication)
createEventBus
// Helpers
@ -1015,8 +1011,9 @@ createGrepTool, createFindTool, createLsTool
// Types
type CreateAgentSessionOptions
type CreateAgentSessionResult
type CustomTool
type HookFactory
type ExtensionFactory
type ExtensionAPI
type ToolDefinition
type Skill
type PromptTemplate
type Settings
@ -1024,28 +1021,4 @@ type SkillsSettings
type Tool
```
For hook types, import from the hooks subpath:
```typescript
import type {
HookAPI,
HookMessage,
HookFactory,
HookEventContext,
HookCommandContext,
ToolCallEvent,
ToolResultEvent,
} from "@mariozechner/pi-coding-agent/hooks";
```
For message utilities:
```typescript
import { isHookMessage, createHookMessage } from "@mariozechner/pi-coding-agent";
```
For config utilities:
```typescript
import { getAgentDir } from "@mariozechner/pi-coding-agent/config";
```
For extension types, see [extensions.md](extensions.md) for the full API.

View file

@ -37,9 +37,8 @@ import {
discoverModels,
discoverSkills,
discoverExtensions,
discoverCustomTools,
discoverContextFiles,
discoverSlashCommands,
discoverPromptTemplates,
loadSettings,
buildSystemPrompt,
ModelRegistry,
@ -92,7 +91,7 @@ const { session } = await createAgentSession({
extensions: [{ factory: myExtension }],
skills: [],
contextFiles: [],
slashCommands: [],
promptTemplates: [],
sessionManager: SessionManager.inMemory(),
});
@ -123,7 +122,7 @@ await session.prompt("Hello");
| `additionalExtensionPaths` | `[]` | Merge with discovery |
| `skills` | Discovered | Skills for prompt |
| `contextFiles` | Discovered | AGENTS.md files |
| `slashCommands` | Discovered | File commands |
| `promptTemplates` | Discovered | Prompt templates (slash commands) |
| `sessionManager` | `SessionManager.create(cwd)` | Persistence |
| `settingsManager` | From agentDir | Settings overrides |

View file

@ -10,6 +10,7 @@ import * as os from "node:os";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { createJiti } from "@mariozechner/jiti";
import * as _bundledPiAgentCore from "@mariozechner/pi-agent-core";
import * as _bundledPiAi from "@mariozechner/pi-ai";
import type { KeyId } from "@mariozechner/pi-tui";
import * as _bundledPiTui from "@mariozechner/pi-tui";
@ -18,6 +19,9 @@ import * as _bundledPiTui from "@mariozechner/pi-tui";
// The virtualModules option then makes them available to extensions.
import * as _bundledTypebox from "@sinclair/typebox";
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.
import * as _bundledPiCodingAgent from "../../index.js";
import { createEventBus, type EventBus } from "../event-bus.js";
import type { ExecOptions } from "../exec.js";
import { execCommand } from "../exec.js";
@ -33,19 +37,12 @@ import type {
} from "./types.js";
/** Modules available to extensions via virtualModules (for compiled Bun binary) */
let _lazyPiCodingAgent: unknown;
const VIRTUAL_MODULES: Record<string, unknown> = {
"@sinclair/typebox": _bundledTypebox,
"@mariozechner/pi-agent-core": _bundledPiAgentCore,
"@mariozechner/pi-tui": _bundledPiTui,
"@mariozechner/pi-ai": _bundledPiAi,
// Lazy-loaded to avoid circular dependency (loader.ts is part of pi-coding-agent)
get "@mariozechner/pi-coding-agent"() {
if (!_lazyPiCodingAgent) {
// Dynamic require after module initialization completes
_lazyPiCodingAgent = require("../../index.js");
}
return _lazyPiCodingAgent;
},
"@mariozechner/pi-coding-agent": _bundledPiCodingAgent,
};
const require = createRequire(import.meta.url);
@ -66,6 +63,7 @@ function getAliases(): Record<string, string> {
_aliases = {
"@mariozechner/pi-coding-agent": packageIndex,
"@mariozechner/pi-agent-core": require.resolve("@mariozechner/pi-agent-core"),
"@mariozechner/pi-tui": require.resolve("@mariozechner/pi-tui"),
"@mariozechner/pi-ai": require.resolve("@mariozechner/pi-ai"),
"@sinclair/typebox": typeboxRoot,

View file

@ -88,8 +88,6 @@ export type {
UserBashEventResult,
} from "./core/extensions/index.js";
export {
createExtensionRuntime,
discoverAndLoadExtensions,
ExtensionRunner,
isBashToolResult,
isEditToolResult,
@ -98,7 +96,6 @@ export {
isLsToolResult,
isReadToolResult,
isWriteToolResult,
loadExtensions,
wrapRegisteredTool,
wrapRegisteredTools,
wrapToolsWithExtensions,