mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-17 03:10:38 +09:00
feat(adapters): external adapter plugin system with dynamic UI parser
- Plugin loader: install/reload/remove/reinstall external adapters from npm packages or local directories - Plugin store persisted at ~/.paperclip/adapter-plugins.json - Self-healing UI parser resolution with version caching - UI: Adapter Manager page, dynamic loader, display registry with humanized names for unknown adapter types - Dev watch: exclude adapter-plugins dir from tsx watcher to prevent mid-request server restarts during reinstall - All consumer fallbacks use getAdapterLabel() for consistent display - AdapterTypeDropdown uses controlled open state for proper close behavior - Remove hermes-local from built-in UI (externalized to plugin) - Add docs for external adapters and UI parser contract
This commit is contained in:
parent
f8452a4520
commit
14d59da316
72 changed files with 4102 additions and 585 deletions
58
server/src/__tests__/adapter-registry.test.ts
Normal file
58
server/src/__tests__/adapter-registry.test.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { describe, expect, it, beforeEach, afterEach } from "vitest";
|
||||
import type { ServerAdapterModule } from "../adapters/index.js";
|
||||
import {
|
||||
findServerAdapter,
|
||||
listAdapterModels,
|
||||
registerServerAdapter,
|
||||
requireServerAdapter,
|
||||
unregisterServerAdapter,
|
||||
} from "../adapters/index.js";
|
||||
|
||||
const externalAdapter: ServerAdapterModule = {
|
||||
type: "external_test",
|
||||
execute: async () => ({
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
}),
|
||||
testEnvironment: async () => ({
|
||||
adapterType: "external_test",
|
||||
status: "pass",
|
||||
checks: [],
|
||||
testedAt: new Date(0).toISOString(),
|
||||
}),
|
||||
models: [{ id: "external-model", label: "External Model" }],
|
||||
supportsLocalAgentJwt: false,
|
||||
};
|
||||
|
||||
describe("server adapter registry", () => {
|
||||
beforeEach(() => {
|
||||
unregisterServerAdapter("external_test");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unregisterServerAdapter("external_test");
|
||||
});
|
||||
|
||||
it("registers external adapters and exposes them through lookup helpers", async () => {
|
||||
expect(findServerAdapter("external_test")).toBeNull();
|
||||
|
||||
registerServerAdapter(externalAdapter);
|
||||
|
||||
expect(requireServerAdapter("external_test")).toBe(externalAdapter);
|
||||
expect(await listAdapterModels("external_test")).toEqual([
|
||||
{ id: "external-model", label: "External Model" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("removes external adapters when unregistered", () => {
|
||||
registerServerAdapter(externalAdapter);
|
||||
|
||||
unregisterServerAdapter("external_test");
|
||||
|
||||
expect(findServerAdapter("external_test")).toBeNull();
|
||||
expect(() => requireServerAdapter("external_test")).toThrow(
|
||||
"Unknown adapter type: external_test",
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue