Vercel Logo

Normalize the request

GitHub arrives carrying a suitcase of nested fields. The factory needs four of them.

{
  body: string,
  number: number,
  title: string,
  url: string,
}

Normalization creates a stable boundary between somebody else's webhook shape and every decision we make afterward.

Shrink the Webhook at the Door

Convert trusted GitHub issue events into validated source issues for durable work orders.

Hands-on Exercise 2.1

Create agent/lib/intake.ts. Validate the raw fields we consume and accept GitHub's two versions of an empty body, null and missing:

agent/lib/intake.ts
import type { GitHubIssueEvent } from "eve/channels/github";
import { z } from "zod";
 
const rawIssueSchema = z.object({
  body: z.string().nullable().optional(),
  html_url: z.url(),
  title: z.string().min(1),
});
 
export function normalizeIssue(issue: GitHubIssueEvent) {
  const raw = rawIssueSchema.parse(issue.raw);
 
  return {
    body: raw.body ?? "",
    number: issue.issueNumber,
    title: raw.title,
    url: raw.html_url,
  };
}

The nullish coalescing operator preserves a real body and turns either empty representation into "". Downstream tools now receive one shape.

Create agent/lib/intake.test.ts with a labeled issue event. Assert the exact normalized object, including the issue number supplied by eve rather than the raw payload.

Now open agent/channels/github.ts. Keep its existing checks for the factory label, bot senders, and trusted maintainer roles. After those checks pass, normalize the issue and place it in the returned context:

agent/channels/github.ts
const sourceIssue = normalizeIssue(issue);
 
return {
  auth: defaultGitHubAuth(ctx),
  context: [
    intakeTask,
    `Create the work order from this normalized source issue:\n${JSON.stringify(sourceIssue)}`,
  ],
};

This order matters. Invalid or untrusted events return null before they can start durable work or spend model tokens.

Try It

Run the focused test, then inspect the compiled channel:

pnpm test agent/lib/intake.test.ts
pnpm typecheck
pnpm exec eve info

The focused suite reports one passing test. The application still has two root tools because normalization is ordinary TypeScript, not a model-facing capability.

Send an empty issue body

Add a second test with body: null. The normalized result should contain body: "". The request will later become a clarification outcome instead of crashing intake.

Zod rejects a missing body

Keep both .nullable() and .optional(). GitHub may send either representation.

Untrusted events start sessions

Call normalizeIssue only after the channel's label, sender, and permission checks return successfully.

Commit

git add agent/lib/intake.ts agent/lib/intake.test.ts agent/channels/github.ts
git commit -m "feat(factory): normalize trusted issues"

Done-When

  • Intake returns one stable source shape
  • Missing and null bodies become empty strings
  • Invalid URLs fail before entering a work order
  • Untrusted events return before normalization
  • The intake test and typecheck pass

The webhook is now somebody else's shape at the boundary and our stable contract everywhere else.

Solution

The exercise shows the complete agent/lib/intake.ts and channel integration. The finished boundary returns only body, number, title, and url, with an absent or null body normalized to an empty string.

Was this helpful?

supported.