feat: finish the ai plugin (B18)

This commit is contained in:
LukeGus committed 2026-09-24 13:43:26 -05:00
1 parent 6b4d1432ab
commit 21937a6b02
193 files changed
+23429 -4244

No files matched your search

+28 -28
View File
@@ -1,38 +1,38 @@
import type { Router } from "express";
import type { PluginContext } from "@termix/plugin-sdk/backend";
import { setPluginSsh } from "./ssh.js";
import { setPluginServices } from "./services.js";
import { startAiService, stopAiService } from "./routes.js";
import { createCurrentAiRepository } from "../../../../src/backend/database/repositories/factory.js";
import { hostImportNormalizer } from "./host-import.js";
import { createAiRepository } from "./repository.js";
import { registerAiRoutes } from "./routes.js";
export async function activate(ctx: PluginContext) {
setPluginSsh(ctx.ssh);
ctx.disposables.add(() => setPluginSsh(null));
setPluginServices(ctx.services);
ctx.disposables.add(() => setPluginServices(null));
startAiService(ctx.http.router());
const repository = await createAiRepository(ctx);
registerAiRoutes(ctx.http.router<Router>(), repository, ctx);
ctx.registry.provide("ai.hostImportNormalizer", hostImportNormalizer);
ctx.disposables.add(
() =>
void ctx.registry.revoke("ai.hostImportNormalizer", hostImportNormalizer),
);
// Offered by reference, not copied: this hands over a resolver, so the key
// stays ours. Rotating or deleting the provider takes effect on the next
// read by any borrower, and deactivate() below withdraws the offer entirely.
// The resolve is per-user because the keys are stored per-user and encrypted
// under that user's data key, so a borrowed read returns the acting user's
// own default key, never someone else's.
ctx.secrets.offer("api-key", async (userId: string) => {
const repository = createCurrentAiRepository();
const providers = await repository.listProviders(userId);
const preferred = providers.find((provider) => provider.apiKeyPrefix);
if (!preferred) return null;
const withSecret = await repository.findProviderWithSecret(
preferred.id,
userId,
);
return withSecret?.apiKey ?? null;
});
// read by any borrower, and deactivate withdraws the offer. Keys are per
// user, so a borrowed read returns the acting user's own key.
ctx.secrets.offer("api-key", (userId: string) =>
ctx.asUser(userId, async () => {
const providers = await repository.listProviders(userId);
const preferred = providers.find((provider) => provider.apiKeyPrefix);
if (!preferred) return null;
const withSecret = await repository.findProviderWithSecret(
preferred.id,
userId,
);
return withSecret?.apiKey ?? null;
}),
);
ctx.log.info("AI assistant routes mounted at /plugin-api/ai");
}
export async function deactivate() {
stopAiService();
}
export async function deactivate() {}