createEmendAiHandler
Build the server route that checks AI requests and streams the model's answer.
createEmendAiHandler builds your server route. It checks each request from
the browser, lets you decide who is allowed, calls your AI model through a
generate function you write, and streams the answer back.
It returns a standard (request: Request) => Promise<Response> function, so it
works with Next.js route handlers and any framework that uses standard
Request and Response objects. It works with any AI provider.
import { createEmendAiHandler } from "@emend/ai/server"Options
interface EmendAiHandlerOptions {
readonly generate: (
request: EmendAiRequest,
signal: AbortSignal
) => AsyncIterable<string>
readonly authorize?: (
request: Request,
payload: EmendAiRequest
) => Promise<boolean> | boolean
readonly manifest?: readonly EmendActionManifestEntry[]
readonly limits?: Partial<EmendRequestLimits>
}| Option | Required | Purpose |
|---|---|---|
generate | Yes | Calls your AI model and yields the answer piece by piece. |
authorize | No | Returns true to allow a request, false to reject it. |
manifest | No | Limits which actions this route accepts. |
limits | No | Changes the maximum request sizes. |
Start without an AI model
import { createEmendAiHandler, mockGenerate } from "@emend/ai/server"
export const POST = createEmendAiHandler({
generate: mockGenerate,
})mockGenerate returns fake answers, so you can test the whole route without
an API key.
Connect an AI model
generate only runs for requests that passed every check. Call your model,
and yield each piece of Markdown text as it arrives.
import { createEmendAiHandler } from "@emend/ai/server"
export const POST = createEmendAiHandler({
async *generate(request, signal) {
const stream = createProviderStream({
action: request.actionId,
instruction: request.instruction,
target: request.targetMarkdown,
context: request.contextMarkdown,
messages: request.messages,
signal,
})
for await (const chunk of stream) yield chunk
},
})Pass signal to your provider. If the user clicks Stop or closes the page,
the model stops too, so you do not pay for an answer nobody sees.
Check who is making the request
export const POST = createEmendAiHandler({
authorize: async (request, payload) => {
const user = await getCurrentUser(request)
return Boolean(user && (await userCanEdit(user, payload)))
},
generate,
})Return false to reject the request. If authorize throws, the browser gets
a generic internal_error, so your server's error details stay private.
What happens to each request
- The request must be a
POSTwith JSON. - The JSON is checked: format, action, and size.
authorizeruns, if you added it.- Each piece of text from
generateis sent to the browser. - The stream ends with
done, or with an error ifgeneratefailed.
HTTP responses
| Status | Trigger |
|---|---|
400 | The JSON is broken or not a valid emend request. |
401 | authorize returned false. |
405 | The request was not a POST. |
413 | The request has too much text. |
415 | The request was not sent as JSON. |
500 | authorize threw an error. |
A successful response streams with text/event-stream; charset=utf-8. Read
Production Considerations before you go
live.