{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ai-sdk-backend-resumable",
  "type": "registry:page",
  "title": "AI SDK Resumable Backend",
  "description": "AI SDK route handlers with resumable stream context, so a run survives a reload.",
  "dependencies": [
    "ai",
    "@ai-sdk/openai",
    "@assistant-ui/ai-sdk",
    "assistant-stream",
    "next"
  ],
  "files": [
    {
      "type": "registry:page",
      "path": "app/api/chat/route.ts",
      "target": "app/api/chat/route.ts",
      "sourcePath": "apps/registry/templates/ai-sdk-backend-resumable/app/api/chat/route.ts",
      "content": "import { openai } from \"@ai-sdk/openai\";\nimport { frontendTools } from \"@assistant-ui/ai-sdk\";\nimport {\n  streamText,\n  convertToModelMessages,\n  type UIMessage,\n  type JSONSchema7,\n  UI_MESSAGE_STREAM_HEADERS,\n} from \"ai\";\nimport { RESUMABLE_STREAM_ID_HEADER } from \"assistant-stream/resumable\";\nimport { resumableContext } from \"@/lib/resumable-context\";\n\nexport async function POST(req: Request) {\n  const {\n    messages,\n    system,\n    tools,\n  }: {\n    messages: UIMessage[];\n    system?: string;\n    tools?: Record<string, { description?: string; parameters: JSONSchema7 }>;\n  } = await req.json();\n\n  const streamId = crypto.randomUUID();\n\n  const result = streamText({\n    model: openai(\"gpt-5.6-luna\"),\n    messages: await convertToModelMessages(messages),\n    tools: {\n      ...frontendTools(tools ?? {}),\n    },\n    ...(system === undefined ? {} : { system }),\n  });\n\n  const body = result.toUIMessageStreamResponse().body;\n  if (!body) {\n    throw new Error(\"UI message stream response has no body\");\n  }\n  const stream = await resumableContext.run(streamId, () => body);\n\n  return new Response(stream, {\n    headers: {\n      ...UI_MESSAGE_STREAM_HEADERS,\n      [RESUMABLE_STREAM_ID_HEADER]: streamId,\n    },\n  });\n}\n"
    },
    {
      "type": "registry:page",
      "path": "app/api/chat/resume/[streamId]/route.ts",
      "target": "app/api/chat/resume/[streamId]/route.ts",
      "sourcePath": "apps/registry/templates/ai-sdk-backend-resumable/app/api/chat/resume/[streamId]/route.ts",
      "content": "import { UI_MESSAGE_STREAM_HEADERS } from \"ai\";\nimport { RESUMABLE_STREAM_ID_HEADER } from \"assistant-stream/resumable\";\nimport { resumableContext } from \"@/lib/resumable-context\";\n\n// Resume access is authenticated only by stream id; scope it to the signed-in user in production (see /docs/guides/resumable-stream-deployment).\nexport async function GET(\n  _req: Request,\n  context: { params: Promise<{ streamId: string }> },\n) {\n  const { streamId } = await context.params;\n  const stream = await resumableContext.resume(streamId);\n\n  if (!stream) {\n    return new Response(JSON.stringify({ error: \"stream not found\" }), {\n      status: 404,\n      headers: { \"Content-Type\": \"application/json\" },\n    });\n  }\n\n  return new Response(stream, {\n    headers: {\n      ...UI_MESSAGE_STREAM_HEADERS,\n      [RESUMABLE_STREAM_ID_HEADER]: streamId,\n    },\n  });\n}\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/resumable-context.ts",
      "target": "lib/resumable-context.ts",
      "sourcePath": "apps/registry/templates/ai-sdk-backend-resumable/lib/resumable-context.ts",
      "content": "import {\n  createInMemoryResumableStreamStore,\n  createResumableStreamContext,\n} from \"assistant-stream/resumable\";\nimport { after } from \"next/server\";\n\nconst store = createInMemoryResumableStreamStore();\nexport const resumableContext = createResumableStreamContext({\n  store,\n  waitUntil: (promise) => after(promise),\n});\n"
    }
  ]
}