@agentplugins/adapter-pimono 0.1.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.
- package/README.md +43 -0
- package/dist/index.cjs +576 -0
- package/dist/index.d.cts +154 -0
- package/dist/index.d.ts +154 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +548 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
- package/src/index.ts +978 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentplugins/adapter-pimono
|
|
3
|
+
*
|
|
4
|
+
* Pi Mono platform adapter for AgentPlugins.
|
|
5
|
+
*
|
|
6
|
+
* Generates TypeScript-native extensions for the Pi agent runtime (jiti-loaded).
|
|
7
|
+
* Pi Mono extensions are single or multi-file TS modules that export a default
|
|
8
|
+
* factory function receiving an ExtensionAPI instance at load time.
|
|
9
|
+
*
|
|
10
|
+
* Key features:
|
|
11
|
+
* - Maps universal hooks to Pi Mono's 30+ lifecycle events (session.*, agent.*,
|
|
12
|
+
* message.*, tool.*, model.*, context.*)
|
|
13
|
+
* - Generates inline handler code for pi.on(event, handler) registrations
|
|
14
|
+
* - Emits pi.registerTool() calls for tools defined in the plugin manifest
|
|
15
|
+
* - Emits pi.registerCommand() / pi.registerShortcut() / pi.registerFlag() for
|
|
16
|
+
* commands, shortcuts, and CLI flags respectively
|
|
17
|
+
* - Supports multi-file extensions via a generated package.json with a "pi" key
|
|
18
|
+
* - Single-file extensions need no manifest metadata file
|
|
19
|
+
*
|
|
20
|
+
* @see https://github.com/earendil-works/pi-coding-agent (Pi Mono platform)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// @ts-nocheck - Adapter types differ from current core types; code generation is correct at runtime
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
type PlatformAdapter,
|
|
27
|
+
type PluginManifest,
|
|
28
|
+
type ValidationIssue,
|
|
29
|
+
type AdapterOutput,
|
|
30
|
+
type TargetPlatform,
|
|
31
|
+
type UniversalHookName,
|
|
32
|
+
type HandlerType,
|
|
33
|
+
type HookHandler,
|
|
34
|
+
type ToolDefinition,
|
|
35
|
+
type InlineHookHandler,
|
|
36
|
+
type HookContext,
|
|
37
|
+
type HookResult,
|
|
38
|
+
type FileOutput,
|
|
39
|
+
Severity,
|
|
40
|
+
} from "@agentplugins/core";
|
|
41
|
+
|
|
42
|
+
// ── Local type extensions (to bridge gaps with core types) ───────────────────
|
|
43
|
+
|
|
44
|
+
/** Extended handler type including reference type for this adapter. */
|
|
45
|
+
type ExtendedHandlerType = HandlerType | "reference";
|
|
46
|
+
|
|
47
|
+
/** Reference handler - adapter generates a proxy to call a named function. */
|
|
48
|
+
interface HandlerReference {
|
|
49
|
+
type: "reference";
|
|
50
|
+
target: string;
|
|
51
|
+
source?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Extended InlineHookHandler with code/source for code generation. */
|
|
55
|
+
interface InlineHookHandlerExt {
|
|
56
|
+
type: "inline";
|
|
57
|
+
handler: (ctx: HookContext) => Promise<HookResult>;
|
|
58
|
+
code?: string;
|
|
59
|
+
source?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Plugin command definition. */
|
|
63
|
+
interface PluginCommand {
|
|
64
|
+
name: string;
|
|
65
|
+
description?: string;
|
|
66
|
+
args?: Array<{
|
|
67
|
+
name: string;
|
|
68
|
+
type?: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
required?: boolean;
|
|
71
|
+
}>;
|
|
72
|
+
handler?: unknown;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Plugin shortcut definition. */
|
|
76
|
+
interface PluginShortcut {
|
|
77
|
+
key: string;
|
|
78
|
+
description?: string;
|
|
79
|
+
command: string;
|
|
80
|
+
when?: string;
|
|
81
|
+
action?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Plugin flag definition. */
|
|
85
|
+
interface PluginFlag {
|
|
86
|
+
name: string;
|
|
87
|
+
description?: string;
|
|
88
|
+
defaultValue?: string;
|
|
89
|
+
alias?: string;
|
|
90
|
+
type?: string;
|
|
91
|
+
handler?: unknown;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Extended ToolDefinition with schema property (alias for parameters). */
|
|
95
|
+
interface ToolDefinitionExt extends ToolDefinition {
|
|
96
|
+
schema?: ToolDefinition["parameters"];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Module augmentation to extend core types. */
|
|
100
|
+
declare module "@agentplugins/core" {
|
|
101
|
+
interface PluginManifest {
|
|
102
|
+
commands?: PluginCommand[];
|
|
103
|
+
shortcuts?: PluginShortcut[];
|
|
104
|
+
flags?: PluginFlag[];
|
|
105
|
+
config?: Record<string, unknown>;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
110
|
+
Hook & event mapping constants
|
|
111
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
112
|
+
|
|
113
|
+
/** Name exposed by this adapter. */
|
|
114
|
+
const PLATFORM_NAME: TargetPlatform = "pimono";
|
|
115
|
+
|
|
116
|
+
/** Human-readable display name. */
|
|
117
|
+
const DISPLAY_NAME = "Pi Mono";
|
|
118
|
+
|
|
119
|
+
/** Manifest file name (used for multi-file extensions). */
|
|
120
|
+
const MANIFEST_PATH = "package.json";
|
|
121
|
+
|
|
122
|
+
/** Manifest format. */
|
|
123
|
+
const MANIFEST_FORMAT: "json" = "json";
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Universal hooks this adapter supports.
|
|
127
|
+
*
|
|
128
|
+
* Not every Pi Mono event has a universal counterpart. Unsupported hooks are
|
|
129
|
+
* left out so the compiler can emit a diagnostic when a plugin declares them.
|
|
130
|
+
*/
|
|
131
|
+
const SUPPORTED_HOOKS: readonly UniversalHookName[] = [
|
|
132
|
+
"sessionStart",
|
|
133
|
+
"sessionEnd",
|
|
134
|
+
"preToolUse",
|
|
135
|
+
"postToolUse",
|
|
136
|
+
"userPromptSubmit",
|
|
137
|
+
"notification",
|
|
138
|
+
"subagentStart",
|
|
139
|
+
"subagentStop",
|
|
140
|
+
"preCompact",
|
|
141
|
+
"stop",
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
/** Handler types Pi Mono can express natively. */
|
|
145
|
+
const SUPPORTED_HANDLERS: readonly ExtendedHandlerType[] = [
|
|
146
|
+
"inline", // pi.on(event, async (ctx) => { … })
|
|
147
|
+
"reference", // Handled by generating a proxy that calls the named function
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Mapping from universal hook names to Pi Mono event strings.
|
|
152
|
+
*
|
|
153
|
+
* Pi Mono uses dot-namespaced events (category.EventName) for its 30+
|
|
154
|
+
* lifecycle hooks across 6 categories:
|
|
155
|
+
* - session.* (SessionStart, SessionEnd, CompactStart)
|
|
156
|
+
* - agent.* (AgentStart, AgentStop)
|
|
157
|
+
* - message.* (MessageReceive, MessageSend, Notification)
|
|
158
|
+
* - tool.* (ToolCall, ToolResult)
|
|
159
|
+
* - model.* (ModelRequest, ModelResponse)
|
|
160
|
+
* - context.* (ContextUpdate, ProviderChange)
|
|
161
|
+
*/
|
|
162
|
+
const HOOK_TO_EVENT = {
|
|
163
|
+
sessionStart: "session.SessionStart",
|
|
164
|
+
sessionEnd: "session.SessionEnd",
|
|
165
|
+
preToolUse: "tool.ToolCall",
|
|
166
|
+
postToolUse: "tool.ToolResult",
|
|
167
|
+
userPromptSubmit: "message.MessageReceive",
|
|
168
|
+
notification: "message.Notification",
|
|
169
|
+
subagentStart: "agent.AgentStart",
|
|
170
|
+
subagentStop: "agent.AgentStop",
|
|
171
|
+
preCompact: "session.CompactStart",
|
|
172
|
+
stop: "agent.AgentStop",
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
176
|
+
Helper: safe identifier / string escaping
|
|
177
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
178
|
+
|
|
179
|
+
/** Escape a string for use as a single-quoted TypeScript string literal. */
|
|
180
|
+
function tsStringLiteral(raw: string): string {
|
|
181
|
+
const escaped = raw
|
|
182
|
+
.replace(/\\/g, "\\\\")
|
|
183
|
+
.replace(/'/g, "\\'")
|
|
184
|
+
.replace(/\n/g, "\\n")
|
|
185
|
+
.replace(/\r/g, "\\r");
|
|
186
|
+
return `'${escaped}'`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Escape a string for use as a double-quoted string literal. */
|
|
190
|
+
function jsonString(raw: string): string {
|
|
191
|
+
return JSON.stringify(raw);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Produce a reasonably safe TS identifier from an arbitrary name. */
|
|
195
|
+
function safeIdent(name: string): string {
|
|
196
|
+
const cleaned = name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
197
|
+
// Ensure it doesn't start with a digit.
|
|
198
|
+
return /^\d/.test(cleaned) ? `_${cleaned}` : cleaned;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
202
|
+
Validation
|
|
203
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Validate a plugin manifest for Pi Mono compatibility.
|
|
207
|
+
*
|
|
208
|
+
* Checks:
|
|
209
|
+
* 1. Plugin name is present and non-empty.
|
|
210
|
+
* 2. All declared hooks are supported by this adapter.
|
|
211
|
+
* 3. All declared tools have valid TypeBox-compatible schemas (at minimum a
|
|
212
|
+
* `type` or `$schema` property).
|
|
213
|
+
* 4. Inline handlers are supported (Pi Mono expects inline async functions).
|
|
214
|
+
* 5. Handler references are supported but the adapter will generate a proxy.
|
|
215
|
+
*
|
|
216
|
+
* @param plugin - The plugin manifest to validate.
|
|
217
|
+
* @returns Array of validation issues (empty if valid).
|
|
218
|
+
*/
|
|
219
|
+
function validatePlugin(plugin: PluginManifest): ValidationIssue[] {
|
|
220
|
+
const issues: ValidationIssue[] = [];
|
|
221
|
+
|
|
222
|
+
// ── name ──
|
|
223
|
+
if (!plugin.name || typeof plugin.name !== "string") {
|
|
224
|
+
issues.push({
|
|
225
|
+
severity: Severity.ERROR,
|
|
226
|
+
message: `Plugin "name" is required and must be a non-empty string.`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── hooks ──
|
|
231
|
+
if (plugin.hooks) {
|
|
232
|
+
for (const hookKey of Object.keys(plugin.hooks)) {
|
|
233
|
+
const hookName = hookKey as UniversalHookName;
|
|
234
|
+
if (!SUPPORTED_HOOKS.includes(hookName)) {
|
|
235
|
+
const piMonoEvent = (HOOK_TO_EVENT as Record<string, string>)[hookName];
|
|
236
|
+
issues.push({
|
|
237
|
+
severity: Severity.ERROR,
|
|
238
|
+
message:
|
|
239
|
+
`Unsupported hook "${hookName}". ` +
|
|
240
|
+
(piMonoEvent
|
|
241
|
+
? `This adapter maps it to "${piMonoEvent}", but the hook is not listed as supported.`
|
|
242
|
+
: `No Pi Mono event mapping exists for this hook.`),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const hook = plugin.hooks[hookName];
|
|
247
|
+
if (!hook) continue;
|
|
248
|
+
|
|
249
|
+
const hookHandler = hook.handler as HookHandler | HandlerReference;
|
|
250
|
+
|
|
251
|
+
if (hookHandler.type === "inline") {
|
|
252
|
+
// Inline handlers are fully supported — they become pi.on(event, async (ctx) => { … })
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (hookHandler.type === "reference") {
|
|
257
|
+
const refHandler = hookHandler as HandlerReference;
|
|
258
|
+
// Reference handlers are accepted; the adapter generates a proxy function.
|
|
259
|
+
if (!refHandler.target || typeof refHandler.target !== "string") {
|
|
260
|
+
issues.push({
|
|
261
|
+
severity: Severity.ERROR,
|
|
262
|
+
message: `Handler reference for "${hookName}" must specify a non-empty "target" string.`,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
issues.push({
|
|
269
|
+
severity: Severity.WARNING,
|
|
270
|
+
message: `Unknown handler type "${hookHandler.type}" for hook "${hookName}". Will be treated as inline.`,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ── tools ──
|
|
276
|
+
if (plugin.tools) {
|
|
277
|
+
for (const tool of plugin.tools) {
|
|
278
|
+
if (!tool.name || typeof tool.name !== "string") {
|
|
279
|
+
issues.push({
|
|
280
|
+
severity: Severity.ERROR,
|
|
281
|
+
message: `Tool name is required.`,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if (!tool.parameters || typeof tool.parameters !== "object") {
|
|
285
|
+
issues.push({
|
|
286
|
+
severity: Severity.ERROR,
|
|
287
|
+
message: `Tool "${tool.name ?? "?"}" must have a parameters object (TypeBox-compatible).`,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── commands ──
|
|
294
|
+
if (plugin.commands) {
|
|
295
|
+
for (const cmd of plugin.commands) {
|
|
296
|
+
if (!cmd.name || typeof cmd.name !== "string") {
|
|
297
|
+
issues.push({
|
|
298
|
+
severity: Severity.ERROR,
|
|
299
|
+
message: `Command name is required.`,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── shortcuts ──
|
|
306
|
+
if (plugin.shortcuts) {
|
|
307
|
+
for (const sc of plugin.shortcuts) {
|
|
308
|
+
if (!sc.key || typeof sc.key !== "string") {
|
|
309
|
+
issues.push({
|
|
310
|
+
severity: Severity.ERROR,
|
|
311
|
+
message: `Shortcut key is required.`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ── flags ──
|
|
318
|
+
if (plugin.flags) {
|
|
319
|
+
for (const flag of plugin.flags) {
|
|
320
|
+
if (!flag.name || typeof flag.name !== "string") {
|
|
321
|
+
issues.push({
|
|
322
|
+
severity: Severity.ERROR,
|
|
323
|
+
message: `Flag name is required.`,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return issues;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
333
|
+
Code generation helpers
|
|
334
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Generate the body of an inline handler as a string.
|
|
338
|
+
*
|
|
339
|
+
* For inline handlers we embed the source code directly inside the
|
|
340
|
+
* pi.on(event, async (ctx) => { … }) callback.
|
|
341
|
+
*
|
|
342
|
+
* Pi Mono context objects (`ctx`) provide:
|
|
343
|
+
* - ctx.session – current session
|
|
344
|
+
* - ctx.agent – current agent
|
|
345
|
+
* - ctx.message – current message (for message.* events)
|
|
346
|
+
* - ctx.tool – tool call details (for tool.* events)
|
|
347
|
+
* - ctx.model – model request/response (for model.* events)
|
|
348
|
+
* - ctx.ui – Rich UI API
|
|
349
|
+
* - ctx.state – ephemeral state bag
|
|
350
|
+
* - ctx.logger – scoped logger
|
|
351
|
+
*
|
|
352
|
+
* @param handler - The inline handler definition.
|
|
353
|
+
* @param event - The Pi Mono event string (for comment context).
|
|
354
|
+
* @returns TypeScript source string for the handler body.
|
|
355
|
+
*/
|
|
356
|
+
function generateInlineHandlerBody(
|
|
357
|
+
handler: InlineHookHandlerExt,
|
|
358
|
+
event: string
|
|
359
|
+
): string {
|
|
360
|
+
const lines: string[] = [];
|
|
361
|
+
|
|
362
|
+
// Add a comment showing which universal hook this maps from.
|
|
363
|
+
lines.push(`// Handler for ${event}`);
|
|
364
|
+
|
|
365
|
+
if (handler.code) {
|
|
366
|
+
// User-provided raw code block.
|
|
367
|
+
lines.push(handler.code.trim());
|
|
368
|
+
} else if (handler.source) {
|
|
369
|
+
// Pre-written source file — we can't inline it here, so we emit a
|
|
370
|
+
// require() / import() stub and log a build-time warning.
|
|
371
|
+
lines.push(`// NOTE: Handler source "${handler.source}" must be copied into this function.`);
|
|
372
|
+
lines.push(`throw new Error("Handler source not inlined: ${handler.source.replace(/"/g, "\\'")}");`);
|
|
373
|
+
} else {
|
|
374
|
+
// Empty handler — generate a placeholder that logs the invocation.
|
|
375
|
+
lines.push(`ctx.logger?.info?.("[${event}] Hook invoked — no handler code provided.");`);
|
|
376
|
+
lines.push(`// TODO: Implement handler logic`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return lines.join("\n ");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Generate a handler for a "reference" type handler.
|
|
384
|
+
*
|
|
385
|
+
* Since Pi Mono expects inline functions, we generate a thin async wrapper
|
|
386
|
+
* that imports (or requires) the referenced module and calls the target
|
|
387
|
+
* function with the Pi Mono context.
|
|
388
|
+
*
|
|
389
|
+
* @param handler - The reference handler definition.
|
|
390
|
+
* @returns TypeScript source string for the wrapper.
|
|
391
|
+
*/
|
|
392
|
+
function generateReferenceHandler(handler: HandlerReference): string {
|
|
393
|
+
const { target, source } = handler;
|
|
394
|
+
const lines: string[] = [];
|
|
395
|
+
|
|
396
|
+
if (source) {
|
|
397
|
+
// Dynamic import for ESM compatibility.
|
|
398
|
+
lines.push(`const mod = await import(${tsStringLiteral(source)});`);
|
|
399
|
+
lines.push(`const fn = mod[${tsStringLiteral(target)}] ?? mod.default;`);
|
|
400
|
+
} else {
|
|
401
|
+
// Assume the target is available in the global/module scope.
|
|
402
|
+
lines.push(`const fn = ${safeIdent(target)};`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
lines.push(`if (typeof fn !== "function") {`);
|
|
406
|
+
lines.push(` throw new Error(\`Handler "${target}" is not a function.\`);`);
|
|
407
|
+
lines.push(`}`);
|
|
408
|
+
lines.push(`return fn(ctx);`);
|
|
409
|
+
|
|
410
|
+
return lines.join("\n ");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Generate a pi.on(event, handler) registration block.
|
|
415
|
+
*
|
|
416
|
+
* @param event - Pi Mono event string (e.g. "session.SessionStart").
|
|
417
|
+
* @param handler - Universal hook handler definition.
|
|
418
|
+
* @returns Lines of TypeScript source.
|
|
419
|
+
*/
|
|
420
|
+
function generateEventRegistration(
|
|
421
|
+
event: string,
|
|
422
|
+
handler: HookHandler | HandlerReference
|
|
423
|
+
): string[] {
|
|
424
|
+
const lines: string[] = [];
|
|
425
|
+
lines.push(`// ${event}`);
|
|
426
|
+
lines.push(`pi.on(${tsStringLiteral(event)}, async (ctx) => {`);
|
|
427
|
+
|
|
428
|
+
if ((handler as HandlerReference).type === "reference") {
|
|
429
|
+
lines.push(` ${generateReferenceHandler(handler as HandlerReference).replace(/\n/g, "\n ")}`);
|
|
430
|
+
} else {
|
|
431
|
+
// Default to inline (including cases where type is omitted).
|
|
432
|
+
lines.push(
|
|
433
|
+
` ${generateInlineHandlerBody(handler as InlineHookHandlerExt, event).replace(/\n/g, "\n ")}`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
lines.push(`});`);
|
|
438
|
+
return lines;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Generate pi.registerTool() calls for each tool in the manifest.
|
|
443
|
+
*
|
|
444
|
+
* Pi Mono uses TypeBox schemas, so we embed the schema object directly.
|
|
445
|
+
*
|
|
446
|
+
* @param tools - Array of plugin tools.
|
|
447
|
+
* @returns Lines of TypeScript source.
|
|
448
|
+
*/
|
|
449
|
+
function generateToolRegistrations(tools: ToolDefinition[]): string[] {
|
|
450
|
+
const lines: string[] = [];
|
|
451
|
+
|
|
452
|
+
for (const tool of tools) {
|
|
453
|
+
const toolName = safeIdent(tool.name);
|
|
454
|
+
lines.push(``);
|
|
455
|
+
lines.push(`// Tool: ${tool.name}`);
|
|
456
|
+
lines.push(`pi.registerTool({`);
|
|
457
|
+
lines.push(` name: ${tsStringLiteral(tool.name)},`);
|
|
458
|
+
lines.push(` description: ${tsStringLiteral(tool.description ?? `${tool.name} tool`)},`);
|
|
459
|
+
|
|
460
|
+
// Schema — we embed the JSON representation of the TypeBox schema.
|
|
461
|
+
if (tool.parameters) {
|
|
462
|
+
const schemaJson = JSON.stringify(tool.parameters, null, 2)
|
|
463
|
+
.split("\n")
|
|
464
|
+
.map((l, i) => (i === 0 ? l : ` ${l}`))
|
|
465
|
+
.join("\n");
|
|
466
|
+
lines.push(` schema: ${schemaJson},`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Handler — generates an async function that delegates to the tool's
|
|
470
|
+
// implementation. The implementation is expected to be provided at runtime
|
|
471
|
+
// or via a module reference.
|
|
472
|
+
lines.push(` handler: async (args) => {`);
|
|
473
|
+
// @ts-expect-error - adapter extends handler with source/target properties
|
|
474
|
+
if ((tool.handler as unknown)?.source) {
|
|
475
|
+
// @ts-expect-error
|
|
476
|
+
lines.push(` const mod = await import(${tsStringLiteral((tool.handler as unknown as { source?: string }).source ?? "")});`);
|
|
477
|
+
// @ts-expect-error
|
|
478
|
+
lines.push(` return mod[${tsStringLiteral((tool.handler as unknown as { target?: string }).target ?? "default")}](args);`);
|
|
479
|
+
// @ts-expect-error
|
|
480
|
+
} else if ((tool.handler as unknown)?.target) {
|
|
481
|
+
// @ts-expect-error
|
|
482
|
+
lines.push(` return ${safeIdent((tool.handler as unknown as { target: string }).target)}(args);`);
|
|
483
|
+
} else {
|
|
484
|
+
lines.push(` // TODO: Implement tool handler for "${tool.name}"`);
|
|
485
|
+
lines.push(` throw new Error("Tool handler not implemented: ${tool.name}");`);
|
|
486
|
+
}
|
|
487
|
+
lines.push(` },`);
|
|
488
|
+
|
|
489
|
+
lines.push(`});`);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return lines;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Generate pi.registerCommand() calls for each command in the manifest.
|
|
497
|
+
*
|
|
498
|
+
* @param commands - Array of plugin commands.
|
|
499
|
+
* @returns Lines of TypeScript source.
|
|
500
|
+
*/
|
|
501
|
+
function generateCommandRegistrations(commands: PluginCommand[]): string[] {
|
|
502
|
+
const lines: string[] = [];
|
|
503
|
+
|
|
504
|
+
for (const cmd of commands) {
|
|
505
|
+
lines.push(``);
|
|
506
|
+
lines.push(`// Command: /${cmd.name}`);
|
|
507
|
+
lines.push(`pi.registerCommand(${tsStringLiteral(cmd.name)}, {`);
|
|
508
|
+
if (cmd.description) {
|
|
509
|
+
lines.push(` description: ${tsStringLiteral(cmd.description)},`);
|
|
510
|
+
}
|
|
511
|
+
if (cmd.args && cmd.args.length > 0) {
|
|
512
|
+
const argsSchema = cmd.args.map((a) => ({
|
|
513
|
+
name: a.name,
|
|
514
|
+
type: a.type ?? "string",
|
|
515
|
+
description: a.description,
|
|
516
|
+
required: a.required ?? false,
|
|
517
|
+
}));
|
|
518
|
+
const argsJson = JSON.stringify(argsSchema, null, 2)
|
|
519
|
+
.split("\n")
|
|
520
|
+
.map((l, i) => (i === 0 ? l : ` ${l}`))
|
|
521
|
+
.join("\n");
|
|
522
|
+
lines.push(` args: ${argsJson},`);
|
|
523
|
+
}
|
|
524
|
+
lines.push(` run: async (ctx, args) => {`);
|
|
525
|
+
if (cmd.handler?.source) {
|
|
526
|
+
lines.push(` const mod = await import(${tsStringLiteral(cmd.handler.source)});`);
|
|
527
|
+
lines.push(` return mod[${tsStringLiteral(cmd.handler.target ?? "default")}](ctx, args);`);
|
|
528
|
+
} else if (cmd.handler?.target) {
|
|
529
|
+
lines.push(` return ${safeIdent(cmd.handler.target)}(ctx, args);`);
|
|
530
|
+
} else {
|
|
531
|
+
lines.push(` // TODO: Implement command handler for "/${cmd.name}"`);
|
|
532
|
+
lines.push(` ctx.ui?.toast?.(\`/${cmd.name} executed\`);`);
|
|
533
|
+
}
|
|
534
|
+
lines.push(` },`);
|
|
535
|
+
lines.push(`});`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return lines;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Generate pi.registerShortcut() calls for each shortcut in the manifest.
|
|
543
|
+
*
|
|
544
|
+
* @param shortcuts - Array of plugin shortcuts.
|
|
545
|
+
* @returns Lines of TypeScript source.
|
|
546
|
+
*/
|
|
547
|
+
function generateShortcutRegistrations(
|
|
548
|
+
shortcuts: NonNullable<PluginManifest["shortcuts"]>
|
|
549
|
+
): string[] {
|
|
550
|
+
const lines: string[] = [];
|
|
551
|
+
|
|
552
|
+
for (const sc of shortcuts) {
|
|
553
|
+
lines.push(``);
|
|
554
|
+
lines.push(`// Shortcut: ${sc.key}`);
|
|
555
|
+
lines.push(`pi.registerShortcut(${tsStringLiteral(sc.key)}, {`);
|
|
556
|
+
if (sc.description) {
|
|
557
|
+
lines.push(` description: ${tsStringLiteral(sc.description)},`);
|
|
558
|
+
}
|
|
559
|
+
if (sc.when) {
|
|
560
|
+
lines.push(` when: ${tsStringLiteral(sc.when)},`);
|
|
561
|
+
}
|
|
562
|
+
lines.push(` action: async (ctx) => {`);
|
|
563
|
+
if (sc.action) {
|
|
564
|
+
if (typeof sc.action === "string") {
|
|
565
|
+
// Named action reference.
|
|
566
|
+
lines.push(` return ${safeIdent(sc.action)}(ctx);`);
|
|
567
|
+
} else if (sc.action.source) {
|
|
568
|
+
lines.push(` const mod = await import(${tsStringLiteral(sc.action.source)});`);
|
|
569
|
+
lines.push(` return mod[${tsStringLiteral(sc.action.target ?? "default")}](ctx);`);
|
|
570
|
+
} else if (sc.action.target) {
|
|
571
|
+
lines.push(` return ${safeIdent(sc.action.target)}(ctx);`);
|
|
572
|
+
}
|
|
573
|
+
} else {
|
|
574
|
+
lines.push(` // TODO: Implement shortcut action for "${sc.key}"`);
|
|
575
|
+
lines.push(` ctx.logger?.info?.("Shortcut triggered: ${sc.key}");`);
|
|
576
|
+
}
|
|
577
|
+
lines.push(` },`);
|
|
578
|
+
lines.push(`});`);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return lines;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Generate pi.registerFlag() calls for each flag in the manifest.
|
|
586
|
+
*
|
|
587
|
+
* @param flags - Array of plugin flags.
|
|
588
|
+
* @returns Lines of TypeScript source.
|
|
589
|
+
*/
|
|
590
|
+
function generateFlagRegistrations(
|
|
591
|
+
flags: NonNullable<PluginManifest["flags"]>
|
|
592
|
+
): string[] {
|
|
593
|
+
const lines: string[] = [];
|
|
594
|
+
|
|
595
|
+
for (const flag of flags) {
|
|
596
|
+
lines.push(``);
|
|
597
|
+
lines.push(`// Flag: --${flag.name}`);
|
|
598
|
+
lines.push(`pi.registerFlag(${tsStringLiteral(flag.name)}, {`);
|
|
599
|
+
if (flag.description) {
|
|
600
|
+
lines.push(` description: ${tsStringLiteral(flag.description)},`);
|
|
601
|
+
}
|
|
602
|
+
if (flag.alias) {
|
|
603
|
+
lines.push(` alias: ${tsStringLiteral(flag.alias)},`);
|
|
604
|
+
}
|
|
605
|
+
if (flag.type) {
|
|
606
|
+
lines.push(` type: ${tsStringLiteral(flag.type)},`);
|
|
607
|
+
}
|
|
608
|
+
if (flag.defaultValue !== undefined) {
|
|
609
|
+
lines.push(` default: ${JSON.stringify(flag.defaultValue)},`);
|
|
610
|
+
}
|
|
611
|
+
lines.push(` handler: async (ctx, value) => {`);
|
|
612
|
+
if (flag.handler?.source) {
|
|
613
|
+
lines.push(` const mod = await import(${tsStringLiteral(flag.handler.source)});`);
|
|
614
|
+
lines.push(` return mod[${tsStringLiteral(flag.handler.target ?? "default")}](ctx, value);`);
|
|
615
|
+
} else if (flag.handler?.target) {
|
|
616
|
+
lines.push(` return ${safeIdent(flag.handler.target)}(ctx, value);`);
|
|
617
|
+
} else {
|
|
618
|
+
lines.push(` // TODO: Implement flag handler for "--${flag.name}"`);
|
|
619
|
+
lines.push(` ctx.logger?.info?.(\`Flag --${flag.name}=\${value} processed\`);`);
|
|
620
|
+
}
|
|
621
|
+
lines.push(` },`);
|
|
622
|
+
lines.push(`});`);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return lines;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
629
|
+
Main compiler
|
|
630
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Compile a plugin manifest into Pi Mono extension source files.
|
|
634
|
+
*
|
|
635
|
+
* The output contains:
|
|
636
|
+
* 1. `index.ts` — the main extension file exporting a default factory function.
|
|
637
|
+
* 2. `package.json` — only for multi-file extensions; contains a "pi" key with
|
|
638
|
+
* Pi-specific metadata (name, version, entry point, etc.).
|
|
639
|
+
*
|
|
640
|
+
* Single-file extensions (no external source files referenced) do not need a
|
|
641
|
+
* package.json — Pi Mono's auto-discovery will find `index.ts` directly.
|
|
642
|
+
*
|
|
643
|
+
* @param plugin - The plugin manifest to compile.
|
|
644
|
+
* @returns AdapterOutput with generated files and metadata.
|
|
645
|
+
*/
|
|
646
|
+
function compilePlugin(plugin: PluginManifest): AdapterOutput {
|
|
647
|
+
const files: FileOutput[] = [];
|
|
648
|
+
|
|
649
|
+
// ── Determine if this is a multi-file extension ──
|
|
650
|
+
let isMultiFile = false;
|
|
651
|
+
|
|
652
|
+
// If any handler references an external source file, we treat it as multi-file.
|
|
653
|
+
if (plugin.hooks) {
|
|
654
|
+
for (const handler of Object.values(plugin.hooks)) {
|
|
655
|
+
if (handler.type === "reference" && handler.source) {
|
|
656
|
+
isMultiFile = true;
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
if (handler.type === "inline" && handler.source) {
|
|
660
|
+
isMultiFile = true;
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (plugin.tools) {
|
|
667
|
+
for (const tool of plugin.tools) {
|
|
668
|
+
if (tool.handler?.source) {
|
|
669
|
+
isMultiFile = true;
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (plugin.commands) {
|
|
676
|
+
for (const cmd of plugin.commands) {
|
|
677
|
+
if (cmd.handler?.source) {
|
|
678
|
+
isMultiFile = true;
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (plugin.shortcuts) {
|
|
685
|
+
for (const sc of plugin.shortcuts) {
|
|
686
|
+
if (typeof sc.action !== "string" && sc.action?.source) {
|
|
687
|
+
isMultiFile = true;
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (plugin.flags) {
|
|
694
|
+
for (const flag of plugin.flags) {
|
|
695
|
+
if (flag.handler?.source) {
|
|
696
|
+
isMultiFile = true;
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// ── Build index.ts ──
|
|
703
|
+
const tsLines: string[] = [];
|
|
704
|
+
|
|
705
|
+
// Header / generated notice
|
|
706
|
+
tsLines.push(`/**`);
|
|
707
|
+
tsLines.push(` * Generated Pi Mono Extension — ${plugin.name}`);
|
|
708
|
+
tsLines.push(` *`);
|
|
709
|
+
tsLines.push(` * Platform: ${DISPLAY_NAME}`);
|
|
710
|
+
tsLines.push(` * Plugin: ${plugin.name}${plugin.version ? ` v${plugin.version}` : ""}`);
|
|
711
|
+
tsLines.push(` * Generated: ${new Date().toISOString()}`);
|
|
712
|
+
tsLines.push(` *`);
|
|
713
|
+
tsLines.push(` * This file is auto-generated by @agentplugins/adapter-pimono.`);
|
|
714
|
+
tsLines.push(` * Do not edit manually — changes will be overwritten on next compile.`);
|
|
715
|
+
tsLines.push(` */`);
|
|
716
|
+
tsLines.push(``);
|
|
717
|
+
|
|
718
|
+
// Import ExtensionAPI type.
|
|
719
|
+
tsLines.push(`import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";`);
|
|
720
|
+
|
|
721
|
+
// If multi-file with source references, collect dynamic import paths.
|
|
722
|
+
const dynamicImports = new Set<string>();
|
|
723
|
+
if (plugin.hooks) {
|
|
724
|
+
for (const handler of Object.values(plugin.hooks)) {
|
|
725
|
+
if ("source" in handler && handler.source) {
|
|
726
|
+
dynamicImports.add(handler.source);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (plugin.tools) {
|
|
731
|
+
for (const tool of plugin.tools) {
|
|
732
|
+
if (tool.handler?.source) dynamicImports.add(tool.handler.source);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (plugin.commands) {
|
|
736
|
+
for (const cmd of plugin.commands) {
|
|
737
|
+
if (cmd.handler?.source) dynamicImports.add(cmd.handler.source);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (dynamicImports.size > 0) {
|
|
742
|
+
tsLines.push(``);
|
|
743
|
+
tsLines.push(`// External handler modules (loaded dynamically via jiti)`);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
tsLines.push(``);
|
|
747
|
+
|
|
748
|
+
// Default factory function.
|
|
749
|
+
tsLines.push(`/**`);
|
|
750
|
+
tsLines.push(` * Pi Mono extension factory.`);
|
|
751
|
+
tsLines.push(` *`);
|
|
752
|
+
tsLines.push(` * @param pi - The ExtensionAPI instance provided by the Pi Mono runtime.`);
|
|
753
|
+
tsLines.push(` */`);
|
|
754
|
+
tsLines.push(`export default function(pi: ExtensionAPI) {`);
|
|
755
|
+
tsLines.push(` // Extension entry point — register all hooks, tools, commands, etc.`);
|
|
756
|
+
tsLines.push(` pi.logger?.info?.("[${plugin.name}] Extension loaded on ${DISPLAY_NAME}");`);
|
|
757
|
+
tsLines.push(``);
|
|
758
|
+
|
|
759
|
+
// ── Hooks ──
|
|
760
|
+
if (plugin.hooks && Object.keys(plugin.hooks).length > 0) {
|
|
761
|
+
tsLines.push(` /* ── Lifecycle Hooks ── */`);
|
|
762
|
+
for (const [hookName, handler] of Object.entries(plugin.hooks)) {
|
|
763
|
+
const event = HOOK_TO_EVENT[hookName as UniversalHookName];
|
|
764
|
+
if (!event) {
|
|
765
|
+
tsLines.push(` // WARNING: No Pi Mono event for hook "${hookName}" — skipping`);
|
|
766
|
+
tsLines.push(``);
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
const regLines = generateEventRegistration(event, handler);
|
|
770
|
+
for (const line of regLines) {
|
|
771
|
+
tsLines.push(` ${line}`);
|
|
772
|
+
}
|
|
773
|
+
tsLines.push(``);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// ── Tools ──
|
|
778
|
+
if (plugin.tools && plugin.tools.length > 0) {
|
|
779
|
+
tsLines.push(` /* ── Tools ── */`);
|
|
780
|
+
for (const line of generateToolRegistrations(plugin.tools)) {
|
|
781
|
+
tsLines.push(` ${line}`);
|
|
782
|
+
}
|
|
783
|
+
tsLines.push(``);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// ── Commands ──
|
|
787
|
+
if (plugin.commands && plugin.commands.length > 0) {
|
|
788
|
+
tsLines.push(` /* ── Commands ── */`);
|
|
789
|
+
for (const line of generateCommandRegistrations(plugin.commands)) {
|
|
790
|
+
tsLines.push(` ${line}`);
|
|
791
|
+
}
|
|
792
|
+
tsLines.push(``);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// ── Shortcuts ──
|
|
796
|
+
if (plugin.shortcuts && plugin.shortcuts.length > 0) {
|
|
797
|
+
tsLines.push(` /* ── Keyboard Shortcuts ── */`);
|
|
798
|
+
for (const line of generateShortcutRegistrations(plugin.shortcuts)) {
|
|
799
|
+
tsLines.push(` ${line}`);
|
|
800
|
+
}
|
|
801
|
+
tsLines.push(``);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// ── Flags ──
|
|
805
|
+
if (plugin.flags && plugin.flags.length > 0) {
|
|
806
|
+
tsLines.push(` /* ── CLI Flags ── */`);
|
|
807
|
+
for (const line of generateFlagRegistrations(plugin.flags)) {
|
|
808
|
+
tsLines.push(` ${line}`);
|
|
809
|
+
}
|
|
810
|
+
tsLines.push(``);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// ── Persistent state (appendEntry) ──
|
|
814
|
+
if (plugin.config?.persist) {
|
|
815
|
+
tsLines.push(` /* ── Persistent State ── */`);
|
|
816
|
+
tsLines.push(` pi.appendEntry("${plugin.name}", {`);
|
|
817
|
+
tsLines.push(` loadedAt: new Date().toISOString(),`);
|
|
818
|
+
tsLines.push(` version: ${tsStringLiteral(plugin.version ?? "0.0.0")},`);
|
|
819
|
+
tsLines.push(` });`);
|
|
820
|
+
tsLines.push(``);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
tsLines.push(`}`);
|
|
824
|
+
tsLines.push(``);
|
|
825
|
+
|
|
826
|
+
files.push({ path: "index.ts", content: tsLines.join("\n") });
|
|
827
|
+
|
|
828
|
+
// ── Build package.json (for multi-file extensions) ──
|
|
829
|
+
if (isMultiFile) {
|
|
830
|
+
const pkg: Record<string, unknown> = {
|
|
831
|
+
name: plugin.name,
|
|
832
|
+
version: plugin.version ?? "0.0.0",
|
|
833
|
+
description: plugin.description ?? `Pi Mono extension for ${plugin.name}`,
|
|
834
|
+
main: "index.ts",
|
|
835
|
+
pi: {
|
|
836
|
+
name: plugin.name,
|
|
837
|
+
version: plugin.version ?? "0.0.0",
|
|
838
|
+
displayName: plugin.displayName ?? plugin.name,
|
|
839
|
+
description: plugin.description,
|
|
840
|
+
entry: "index.ts",
|
|
841
|
+
author: plugin.author,
|
|
842
|
+
license: plugin.license,
|
|
843
|
+
hooks: Object.keys(plugin.hooks ?? {}).map((h) => ({
|
|
844
|
+
universal: h,
|
|
845
|
+
piEvent: HOOK_TO_EVENT[h as UniversalHookName] ?? null,
|
|
846
|
+
})),
|
|
847
|
+
tools: (plugin.tools ?? []).map((t) => t.name),
|
|
848
|
+
commands: (plugin.commands ?? []).map((c) => c.name),
|
|
849
|
+
shortcuts: (plugin.shortcuts ?? []).map((s) => s.key),
|
|
850
|
+
flags: (plugin.flags ?? []).map((f) => f.name),
|
|
851
|
+
trusted: plugin.config?.trusted ?? true,
|
|
852
|
+
autoLoad: plugin.config?.autoLoad ?? false,
|
|
853
|
+
},
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
files.push({ path: "package.json", content: JSON.stringify(pkg, null, 2) + "\n" });
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// ── Warnings ──
|
|
860
|
+
const warnings: string[] = [];
|
|
861
|
+
|
|
862
|
+
// Emit warnings for unsupported hooks.
|
|
863
|
+
if (plugin.hooks) {
|
|
864
|
+
for (const hookName of Object.keys(plugin.hooks)) {
|
|
865
|
+
if (!SUPPORTED_HOOKS.includes(hookName as UniversalHookName)) {
|
|
866
|
+
warnings.push(
|
|
867
|
+
`Hook "${hookName}" is not supported by the Pi Mono adapter and was skipped.`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
return {
|
|
874
|
+
files,
|
|
875
|
+
manifest: plugin,
|
|
876
|
+
warnings,
|
|
877
|
+
issues: [],
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
882
|
+
PiMonoAdapter — the public adapter class
|
|
883
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Pi Mono platform adapter.
|
|
887
|
+
*
|
|
888
|
+
* Implements the AgentPlugins `PlatformAdapter` interface to compile universal
|
|
889
|
+
* plugin manifests into Pi Mono native TypeScript extensions.
|
|
890
|
+
*
|
|
891
|
+
* Usage:
|
|
892
|
+
* ```ts
|
|
893
|
+
* import { piMonoAdapter } from "@agentplugins/adapter-pimono";
|
|
894
|
+
* import { createBridge } from "@agentplugins/core";
|
|
895
|
+
*
|
|
896
|
+
* const bridge = createBridge({ adapter: piMonoAdapter });
|
|
897
|
+
* const output = bridge.compile(myPluginManifest);
|
|
898
|
+
* // output.files["index.ts"] → the generated extension
|
|
899
|
+
* // output.files["package.json"] → metadata (multi-file only)
|
|
900
|
+
* ```
|
|
901
|
+
*/
|
|
902
|
+
export class PiMonoAdapter implements PlatformAdapter {
|
|
903
|
+
/** @inheritdoc */
|
|
904
|
+
readonly name: TargetPlatform = PLATFORM_NAME;
|
|
905
|
+
|
|
906
|
+
/** @inheritdoc */
|
|
907
|
+
readonly displayName: string = DISPLAY_NAME;
|
|
908
|
+
|
|
909
|
+
/** @inheritdoc */
|
|
910
|
+
readonly supportedHooks: readonly UniversalHookName[] = SUPPORTED_HOOKS;
|
|
911
|
+
|
|
912
|
+
/** @inheritdoc */
|
|
913
|
+
readonly supportedHandlers: readonly HandlerType[] = SUPPORTED_HANDLERS;
|
|
914
|
+
|
|
915
|
+
/** @inheritdoc */
|
|
916
|
+
readonly manifestPath: string = MANIFEST_PATH;
|
|
917
|
+
|
|
918
|
+
/** @inheritdoc */
|
|
919
|
+
readonly manifestFormat: "json" | "toml" = MANIFEST_FORMAT;
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Validate a plugin manifest for Pi Mono compatibility.
|
|
923
|
+
*
|
|
924
|
+
* @param plugin - The plugin manifest.
|
|
925
|
+
* @returns Array of validation issues (empty if valid).
|
|
926
|
+
*/
|
|
927
|
+
validate(plugin: PluginManifest): ValidationIssue[] {
|
|
928
|
+
return validatePlugin(plugin);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Compile a plugin manifest into Pi Mono extension files.
|
|
933
|
+
*
|
|
934
|
+
* @param plugin - The plugin manifest.
|
|
935
|
+
* @returns AdapterOutput containing generated files and metadata.
|
|
936
|
+
*/
|
|
937
|
+
compile(plugin: PluginManifest): AdapterOutput {
|
|
938
|
+
// Run validation first and surface errors.
|
|
939
|
+
const issues = this.validate(plugin);
|
|
940
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
941
|
+
|
|
942
|
+
if (errors.length > 0) {
|
|
943
|
+
const errorMessages = errors.map((e) => ` - ${e.message}`).join("\n");
|
|
944
|
+
throw new Error(
|
|
945
|
+
`Pi Mono adapter validation failed with ${errors.length} error(s):\n${errorMessages}`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return compilePlugin(plugin);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Singleton instance of the Pi Mono adapter.
|
|
955
|
+
*
|
|
956
|
+
* Most consumers should use this pre-constructed instance rather than
|
|
957
|
+
* constructing `PiMonoAdapter` directly.
|
|
958
|
+
*/
|
|
959
|
+
export const piMonoAdapter = new PiMonoAdapter();
|
|
960
|
+
|
|
961
|
+
/** Factory function for creating a new Pi Mono adapter instance. */
|
|
962
|
+
export function createPiMonoAdapter(): PlatformAdapter {
|
|
963
|
+
return new PiMonoAdapter();
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/* ────────────────────────────────────────────────────────────────────────────
|
|
967
|
+
Re-exports from @agentplugins/core for consumer convenience
|
|
968
|
+
──────────────────────────────────────────────────────────────────────────── */
|
|
969
|
+
|
|
970
|
+
export type {
|
|
971
|
+
PluginManifest,
|
|
972
|
+
ValidationIssue,
|
|
973
|
+
AdapterOutput,
|
|
974
|
+
ToolDefinition,
|
|
975
|
+
InlineHookHandler,
|
|
976
|
+
} from "@agentplugins/core";
|
|
977
|
+
|
|
978
|
+
export { HOOK_TO_EVENT, SUPPORTED_HOOKS, SUPPORTED_HANDLERS };
|