feat(FN-2262): merge fusion/fn-2262 (auto-resolved)

- feat(FN-2262): document Paperclip runtime configuration and constraints
- docs(FN-2261): update README with implementation details
- feat(FN-2261): add paperclip runtime resolution compatibility tests
- feat(FN-2261): add runtime adapter and registration tests
- feat(FN-2261): integrate adapter into plugin entrypoint
- feat(FN-2261): implement PaperclipRuntimeAdapter
- fix(FN-2261): remove pi-coding-agent re-exports from types.ts
- feat(FN-2261): define runtime types for Paperclip plugin
- feat(FN-2260): merge fusion/fn-2260
This commit is contained in:
Fusion
2026-04-22 15:39:15 -07:00
committed by gsxdsm
parent 1a43e23562
commit 53e3f351d9
31 changed files with 1984 additions and 310 deletions

View File

@@ -52,13 +52,9 @@ describe("paperclip-runtime plugin", () => {
describe("runtime factory invocation", () => {
beforeEach(() => {
// Mock @fusion/engine for createFnAgent
vi.mock("@fusion/engine", () => ({
vi.mock("../../../../packages/engine/src/pi.js", () => ({
createFnAgent: vi.fn().mockResolvedValue({ session: {} }),
promptWithFallback: vi.fn(),
}));
// Mock describeModel
vi.mock("../../engine/src/pi.js", () => ({
describeModel: vi.fn().mockReturnValue("mock/model"),
}));
});
@@ -73,7 +69,7 @@ describe("paperclip-runtime plugin", () => {
});
it("should return an adapter with correct id and name", async () => {
const runtime = await plugin.runtime!.factory({} as any);
const runtime = (await plugin.runtime!.factory({} as any)) as PaperclipRuntimeAdapter;
expect(runtime.id).toBe("paperclip");
expect(runtime.name).toBe("Paperclip Runtime");
});

View File

@@ -9,18 +9,12 @@ import { PaperclipRuntimeAdapter } from "../runtime-adapter.js";
// ── Mock Modules ────────────────────────────────────────────────────────────────
// Mock @fusion/engine for createFnAgent and promptWithFallback
const mockCreateFnAgent = vi.fn();
const mockPromptWithFallback = vi.fn();
vi.mock("@fusion/engine", () => ({
vi.mock("../../../../packages/engine/src/pi.js", () => ({
createFnAgent: mockCreateFnAgent,
promptWithFallback: mockPromptWithFallback,
}));
// Mock the relative import of describeModel from pi.ts
// This uses require() in the adapter, so we mock the entire module
vi.mock("../../engine/src/pi.js", () => ({
describeModel: vi.fn().mockReturnValue("mock/anthropic-claude"),
}));
@@ -169,7 +163,7 @@ describe("PaperclipRuntimeAdapter", () => {
describe("describeModel", () => {
it("should return model description from pi describeModel", () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { describeModel } = require("../../engine/src/pi.js");
const { describeModel } = require("../../../../packages/engine/src/pi.js");
const mockSession = { model: { provider: "anthropic", id: "claude-sonnet-4-5" } };
const result = adapter.describeModel(mockSession as any);

View File

@@ -30,8 +30,12 @@
* ```
*/
import type { AgentRuntime, AgentRuntimeOptions, AgentSessionResult } from "./types.js";
import type { AgentSession } from "@mariozechner/pi-coding-agent";
import type {
AgentRuntime,
AgentRuntimeOptions,
AgentSession,
AgentSessionResult,
} from "./types.js";
// ── describeModel (from pi.ts, not re-exported from @fusion/engine) ─────────────
//
@@ -40,8 +44,16 @@ import type { AgentSession } from "@mariozechner/pi-coding-agent";
// This is acceptable within the monorepo workspace. External plugins would need a
// different approach (e.g., the engine could export it publicly in the future).
//
type PiModule = {
createFnAgent: (options: unknown) => Promise<AgentSessionResult>;
promptWithFallback: (session: unknown, prompt: string, options?: unknown) => Promise<void>;
describeModel: (session: unknown) => string;
};
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { describeModel: getModelDescription } = require("../../engine/src/pi.js");
const loadPiModule = (): PiModule => require("../../../packages/engine/src/pi.js") as PiModule;
const { describeModel: getModelDescription } = loadPiModule();
/**
* Paperclip runtime adapter implementing the Fusion AgentRuntime interface.
@@ -69,7 +81,7 @@ export class PaperclipRuntimeAdapter implements AgentRuntime {
* @returns Promise resolving to the session result with session and optional sessionFile
*/
async createSession(options: AgentRuntimeOptions): Promise<AgentSessionResult> {
const { createFnAgent } = await import("@fusion/engine");
const { createFnAgent } = loadPiModule();
return createFnAgent({
cwd: options.cwd,
systemPrompt: options.systemPrompt,
@@ -103,7 +115,7 @@ export class PaperclipRuntimeAdapter implements AgentRuntime {
* @param options - Optional prompt options (e.g., images for vision)
*/
async promptWithFallback(session: AgentSession, prompt: string, options?: unknown): Promise<void> {
const { promptWithFallback: pwf } = await import("@fusion/engine");
const { promptWithFallback: pwf } = loadPiModule();
return pwf(session, prompt, options);
}

View File

@@ -1,74 +1,58 @@
/**
* Paperclip Runtime Plugin - Type Definitions
*
* Re-exports runtime contract types from @fusion/engine and plugin types from @fusion/plugin-sdk.
* These types define the interface that the Paperclip runtime adapter must implement.
*
* ## Type Sources
*
* - `AgentRuntime`, `AgentRuntimeOptions`, `AgentSessionResult`: from @fusion/engine (FN-2256 contract)
* - `PluginRuntimeRegistration`, `PluginRuntimeManifestMetadata`, `FusionPlugin`: from @fusion/plugin-sdk
*
* ## Internal Types
*
* `AgentSession` and `ToolDefinition` are used internally in the adapter implementation
* but are NOT re-exported here since they come from @mariozechner/pi-coding-agent,
* which is not a direct dependency of this plugin. They are accessible via
* `AgentSessionResult.session` and `AgentRuntimeOptions.customTools` respectively.
* The plugin runtime contract is defined locally in this example plugin to avoid
* a hard compile-time dependency on internal engine package exports.
*/
// ── Agent Runtime Contract (from @fusion/engine) ──────────────────────────────
// ── Local Agent Runtime Contract ──────────────────────────────────────────────
/** Minimal session shape used by the runtime adapter. */
export interface AgentSession {
dispose?: () => Promise<void> | void;
}
/** Options for creating an agent session. Mirrors createFnAgent inputs used by the adapter. */
export interface AgentRuntimeOptions {
cwd: string;
systemPrompt: string;
tools?: unknown;
customTools?: unknown;
onText?: (text: string) => void;
onThinking?: (text: string) => void;
onToolStart?: (toolName: string, args?: unknown) => void;
onToolEnd?: (toolName: string, result?: unknown) => void;
defaultProvider?: string;
defaultModelId?: string;
fallbackProvider?: string;
fallbackModelId?: string;
defaultThinkingLevel?: string;
sessionManager?: unknown;
skillSelection?: unknown;
skills?: string[];
}
/** Result of creating a session. */
export interface AgentSessionResult {
session: AgentSession;
sessionFile?: string;
}
/** Agent runtime adapter interface. */
export interface AgentRuntime {
id: string;
name: string;
createSession(options: AgentRuntimeOptions): Promise<AgentSessionResult>;
promptWithFallback(session: AgentSession, prompt: string, options?: unknown): Promise<void>;
describeModel(session: AgentSession): string;
dispose?(session: AgentSession): Promise<void>;
}
// ── Plugin Registration Types (from @fusion/plugin-sdk) ─────────────────────
export type {
/**
* Agent runtime adapter interface.
*
* All session runtimes (default pi runtime, plugin-provided runtimes) must
* implement this interface to ensure consistent behavior across engine subsystems.
*/
AgentRuntime,
/**
* Options for creating an agent session.
* Mirrors the options accepted by createFnAgent.
*/
AgentRuntimeOptions,
/**
* Result of creating an agent session.
*/
AgentSessionResult,
} from "@fusion/engine";
// ── Plugin Registration Types (from @fusion/plugin-sdk) ───────────────────────
export type {
/**
* Plugin runtime registration metadata.
* Contains identity and versioning information for a runtime.
*/
PluginRuntimeManifestMetadata,
/**
* Plugin runtime factory function.
* Creates a runtime instance when the plugin is loaded.
*/
PluginRuntimeFactory,
/**
* Plugin runtime registration with metadata and factory.
* The primary registration format used by Fusion's plugin system.
*/
PluginRuntimeRegistration,
/**
* Fusion plugin definition.
* The main export type for all Fusion plugins.
*/
FusionPlugin,
} from "@fusion/plugin-sdk";
// ── Note on describeModel ──────────────────────────────────────────────────────
//
// describeModel is NOT exported from @fusion/engine's public API.
// It is defined in packages/engine/src/pi.ts but only used internally.
// Plugin adapters should import describeModel directly from the relative path:
// import { describeModel } from "../../engine/src/pi.js";
//
// This relative import is only valid within the monorepo workspace.
// External plugins would need a different approach.