mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
fix(ai): sync provider configuration and keys across linked devices (#1506)
This commit is contained in:
1 parent
4ed9b69aab
commit
55d7c8837b
9 files changed
+175
No files matched your search
@@ -2,10 +2,12 @@ import type { Router } from "express";
|
||||
import type { PluginContext } from "@termix/plugin-sdk/backend";
|
||||
import { hostImportNormalizer } from "./host-import.js";
|
||||
import { createAiRepository } from "./repository.js";
|
||||
import { registerProviderSync } from "./sync.js";
|
||||
import { registerAiRoutes } from "./routes.js";
|
||||
|
||||
export async function activate(ctx: PluginContext) {
|
||||
const repository = await createAiRepository(ctx);
|
||||
await registerProviderSync(ctx, repository);
|
||||
|
||||
registerAiRoutes(ctx.http.router<Router>(), repository, ctx);
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { PluginContext, SyncRow } from "@termix/plugin-sdk/backend";
|
||||
import {
|
||||
apiKeyPrefix,
|
||||
providerSecretKey,
|
||||
type AiRepository,
|
||||
} from "./repository.js";
|
||||
import { providers } from "./tables.js";
|
||||
|
||||
function keyFromWire(row: SyncRow): string | null {
|
||||
if (typeof row.apiKey === "string") return row.apiKey || null;
|
||||
if (row.apiKey === null) return null;
|
||||
throw new Error("Invalid synced provider key");
|
||||
}
|
||||
|
||||
export async function registerProviderSync(
|
||||
ctx: PluginContext,
|
||||
repo: AiRepository,
|
||||
) {
|
||||
ctx.sync.registerEntity({
|
||||
type: "aiProviders",
|
||||
table: await ctx.db.define(providers),
|
||||
permissions: {
|
||||
create: "ai.manage_providers",
|
||||
update: "ai.manage_providers",
|
||||
delete: "ai.manage_providers",
|
||||
},
|
||||
serialize: async (row) => {
|
||||
const apiKey = await ctx.asUser(String(row.userId), () =>
|
||||
ctx.secrets.get(providerSecretKey(Number(row.id))),
|
||||
);
|
||||
return { ...row, apiKey, apiKeyPrefix: apiKeyPrefix(apiKey) };
|
||||
},
|
||||
deserialize: async (row) => {
|
||||
const apiKey = keyFromWire(row);
|
||||
const { apiKey: _key, ...fields } = row;
|
||||
return { ...fields, apiKeyPrefix: apiKeyPrefix(apiKey) };
|
||||
},
|
||||
afterWrite: async ({ id, userId, wire }) => {
|
||||
if (id === null) throw new Error("Synced provider has no local id");
|
||||
await ctx.asUser(userId, () =>
|
||||
ctx.secrets.set(providerSecretKey(id), keyFromWire(wire)),
|
||||
);
|
||||
},
|
||||
remove: async (row, userId) => {
|
||||
await ctx.asUser(userId, () =>
|
||||
repo.deleteProvider(Number(row.id), userId),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export const providers = adoptLegacyTable(
|
||||
"providers",
|
||||
{
|
||||
id: id(),
|
||||
syncId: varchar().unique(),
|
||||
userId: refUser(),
|
||||
// ollama | anthropic | openai | gemini | openai_compatible
|
||||
providerType: text().notNull(),
|
||||
|
||||
Reference in new issue
Block a user