mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 18:30:39 +09:00
Add adapter skill sync for codex and claude
This commit is contained in:
parent
271c2b9018
commit
56a34a8f8a
22 changed files with 907 additions and 26 deletions
|
|
@ -5,6 +5,7 @@ import type { Db } from "@paperclipai/db";
|
|||
import { agents as agentsTable, companies, heartbeatRuns } from "@paperclipai/db";
|
||||
import { and, desc, eq, inArray, not, sql } from "drizzle-orm";
|
||||
import {
|
||||
agentSkillSyncSchema,
|
||||
createAgentKeySchema,
|
||||
createAgentHireSchema,
|
||||
createAgentSchema,
|
||||
|
|
@ -12,12 +13,17 @@ import {
|
|||
isUuidLike,
|
||||
resetAgentSessionSchema,
|
||||
testAdapterEnvironmentSchema,
|
||||
type AgentSkillSnapshot,
|
||||
type InstanceSchedulerHeartbeatAgent,
|
||||
updateAgentPermissionsSchema,
|
||||
updateAgentInstructionsPathSchema,
|
||||
wakeAgentSchema,
|
||||
updateAgentSchema,
|
||||
} from "@paperclipai/shared";
|
||||
import {
|
||||
readPaperclipSkillSyncPreference,
|
||||
writePaperclipSkillSyncPreference,
|
||||
} from "@paperclipai/adapter-utils/server-utils";
|
||||
import { validate } from "../middleware/validate.js";
|
||||
import {
|
||||
agentService,
|
||||
|
|
@ -334,6 +340,20 @@ export function agentRoutes(db: Db) {
|
|||
return details;
|
||||
}
|
||||
|
||||
function buildUnsupportedSkillSnapshot(
|
||||
adapterType: string,
|
||||
desiredSkills: string[] = [],
|
||||
): AgentSkillSnapshot {
|
||||
return {
|
||||
adapterType,
|
||||
supported: false,
|
||||
mode: "unsupported",
|
||||
desiredSkills,
|
||||
entries: [],
|
||||
warnings: ["This adapter does not implement skill sync yet."],
|
||||
};
|
||||
}
|
||||
|
||||
function redactForRestrictedAgentView(agent: Awaited<ReturnType<typeof svc.getById>>) {
|
||||
if (!agent) return null;
|
||||
return {
|
||||
|
|
@ -459,6 +479,119 @@ export function agentRoutes(db: Db) {
|
|||
},
|
||||
);
|
||||
|
||||
router.get("/agents/:id/skills", async (req, res) => {
|
||||
const id = req.params.id as string;
|
||||
const agent = await svc.getById(id);
|
||||
if (!agent) {
|
||||
res.status(404).json({ error: "Agent not found" });
|
||||
return;
|
||||
}
|
||||
await assertCanReadConfigurations(req, agent.companyId);
|
||||
|
||||
const adapter = findServerAdapter(agent.adapterType);
|
||||
if (!adapter?.listSkills) {
|
||||
const preference = readPaperclipSkillSyncPreference(
|
||||
agent.adapterConfig as Record<string, unknown>,
|
||||
);
|
||||
res.json(buildUnsupportedSkillSnapshot(agent.adapterType, preference.desiredSkills));
|
||||
return;
|
||||
}
|
||||
|
||||
const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(
|
||||
agent.companyId,
|
||||
agent.adapterConfig,
|
||||
);
|
||||
const snapshot = await adapter.listSkills({
|
||||
agentId: agent.id,
|
||||
companyId: agent.companyId,
|
||||
adapterType: agent.adapterType,
|
||||
config: runtimeConfig,
|
||||
});
|
||||
res.json(snapshot);
|
||||
});
|
||||
|
||||
router.post(
|
||||
"/agents/:id/skills/sync",
|
||||
validate(agentSkillSyncSchema),
|
||||
async (req, res) => {
|
||||
const id = req.params.id as string;
|
||||
const agent = await svc.getById(id);
|
||||
if (!agent) {
|
||||
res.status(404).json({ error: "Agent not found" });
|
||||
return;
|
||||
}
|
||||
await assertCanUpdateAgent(req, agent);
|
||||
|
||||
const desiredSkills = Array.from(
|
||||
new Set(
|
||||
(req.body.desiredSkills as string[])
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
);
|
||||
const nextAdapterConfig = writePaperclipSkillSyncPreference(
|
||||
agent.adapterConfig as Record<string, unknown>,
|
||||
desiredSkills,
|
||||
);
|
||||
const actor = getActorInfo(req);
|
||||
const updated = await svc.update(agent.id, {
|
||||
adapterConfig: nextAdapterConfig,
|
||||
}, {
|
||||
recordRevision: {
|
||||
createdByAgentId: actor.agentId,
|
||||
createdByUserId: actor.actorType === "user" ? actor.actorId : null,
|
||||
source: "skill-sync",
|
||||
},
|
||||
});
|
||||
if (!updated) {
|
||||
res.status(404).json({ error: "Agent not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const adapter = findServerAdapter(updated.adapterType);
|
||||
const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(
|
||||
updated.companyId,
|
||||
updated.adapterConfig,
|
||||
);
|
||||
const snapshot = adapter?.syncSkills
|
||||
? await adapter.syncSkills({
|
||||
agentId: updated.id,
|
||||
companyId: updated.companyId,
|
||||
adapterType: updated.adapterType,
|
||||
config: runtimeConfig,
|
||||
}, desiredSkills)
|
||||
: adapter?.listSkills
|
||||
? await adapter.listSkills({
|
||||
agentId: updated.id,
|
||||
companyId: updated.companyId,
|
||||
adapterType: updated.adapterType,
|
||||
config: runtimeConfig,
|
||||
})
|
||||
: buildUnsupportedSkillSnapshot(updated.adapterType, desiredSkills);
|
||||
|
||||
await logActivity(db, {
|
||||
companyId: updated.companyId,
|
||||
actorType: actor.actorType,
|
||||
actorId: actor.actorId,
|
||||
action: "agent.skills_synced",
|
||||
entityType: "agent",
|
||||
entityId: updated.id,
|
||||
agentId: actor.agentId,
|
||||
runId: actor.runId,
|
||||
details: {
|
||||
adapterType: updated.adapterType,
|
||||
desiredSkills,
|
||||
mode: snapshot.mode,
|
||||
supported: snapshot.supported,
|
||||
entryCount: snapshot.entries.length,
|
||||
warningCount: snapshot.warnings.length,
|
||||
},
|
||||
});
|
||||
|
||||
res.json(snapshot);
|
||||
},
|
||||
);
|
||||
|
||||
router.get("/companies/:companyId/agents", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue