Files
paperclip/server/src/routes/llms.ts
Forgotten 1c2873d22a feat: enforce agent icon enum and expose via LLM endpoint
Move icon name list to shared constants with strict enum validation.
Add /llms/agent-icons.txt endpoint, pass icon through hire flow,
and update skills to reference icon discovery step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 08:39:11 -06:00

86 lines
3.1 KiB
TypeScript

import { Router, type Request } from "express";
import type { Db } from "@paperclip/db";
import { AGENT_ICON_NAMES } from "@paperclip/shared";
import { forbidden } from "../errors.js";
import { listServerAdapters } from "../adapters/index.js";
import { agentService } from "../services/agents.js";
function hasCreatePermission(agent: { role: string; permissions: Record<string, unknown> | null | undefined }) {
if (!agent.permissions || typeof agent.permissions !== "object") return false;
return Boolean((agent.permissions as Record<string, unknown>).canCreateAgents);
}
export function llmRoutes(db: Db) {
const router = Router();
const agentsSvc = agentService(db);
async function assertCanRead(req: Request) {
if (req.actor.type === "board") return;
if (req.actor.type !== "agent" || !req.actor.agentId) {
throw forbidden("Board or permitted agent authentication required");
}
const actorAgent = await agentsSvc.getById(req.actor.agentId);
if (!actorAgent || !hasCreatePermission(actorAgent)) {
throw forbidden("Missing permission to read agent configuration reflection");
}
}
router.get("/llms/agent-configuration.txt", async (req, res) => {
await assertCanRead(req);
const adapters = listServerAdapters().sort((a, b) => a.type.localeCompare(b.type));
const lines = [
"# Paperclip Agent Configuration Index",
"",
"Installed adapters:",
...adapters.map((adapter) => `- ${adapter.type}: /llms/agent-configuration/${adapter.type}.txt`),
"",
"Related API endpoints:",
"- GET /api/companies/:companyId/agent-configurations",
"- GET /api/agents/:id/configuration",
"- POST /api/companies/:companyId/agent-hires",
"",
"Agent identity references:",
"- GET /llms/agent-icons.txt",
"",
"Notes:",
"- Sensitive values are redacted in configuration read APIs.",
"- New hires may be created in pending_approval state depending on company settings.",
"",
];
res.type("text/plain").send(lines.join("\n"));
});
router.get("/llms/agent-icons.txt", async (req, res) => {
await assertCanRead(req);
const lines = [
"# Paperclip Agent Icon Names",
"",
"Set the `icon` field on hire/create payloads to one of:",
...AGENT_ICON_NAMES.map((name) => `- ${name}`),
"",
"Example:",
'{ "name": "SearchOps", "role": "researcher", "icon": "search" }',
"",
];
res.type("text/plain").send(lines.join("\n"));
});
router.get("/llms/agent-configuration/:adapterType.txt", async (req, res) => {
await assertCanRead(req);
const adapterType = req.params.adapterType as string;
const adapter = listServerAdapters().find((entry) => entry.type === adapterType);
if (!adapter) {
res.status(404).type("text/plain").send(`Unknown adapter type: ${adapterType}`);
return;
}
res
.type("text/plain")
.send(
adapter.agentConfigurationDoc ??
`# ${adapterType} agent configuration\n\nNo adapter-specific documentation registered.`,
);
});
return router;
}