app.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import modal
  2. from fastapi import FastAPI
  3. from fastapi.responses import JSONResponse, HTMLResponse, FileResponse
  4. from pathlib import Path
  5. app = modal.App("switchboard")
  6. # Simple image that just needs FastAPI
  7. image = (
  8. modal.Image.debian_slim()
  9. .pip_install("fastapi")
  10. .add_local_file("dashboard.html", "/root/dashboard.html")
  11. )
  12. web_app = FastAPI()
  13. # Deprecation message
  14. DEPRECATION_RESPONSE = {
  15. "error": "API Deprecated",
  16. "message": "Letta now provides native scheduling. This API has been deprecated.",
  17. "documentation": "https://docs.letta.com/guides/agents/scheduling/",
  18. "migration_guide": {
  19. "old_api": "https://letta--switchboard-api.modal.run/schedules/*",
  20. "new_api": "https://api.letta.com/v1/agents/{agent_id}/schedule",
  21. "changes": [
  22. "execute_at: ISO string → scheduled_at: Unix milliseconds",
  23. "message: string → messages: [{role, content}]",
  24. "Cron expressions: same 5-field format (no change)",
  25. "API calls now go directly to Letta (no Switchboard backend)"
  26. ],
  27. "examples": {
  28. "one_time": {
  29. "old": {
  30. "POST": "/schedules/one-time",
  31. "body": {"agent_id": "agent-xxx", "execute_at": "2025-11-13T09:00:00Z", "message": "Hello", "role": "user"}
  32. },
  33. "new": {
  34. "POST": "/v1/agents/{agent_id}/schedule",
  35. "body": {"schedule": {"type": "one-time", "scheduled_at": 1731499200000}, "messages": [{"role": "user", "content": "Hello"}]}
  36. }
  37. },
  38. "recurring": {
  39. "old": {
  40. "POST": "/schedules/recurring",
  41. "body": {"agent_id": "agent-xxx", "cron": "0 9 * * *", "message": "Daily", "role": "user"}
  42. },
  43. "new": {
  44. "POST": "/v1/agents/{agent_id}/schedule",
  45. "body": {"schedule": {"type": "recurring", "cron_expression": "0 9 * * *"}, "messages": [{"role": "user", "content": "Daily"}]}
  46. }
  47. }
  48. }
  49. },
  50. "dashboard": {
  51. "message": "The web dashboard has been updated to call Letta's API directly",
  52. "url": "/dashboard"
  53. }
  54. }
  55. # Serve the dashboard
  56. @web_app.get("/dashboard")
  57. @web_app.get("/")
  58. async def serve_dashboard():
  59. """Serve the updated dashboard that calls Letta API directly."""
  60. with open("/root/dashboard.html", "r") as f:
  61. return HTMLResponse(content=f.read())
  62. # Deprecated endpoints - return 410 Gone
  63. @web_app.post("/schedules/one-time")
  64. @web_app.get("/schedules/one-time")
  65. @web_app.get("/schedules/one-time/{schedule_id}")
  66. @web_app.delete("/schedules/one-time/{schedule_id}")
  67. async def deprecated_onetime():
  68. """Old one-time schedule endpoints - deprecated."""
  69. return JSONResponse(
  70. status_code=410, # 410 Gone
  71. content=DEPRECATION_RESPONSE
  72. )
  73. @web_app.post("/schedules/recurring")
  74. @web_app.get("/schedules/recurring")
  75. @web_app.get("/schedules/recurring/{schedule_id}")
  76. @web_app.delete("/schedules/recurring/{schedule_id}")
  77. async def deprecated_recurring():
  78. """Old recurring schedule endpoints - deprecated."""
  79. return JSONResponse(
  80. status_code=410, # 410 Gone
  81. content=DEPRECATION_RESPONSE
  82. )
  83. @web_app.get("/results")
  84. @web_app.get("/results/{schedule_id}")
  85. async def deprecated_results():
  86. """Old results endpoints - deprecated."""
  87. return JSONResponse(
  88. status_code=410, # 410 Gone
  89. content={
  90. **DEPRECATION_RESPONSE,
  91. "note": "Execution results are now tracked in Letta Cloud. Check your agent's run history at https://app.letta.com"
  92. }
  93. )
  94. # Health check
  95. @web_app.get("/health")
  96. async def health():
  97. """Health check endpoint."""
  98. return {"status": "ok", "message": "Dashboard service running"}
  99. # Deploy the app
  100. @app.function(
  101. image=image,
  102. secrets=[], # No secrets needed anymore!
  103. keep_warm=1, # Keep one instance warm
  104. )
  105. @modal.asgi_app()
  106. def api():
  107. """Serve the FastAPI app."""
  108. return web_app