Skip to content

hir-121: AI personalization helper endpoint (rebased onto main)#27

Open
jaredzwick wants to merge 2 commits into
pypesdev:mainfrom
jaredzwick:hir-121/personalize-rebase
Open

hir-121: AI personalization helper endpoint (rebased onto main)#27
jaredzwick wants to merge 2 commits into
pypesdev:mainfrom
jaredzwick:hir-121/personalize-rebase

Conversation

@jaredzwick
Copy link
Copy Markdown
Collaborator

Re-opens HIR-121 for landing. PR #15 was previously merged into the now-stale hir-103/templates feature branch — its commits never made it into main when HIR-103 (PR #11) was squash-merged. This PR rebases the two original hir-121 commits onto current main so the personalize endpoint actually ships.

Commits

  • d46314b hir-121: AI personalization helper endpoint
  • 580895c hir-121: address CTO review (prompt-injection guard + size cap + Vercel tracing)

Both are unchanged from the previously CTO-approved tips (7e537aa and 3e74dc2) — only the parent has moved from hir-103/templates to main. Cherry-pick was clean.

What's in scope

  • POST /api/personalize route
  • Server-side template prefill + Claude call (haiku-4.5 default)
  • "Personalize with AI" button + dialog with unified line diff in compose flow
  • Per-user in-memory rate limit (1 req / 2s)
  • Prompt-injection guard, optional_context size cap (30 keys × 500 chars)
  • outputFileTracingIncludes so templates/*.md ships in the Vercel bundle
  • 20 unit/int tests + a smoke test that skips without ANTHROPIC_API_KEY
  • README ## AI Personalization section

Deploy-time verification (per the CTO/CEO landing checklist)

  1. Hit the Vercel preview URL with template_id: sales_founder_direct (file-only id).
  2. 200 + personalized output → templates pack bundled correctly, merge.
  3. 404 → adjust outputFileTracingIncludes, fix in-PR before merging.

Out of scope

  • Bulk personalization
  • A/B testing personalized vs raw
  • Custom system prompts

🤖 Generated with Claude Code

jaredzwick and others added 2 commits May 16, 2026 09:42
Add POST /api/personalize: takes a template_id (resolved from either the
runtime catalog or the HIR-103 markdown pack) and a contact, fills known
{{vars}} server-side, then asks Claude (haiku-4.5 by default) to fill any
remaining placeholders and add 1-2 light personalization touches. Returns
personalized_subject, personalized_body, used_variables, and the SDK usage
object so we can track spend per call.

Wires a "Personalize with AI" button into the existing campaign-create
flow next to "Browse templates". Opens a dialog that takes a contact
(name / company / role), runs the endpoint, and shows a unified line-by-
line diff between the original template and the personalized variant
before applying.

Authenticated callers are rate-limited to one request every two seconds
via the existing in-memory limiter. Endpoint returns 503 when
ANTHROPIC_API_KEY is missing, 502 if the model leaves any {{vars}}
unfilled or returns malformed output.

Tests: 15 unit cases covering prefill, the JSON envelope parser, prompt
shape, file-template loading from templates/*.md (incl. the HIR-103
subjects that begin with `{{vars}}`), and the line-diff. A live smoke
spec calls Anthropic for real and asserts no leftover placeholders;
auto-skipped without ANTHROPIC_API_KEY.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
…el tracing)

- Add a prompt-injection guard to the system prompt: contact fields
  (including optional_context) are framed as untrusted data; the model is
  told never to follow instructions, role-play prompts, or formatting
  overrides found inside contact fields.
- Move the request schema into src/lib/templates/personalize.ts so it is
  unit-testable. Cap optional_context to 30 keys at 500 chars each
  (~15KB upper bound) instead of unbounded keys at 2000 chars.
- Wire outputFileTracingIncludes for /api/personalize -> templates/**/*.md
  in next.config.js so the markdown pack ships in the Vercel serverless
  bundle (process.cwd() is the function dir, not the repo root).
- Tests: 5 new cases cover the guard wording and the schema caps
  (accepts at the boundary, rejects past it for both keys and value
  length). Full suite: 20/20 pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant