Skip to content

[api-contract-agent-personality-write-validation] Validate personality generate and toggle bodies #6937

Description

@atomantic

Slice

API/route-contract audit of server/routes/agentPersonalities.js write handlers vs server/lib/agentValidation.js (the schema already used by POST/PUT create-update). Audited 2026-09-11.

model:light because the Zod shapes already exist and only need wiring on two handlers; effort:low because the change is a small schema + validateRequest plus a couple of 400 cases.

Problem

Create and update already go through validateRequest(agentSchema) / agentUpdateSchema (server/routes/agentPersonalities.js:41, 51) using server/lib/agentValidation.js:60-76. Generate and toggle do not.

POST /api/agents/personalities/generate (server/routes/agentPersonalities.js:75-80) reads req.body directly:

const { seed = {}, providerId, model } = req.body;
const generated = await generateAgentPersonality(seed, providerId, model);

generateAgentPersonality (server/services/agentPersonalityGenerator.js:61-62) destructures seed and then calls .trim() / nested field reads:

  • seed: nullTypeError: Cannot destructure property 'name' of 'null' → raw 500.
  • seed: { personality: null } → default = {} does not apply (null is present) → personality.style throws → raw 500.
  • seed: { name: 123 }name.trim is not a function → raw 500.
  • seed.name / description / promptPrefix have no .max(), unlike agentSchema (name max 100, description max 1000, promptPrefix max 2000). A huge seed is interpolated into the LLM prompt (server/services/agentPersonalityGenerator.js:79-122) and billed.

Client: apiPersonalities.generateAgentPersonality (client/src/services/apiPersonalities.js:21-23) posts { seed, providerId, model }; AgentList.jsx:90-104 always sends a structured object, so the UI is fine — the route is not.

POST /api/agents/personalities/:id/toggle (server/routes/agentPersonalities.js:84-88) passes req.body.enabled straight into toggleAgentupdateAgent(id, { enabled }) (server/services/agentPersonalities.js:140-141) with no boolean check. A string "false" is truthy and persists as enabled: "false". Later UI !agent.enabled (client/src/components/agents/AgentDetail.jsx:70) then mis-inverts. Create/update already require enabled: z.boolean().

GET / (server/routes/agentPersonalities.js:17-24) takes req.query.userId with no schema. An array query (?userId=a&userId=b) reaches getAgentsByUser.

Impact

  • Malformed generate bodies 500 instead of 400 VALIDATION_ERROR, and skip the { error, code, timestamp } envelope the rest of this router uses via ServerError.
  • Unbounded seed text is interpolated into a paid LLM call (source: 'agent-personality-generation').
  • Toggle can persist a non-boolean enabled, so the next click and any if (agent.enabled) gate read the wrong consent state.

Fix

Add two small schemas next to agentSchema in server/lib/agentValidation.js (or inline in the route) and wire validateRequest:

  • agentGenerateSchema: { seed: agentSchema.partial().optional().default({}), providerId: z.string().min(1).max(128).nullable().optional(), model: z.string().min(1).max(300).nullable().optional() }.strict(). Reject seed: null (do not coerce). Nested personality / avatar must be objects or omitted, not null.
  • agentToggleSchema: { enabled: z.boolean() }.strict().
  • Optional: z.object({ userId: z.string().min(1).max(100).optional() }) on GET /.

Do not change the generate prompt or the stored personality shape. Keep throwing ServerError from the service for provider-unavailable; only the request body becomes 400.

Scope

small

Acceptance criteria

  • POST /api/agents/personalities/generate with seed: null, seed: { personality: null }, or seed: { name: 123 } returns 400 VALIDATION_ERROR, not 500.
  • Generate seed strings honor the same max lengths as agentSchema / agentPersonalitySchema.
  • POST /api/agents/personalities/:id/toggle with missing or non-boolean enabled returns 400; only a real boolean is persisted.
  • Create/update routes stay on agentSchema / agentUpdateSchema unchanged.
  • Route tests cover the 400 cases above.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-contractProposed from an API/route-contract auditeffort:lowLow reasoning budget per stepmodel:lightMechanical single-file changeplanTracked by /do:replanplanner:grok-4-6Plan authored by the grok-4-6 model

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions