[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81300":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":16,"stars30d":17,"stars90d":15,"forks30d":15,"starsTrendScore":18,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":21,"hasPages":21,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":33,"readmeContent":34,"aiSummary":35,"trendingCount":15,"starSnapshotCount":15,"syncStatus":13,"lastSyncTime":36,"discoverSource":37},81300,"nitro-imessage-agent-template","vercel-labs\u002Fnitro-imessage-agent-template","vercel-labs","Durable iMessage AI agent template. Text your agent, get retryable LLM + tool replies, full observability. Production-ready, deployed on Vercel","https:\u002F\u002Fnitro-imessage-agent.vercel.app",null,"TypeScript",50,2,1,0,3,4,9,49.33,"Apache License 2.0",false,"main",[24,25,26,27,28,29,30,31,32],"agent","ai-agent","ai-sdk","chat-sdk","evlog","imessage","nitro","vercel","vercel-workflow","2026-06-12 04:01:32","# nitro-imessage-agent\n\nA **durable** iMessage AI agent built on:\n\n- [Nitro](https:\u002F\u002Fnitro.build) v3 — the API server\n- [Chat SDK](https:\u002F\u002Fchat-sdk.dev) + [`chat-adapter-sendblue`](https:\u002F\u002Fchat-sdk.dev\u002Fadapters\u002Fsendblue) — message routing over [Sendblue](https:\u002F\u002Fsendblue.com)\n- [Vercel AI SDK](https:\u002F\u002Fsdk.vercel.ai) + [AI Gateway](https:\u002F\u002Fvercel.com\u002Fdocs\u002Fai-gateway) — LLM replies, swap models with one constant\n- [Vercel Workflow](https:\u002F\u002Fuseworkflow.dev) — durable orchestration with retryable `\"use step\"` units\n- [evlog](https:\u002F\u002Fwww.evlog.dev) — structured wide-event logging with first-class AI SDK integration (token usage, tool calls, cost estimation)\n\nA user texts your Sendblue number, Sendblue posts a webhook to this server, the Chat SDK dispatches the message, a workflow runs an agent step (LLM + tools), and the final reply is sent back through Sendblue. Each step is retryable on its own, so a transient LLM error or send hiccup never drops the inbound message.\n\n```\n   ┌─────────┐    ┌──────────────┐    ┌──────────────┐   ┌────────────┐\n   │ user    │───▶│   Sendblue   │───▶│  POST \u002Fapi\u002F  │──▶│ Chat SDK   │\n   │iMessage │    │  (cloud)     │    │webhooks\u002F...  │   │ onMention  │\n   └─────────┘    └──────────────┘    └──────────────┘   └─────┬──────┘\n        ▲                                                       │\n        │                                                       ▼\n        │                                           ┌────────────────────────┐\n        │                                           │ workflow start(...)    │\n        │                                           └───────────┬────────────┘\n        │                                                       ▼\n        │                                           ┌────────────────────────┐\n        │                                           │ generateReply(use step)│\n        │                                           │  ├── LLM (AI Gateway)  │\n        │                                           │  ├── tools roundtrip   │\n        │                                           │  └── evlog wide event  │\n        │                                           └───────────┬────────────┘\n        │                                                       ▼\n        │                                           ┌────────────────────────┐\n        └───────────────────────────────────────────┤ postReply (use step)   │\n                  Sendblue postMessage              │ sendblue.postMessage   │\n                                                    └────────────────────────┘\n```\n\n## Architecture\n\nThe Sendblue cloud holds your dedicated phone line and forwards inbound iMessages to your server as HTTPS webhooks. Outbound replies go back through the same API. There is no gateway listener to keep alive, no Mac in production, and no cron.\n\nThe [server\u002Fapi\u002Fwebhooks\u002Fsendblue.post.ts](server\u002Fapi\u002Fwebhooks\u002Fsendblue.post.ts) route receives every webhook and hands it to `chat.webhooks.sendblue(request)`. The Chat SDK then fires `onNewMention` (first DM in a thread) or `onSubscribedMessage` (every following DM) on the bot — handlers registered in [server\u002Fplugins\u002Fimessage.ts](server\u002Fplugins\u002Fimessage.ts) call `start(replyToMessage, [thread.id, message.text])` to queue a workflow.\n\n[workflows\u002Freply.ts](workflows\u002Freply.ts) is a thin `\"use workflow\"` function that just chains two retryable steps from [server\u002Futils\u002Fagent-steps.ts](server\u002Futils\u002Fagent-steps.ts):\n\n1. **`generateReply` step** — calls `generateText` against the Vercel AI Gateway. Tools registered in [server\u002Ftools\u002F](server\u002Ftools\u002F) are looped with `stopWhen: stepCountIs(5)` (LLM → tool → LLM until done). The model is wrapped with `evlog\u002Fai`'s `ai.wrap()` and `experimental_telemetry.integrations` carries `createEvlogIntegration(ai)` so token usage, tool execution timing, and estimated cost are captured into a wide event.\n2. **`postReply` step** — calls `sendblue.postMessage`. Independent retryability: a transient send error doesn't re-run the LLM call.\n\nWorkflow functions can't import Node-only packages like evlog directly, so the AI SDK calls live in steps (which run as normal Node) — that's why the actual logic is in `server\u002Futils\u002Fagent-steps.ts` and the workflow file just orchestrates.\n\n## Local setup (development)\n\nSendblue is webhook-based, so to receive iMessages on your local machine you expose `localhost:3000` through a public tunnel — we use **ngrok**.\n\n**Prerequisites**\n\n- Node 20+ (use `corepack enable` to get pnpm)\n- A Sendblue account ([sendblue.com](https:\u002F\u002Fsendblue.com)). The free tier with a shared line is enough to demo — webhooks work, replies to verified contacts work, and you get 10 contact slots. The AI Agent plan ($100\u002Fmo) is required if you want a dedicated number with higher inbound volume; see [pricing](https:\u002F\u002Fsendblue.com\u002Fpricing).\n- [ngrok](https:\u002F\u002Fngrok.com\u002Fdownload) installed and authenticated with a free account (`ngrok config add-authtoken \u003Cyour-token>`). Any HTTPS tunnel works — `cloudflared tunnel`, `localtunnel`, etc. — ngrok is just what we use here.\n\n**Install & run**\n\n```bash\npnpm install\ncp .env.example .env\n# fill in AI_GATEWAY_API_KEY + SENDBLUE_API_KEY + SENDBLUE_API_SECRET +\n# SENDBLUE_FROM_NUMBER + SENDBLUE_WEBHOOK_SECRET\npnpm dev\n```\n\nIn a second terminal:\n\n```bash\nngrok http 3000\n```\n\nCopy the `https:\u002F\u002F\u003Cid>.ngrok-free.app` URL ngrok prints, then in the [Sendblue dashboard](https:\u002F\u002Fdashboard.sendblue.com\u002F) set the inbound webhook URL to:\n\n```\nhttps:\u002F\u002F\u003Cid>.ngrok-free.app\u002Fapi\u002Fwebhooks\u002Fsendblue\n```\n\n**Test**\n\nText your Sendblue number from any phone. The dev server logs the inbound webhook, the workflow run starts, and a reply lands on your phone. Try `\"what time is it in Paris?\"` to validate the `getCurrentTime` tool path.\n\n## Production setup\n\nSendblue runs in the cloud and just talks HTTP, so production is the same as local minus the tunnel:\n\n1. Deploy this repo to [Vercel](https:\u002F\u002Fvercel.com) (or any Node host that supports Nitro): `pnpm dlx vercel`.\n2. Set the same five env vars in your hosting provider's environment settings (Vercel → Project → Settings → Environment Variables).\n3. In the Sendblue dashboard, point the inbound webhook URL at your production deployment:\n\n   ```\n   https:\u002F\u002F\u003Cyour-app>.vercel.app\u002Fapi\u002Fwebhooks\u002Fsendblue\n   ```\n\nThat's it — no `vercel.json`, no cron, no Mac. The Vercel function spins up on each webhook and the Workflow runs durably in the background.\n\n> **Why Sendblue and not Photon?** Photon's self-serve [Spectrum dashboard](https:\u002F\u002Fapp.photon.codes\u002F) hands out credentials for the new `spectrum-ts` SDK, which isn't compatible with `chat-adapter-imessage` — the latter still uses Photon's older Enterprise SDK and needs negotiated credentials from Photon sales. Sendblue is self-serve, supports webhooks on the free tier, has SMS fallback, and ships US numbers without A2P KYC. Swap the adapter if your needs differ — `chat-adapter-imessage` (Photon Enterprise), `chat-adapter-blooio`, or any future Chat SDK iMessage adapter all plug in the same way.\n\n## Configuration reference\n\n| Env var | Required | When |\n| --- | --- | --- |\n| `AI_GATEWAY_API_KEY` | yes | Always. Auto-detected by the AI SDK (no `NITRO_` prefix). |\n| `SENDBLUE_API_KEY` | yes | Always. Auto-detected by the adapter from process env. |\n| `SENDBLUE_API_SECRET` | yes | Always. Auto-detected by the adapter. |\n| `SENDBLUE_FROM_NUMBER` | yes | Your provisioned Sendblue line in E.164 format (e.g. `+14155551234`). |\n| `SENDBLUE_WEBHOOK_SECRET` | recommended | If set in the Sendblue dashboard, the adapter validates every incoming webhook against it. Set the same value here. |\n\n## Switching the model\n\nEdit one constant in [server\u002Futils\u002Fagent-steps.ts](server\u002Futils\u002Fagent-steps.ts):\n\n```ts\nconst MODEL = 'google\u002Fgemini-3-flash'\n```\n\nAny [supported AI Gateway slug](https:\u002F\u002Fvercel.com\u002Fai-gateway\u002Fmodels) works. A few useful ones:\n\n- `google\u002Fgemini-3-flash`\n- `anthropic\u002Fclaude-sonnet-4.5`\n- `openai\u002Fgpt-4o-mini`\n- `xai\u002Fgrok-4`\n\nThe AI SDK reads `AI_GATEWAY_API_KEY` from the environment automatically, so no provider plumbing is needed.\n\n## Extending the agent\n\n### Add a tool\n\nTools live in [server\u002Ftools\u002Findex.ts](server\u002Ftools\u002Findex.ts). Each tool is a `description` + `inputSchema` (zod) + `execute` function. The `\"use step\"` directive on the execute body makes every tool call a retryable, observable workflow step.\n\nExample (from this repo):\n\n```ts\nimport { z } from 'zod'\n\n\u002F\u002F eslint-disable-next-line require-await\nasync function getCurrentTime({ timezone }: { timezone: string }): Promise\u003Cstring> {\n  'use step'\n\n  return new Intl.DateTimeFormat('en-US', {\n    timeZone: timezone,\n    dateStyle: 'full',\n    timeStyle: 'long',\n  }).format(new Date())\n}\n\nexport const tools = {\n  getCurrentTime: {\n    description: 'Get the current date and time in a specific IANA timezone…',\n    inputSchema: z.object({\n      timezone: z.string().describe('IANA timezone identifier'),\n    }),\n    execute: getCurrentTime,\n  },\n}\n```\n\nTo add another tool, write a new step function and register it in the `tools` map. The agent picks it up automatically — `generateText` discovers them at call time.\n\n### Change the system prompt\n\nEdit `SYSTEM_PROMPT` in [server\u002Futils\u002Fagent-steps.ts](server\u002Futils\u002Fagent-steps.ts).\n\n### Add a workflow step\n\nAny function annotated with `\"use step\"` becomes a retryable, durable step. Wrap higher-level orchestration in a `\"use workflow\"` function and call steps from it. See [Workflows and steps](https:\u002F\u002Fuseworkflow.dev\u002Fdocs\u002Ffoundations\u002Fworkflows-and-steps) for the full mental model. [workflows\u002Freply.ts](workflows\u002Freply.ts) is the canonical example: a thin `\"use workflow\"` function chaining `generateReply` and `postReply` steps from [server\u002Futils\u002Fagent-steps.ts](server\u002Futils\u002Fagent-steps.ts).\n\n> Workflow functions can't import Node-only modules (evlog, native bindings, etc.). Keep heavy logic in `\"use step\"` files outside `workflows\u002F` and import them into the workflow.\n\n### Tweak AI observability\n\n`createAILogger(log, { cost })` in [server\u002Futils\u002Fagent-steps.ts](server\u002Futils\u002Fagent-steps.ts) controls token-cost estimation. Update `COST_MAP` with your real Gateway pricing, or set `cost` to `undefined` to disable. The wide event under the `ai.*` namespace already includes `inputTokens`, `outputTokens`, `toolCalls`, `tools[]` (timing per tool from `createEvlogIntegration`), `msToFirstChunk`, `tokensPerSecond`, and `estimatedCost`. See [evlog AI SDK docs](https:\u002F\u002Fwww.evlog.dev\u002Flogging\u002Fai-sdk\u002Foverview) for the full field list.\n\n### Add a drain (Axiom, OTLP, Sentry, …)\n\nThe Nitro evlog module exposes a `evlog:drain` hook. Drop a server plugin to forward wide events to your observability backend:\n\n```ts\n\u002F\u002F server\u002Fplugins\u002Fevlog-drain.ts\nimport { createAxiomDrain } from 'evlog\u002Faxiom'\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('evlog:drain', createAxiomDrain())\n})\n```\n\nOther adapters: `evlog\u002Fotlp`, `evlog\u002Fhyperdx`, `evlog\u002Fposthog`, `evlog\u002Fsentry`, `evlog\u002Fbetter-stack`, `evlog\u002Fdatadog`. Full list at [evlog adapters](https:\u002F\u002Fwww.evlog.dev\u002Fadapters).\n\n## Project layout\n\n```\nworkflows\u002F\n  reply.ts                          # \"use workflow\" — chains generateReply + postReply\nserver\u002F\n  api\u002F\n    index.ts                        # GET \u002Fapi — health check\n    webhooks\u002Fsendblue.post.ts       # POST \u002Fapi\u002Fwebhooks\u002Fsendblue — Sendblue inbound webhook\n  plugins\u002Fimessage.ts               # Chat SDK handlers, queues the workflow on each DM\n  tools\u002Findex.ts                    # tools passed to generateText (use step)\n  utils\u002Fagent-steps.ts              # generateReply + postReply steps; evlog AI wiring lives here\n  utils\u002Fbot.ts                      # Chat instance + Sendblue adapter (singleton)\nnitro.config.ts                     # registers `workflow\u002Fnitro` and `evlog\u002Fnitro\u002Fv3`\n```\n\n## Observability\n\nTwo layers, both opt-in through this repo's defaults:\n\n**Workflow runs** — durable execution, step retries, replay debugging:\n\n```bash\npnpm workflow:web         # local dashboard with run history, step retries, live logs\nnpx workflow inspect runs # CLI\n```\n\nIn production on Vercel, runs show up automatically in the Vercel dashboard.\n\n**Wide events** — every webhook + every workflow step emits a structured wide event via [evlog](https:\u002F\u002Fwww.evlog.dev). The AI SDK integration captures token usage, tool execution timing, and cost estimation under the `ai.*` namespace automatically. By default events go to console (pretty in dev, JSON in prod). Configure a drain (Axiom, OTLP, Sentry, …) to ship them to your backend — see \"Add a drain\" above.\n\n## Scripts\n\n```sh\npnpm dev        # start the Nitro dev server\npnpm build      # build for production\npnpm preview    # preview the production build\npnpm lint       # eslint\npnpm test       # vitest\npnpm typecheck  # tsc --noEmit\n```\n\n## References\n\n- [`chat-adapter-sendblue`](https:\u002F\u002Fchat-sdk.dev\u002Fadapters\u002Fsendblue) — adapter docs\n- [Sendblue docs](https:\u002F\u002Fdocs.sendblue.com) — API, webhooks, line provisioning\n- [Sendblue pricing](https:\u002F\u002Fsendblue.com\u002Fpricing)\n- [Chat SDK docs](https:\u002F\u002Fchat-sdk.dev)\n- [Vercel Workflow](https:\u002F\u002Fuseworkflow.dev) — durable execution model\n- [Workflows and steps](https:\u002F\u002Fuseworkflow.dev\u002Fdocs\u002Ffoundations\u002Fworkflows-and-steps)\n- [Vercel AI SDK](https:\u002F\u002Fsdk.vercel.ai)\n- [AI Gateway models](https:\u002F\u002Fvercel.com\u002Fai-gateway\u002Fmodels)\n- [evlog](https:\u002F\u002Fwww.evlog.dev) — wide-event logging\n- [evlog AI SDK integration](https:\u002F\u002Fwww.evlog.dev\u002Flogging\u002Fai-sdk\u002Foverview)\n- [evlog drain adapters](https:\u002F\u002Fwww.evlog.dev\u002Fadapters)\n- [Nitro](https:\u002F\u002Fnitro.build)\n- [ngrok](https:\u002F\u002Fngrok.com\u002Fdownload)\n\n## Contributing\n\nContributions are welcome — see [CONTRIBUTING.md](.\u002FCONTRIBUTING.md) for setup, conventions, and how to add a tool\u002Fstep. By participating you agree to the [Code of Conduct](.\u002FCODE_OF_CONDUCT.md). Security issues: see [SECURITY.md](.\u002FSECURITY.md).\n\n## License\n\n[Apache 2.0](.\u002FLICENSE) — Made by [@HugoRCD](https:\u002F\u002Fgithub.com\u002FHugoRCD).\n","该项目提供了一个持久化的iMessage AI代理模板，能够通过文本消息与用户交互，并返回可重试的LLM（大语言模型）和工具回复。它基于Nitro v3 API服务器、Chat SDK及Sendblue消息路由服务构建，利用Vercel AI SDK和AI Gateway处理LLM回复，同时采用Vercel Workflow实现流程的持久化编排与自动重试机制。此外，项目还集成了evlog进行结构化宽事件日志记录，支持令牌使用量追踪、工具调用监控以及成本估算等功能。适用于需要在iMessage平台上部署智能对话代理，且对稳定性和可观测性有较高要求的应用场景。","2026-06-11 04:04:32","CREATED_QUERY"]