feat(agents): resolve agent shortnames to UUIDs in route params
Add router.param middleware that normalizes agent references, allowing shortnames to be used in place of UUIDs in agent API routes. Includes company-scoped lookup with ambiguity detection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
|||||||
createAgentKeySchema,
|
createAgentKeySchema,
|
||||||
createAgentHireSchema,
|
createAgentHireSchema,
|
||||||
createAgentSchema,
|
createAgentSchema,
|
||||||
|
isUuidLike,
|
||||||
resetAgentSessionSchema,
|
resetAgentSessionSchema,
|
||||||
testAdapterEnvironmentSchema,
|
testAdapterEnvironmentSchema,
|
||||||
updateAgentPermissionsSchema,
|
updateAgentPermissionsSchema,
|
||||||
@@ -26,7 +27,7 @@ import {
|
|||||||
logActivity,
|
logActivity,
|
||||||
secretService,
|
secretService,
|
||||||
} from "../services/index.js";
|
} from "../services/index.js";
|
||||||
import { forbidden, unprocessable } from "../errors.js";
|
import { conflict, forbidden, unprocessable } from "../errors.js";
|
||||||
import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
|
import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
|
||||||
import { findServerAdapter, listAdapterModels } from "../adapters/index.js";
|
import { findServerAdapter, listAdapterModels } from "../adapters/index.js";
|
||||||
import { redactEventPayload } from "../redaction.js";
|
import { redactEventPayload } from "../redaction.js";
|
||||||
@@ -114,6 +115,38 @@ export function agentRoutes(db: Db) {
|
|||||||
throw forbidden("Only CEO or agent creators can modify other agents");
|
throw forbidden("Only CEO or agent creators can modify other agents");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resolveCompanyIdForAgentReference(req: Request): Promise<string | null> {
|
||||||
|
const companyIdQuery = req.query.companyId;
|
||||||
|
const requestedCompanyId =
|
||||||
|
typeof companyIdQuery === "string" && companyIdQuery.trim().length > 0
|
||||||
|
? companyIdQuery.trim()
|
||||||
|
: null;
|
||||||
|
if (requestedCompanyId) {
|
||||||
|
assertCompanyAccess(req, requestedCompanyId);
|
||||||
|
return requestedCompanyId;
|
||||||
|
}
|
||||||
|
if (req.actor.type === "agent" && req.actor.companyId) {
|
||||||
|
return req.actor.companyId;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function normalizeAgentReference(req: Request, rawId: string): Promise<string> {
|
||||||
|
const raw = rawId.trim();
|
||||||
|
if (isUuidLike(raw)) return raw;
|
||||||
|
|
||||||
|
const companyId = await resolveCompanyIdForAgentReference(req);
|
||||||
|
if (!companyId) {
|
||||||
|
throw unprocessable("Agent shortname lookup requires companyId query parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolved = await svc.resolveByReference(companyId, raw);
|
||||||
|
if (resolved.ambiguous) {
|
||||||
|
throw conflict("Agent shortname is ambiguous in this company. Use the agent ID.");
|
||||||
|
}
|
||||||
|
return resolved.agent?.id ?? raw;
|
||||||
|
}
|
||||||
|
|
||||||
function parseSourceIssueIds(input: {
|
function parseSourceIssueIds(input: {
|
||||||
sourceIssueId?: string | null;
|
sourceIssueId?: string | null;
|
||||||
sourceIssueIds?: string[];
|
sourceIssueIds?: string[];
|
||||||
@@ -259,6 +292,15 @@ export function agentRoutes(db: Db) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
router.param("id", async (req, _res, next, rawId) => {
|
||||||
|
try {
|
||||||
|
req.params.id = await normalizeAgentReference(req, String(rawId));
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router.get("/adapters/:type/models", async (req, res) => {
|
router.get("/adapters/:type/models", async (req, res) => {
|
||||||
const type = req.params.type as string;
|
const type = req.params.type as string;
|
||||||
const models = await listAdapterModels(type);
|
const models = await listAdapterModels(type);
|
||||||
|
|||||||
Reference in New Issue
Block a user