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:
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:
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 infoThe focused suite reports one passing test. The application still has two root tools because normalization is ordinary TypeScript, not a model-facing capability.
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?