databricks-tellr-app 0.4.3.dev4__tar.gz → 0.4.3.dev5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/PKG-INFO +1 -1
  2. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
  3. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/pyproject.toml +1 -1
  4. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/chat.py +2 -2
  5. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/export.py +4 -4
  6. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/google_slides.py +51 -100
  7. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/sessions.py +1 -1
  8. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/settings/identities.py +3 -3
  9. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/setup.py +2 -2
  10. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/slides.py +13 -13
  11. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/verification.py +3 -3
  12. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/agent.py +17 -4
  13. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/README.md +0 -0
  14. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/databricks_tellr_app/__init__.py +0 -0
  15. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/databricks_tellr_app/run.py +0 -0
  16. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/databricks_tellr_app.egg-info/SOURCES.txt +0 -0
  17. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
  18. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/databricks_tellr_app.egg-info/requires.txt +0 -0
  19. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/databricks_tellr_app.egg-info/top_level.txt +0 -0
  20. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/setup.cfg +0 -0
  21. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/setup.py +0 -0
  22. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/__init__.py +0 -0
  23. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/__init__.py +0 -0
  24. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/_otel_bootstrap.py +0 -0
  25. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/fixtures/tour_demo_deck.json +0 -0
  26. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/main.py +0 -0
  27. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/mcp_auth.py +0 -0
  28. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/mcp_server.py +0 -0
  29. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/middleware/__init__.py +0 -0
  30. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/middleware/csrf.py +0 -0
  31. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/middleware/request_logging.py +0 -0
  32. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/middleware/security_headers.py +0 -0
  33. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/__init__.py +0 -0
  34. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/_authz.py +0 -0
  35. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/admin.py +0 -0
  36. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/admin_usage.py +0 -0
  37. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/agent_config.py +0 -0
  38. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/deck_contributors.py +0 -0
  39. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/feedback.py +0 -0
  40. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/images.py +0 -0
  41. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/local_version.py +0 -0
  42. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/profiles.py +0 -0
  43. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/settings/__init__.py +0 -0
  44. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/settings/contributors.py +0 -0
  45. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/settings/deck_prompts.py +0 -0
  46. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/settings/design_systems.py +0 -0
  47. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/settings/slide_styles.py +0 -0
  48. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/tools.py +0 -0
  49. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/tour.py +0 -0
  50. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/routes/version.py +0 -0
  51. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/schemas/__init__.py +0 -0
  52. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/schemas/agent_config.py +0 -0
  53. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/schemas/feedback.py +0 -0
  54. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/schemas/requests.py +0 -0
  55. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/schemas/responses.py +0 -0
  56. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/schemas/settings/__init__.py +0 -0
  57. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/schemas/settings/requests.py +0 -0
  58. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/schemas/settings/responses.py +0 -0
  59. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/schemas/streaming.py +0 -0
  60. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/services/__init__.py +0 -0
  61. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/services/chat_service.py +0 -0
  62. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/services/export_job_queue.py +0 -0
  63. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/services/feedback_service.py +0 -0
  64. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/services/job_queue.py +0 -0
  65. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/services/session_manager.py +0 -0
  66. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/services/session_naming.py +0 -0
  67. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/services/usage_events.py +0 -0
  68. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/services/usage_service.py +0 -0
  69. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/utils/__init__.py +0 -0
  70. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/api/utils/validation.py +0 -0
  71. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/__init__.py +0 -0
  72. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/config_loader.py +0 -0
  73. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/context_utils.py +0 -0
  74. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/database.py +0 -0
  75. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/databricks_client.py +0 -0
  76. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/defaults.py +0 -0
  77. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/encryption.py +0 -0
  78. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/init_default_profile.py +0 -0
  79. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/lakebase.py +0 -0
  80. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/migrate_profiles_to_agent_config.py +0 -0
  81. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/mlflow_agent_spans.py +0 -0
  82. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/mlflow_tracing.py +0 -0
  83. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/otel_logging.py +0 -0
  84. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/permission_context.py +0 -0
  85. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/prompt_modules.py +0 -0
  86. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/settings_db.py +0 -0
  87. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/core/user_context.py +0 -0
  88. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/__init__.py +0 -0
  89. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/__init__.py +0 -0
  90. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/deck_contributor.py +0 -0
  91. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/design_system.py +0 -0
  92. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/encryption_key.py +0 -0
  93. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/feedback.py +0 -0
  94. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/genie_space.py +0 -0
  95. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/google_global_credentials.py +0 -0
  96. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/google_oauth_token.py +0 -0
  97. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/identity.py +0 -0
  98. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/image.py +0 -0
  99. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/profile.py +0 -0
  100. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/profile_contributor.py +0 -0
  101. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/prompts.py +0 -0
  102. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/request_log.py +0 -0
  103. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/session.py +0 -0
  104. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/slide_deck_prompt.py +0 -0
  105. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/slide_style_library.py +0 -0
  106. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/usage_event.py +0 -0
  107. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/models/user_preference.py +0 -0
  108. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/database/types.py +0 -0
  109. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/domain/__init__.py +0 -0
  110. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/domain/slide.py +0 -0
  111. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/domain/slide_deck.py +0 -0
  112. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/__init__.py +0 -0
  113. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/agent_factory.py +0 -0
  114. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/config_service.py +0 -0
  115. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/config_validator.py +0 -0
  116. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/converter_jail/__init__.py +0 -0
  117. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/converter_jail/ast_guard.py +0 -0
  118. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/converter_jail/codeprep.py +0 -0
  119. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/converter_jail/gslides_runner.py +0 -0
  120. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/converter_jail/jail.py +0 -0
  121. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/converter_jail/pptx_runner.py +0 -0
  122. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/converter_jail/protocol.py +0 -0
  123. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/design_system_compiler.py +0 -0
  124. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/design_system_service.py +0 -0
  125. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/design_system_templates.py +0 -0
  126. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/drive_uploader.py +0 -0
  127. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/evaluation/__init__.py +0 -0
  128. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/evaluation/llm_judge.py +0 -0
  129. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/genie_service.py +0 -0
  130. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/google_slides_auth.py +0 -0
  131. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/google_slides_prompts_defaults.py +0 -0
  132. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/html_to_google_slides.py +0 -0
  133. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/html_to_pptx.py +0 -0
  134. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/identity_provider.py +0 -0
  135. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/identity_providers/__init__.py +0 -0
  136. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/identity_providers/account_provider.py +0 -0
  137. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/identity_providers/local_provider.py +0 -0
  138. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/identity_providers/workspace_provider.py +0 -0
  139. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/image_service.py +0 -0
  140. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/image_tools.py +0 -0
  141. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/permission_service.py +0 -0
  142. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/pptx_from_html_huashu.py +0 -0
  143. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/pptx_from_records.py +0 -0
  144. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/pptx_prompts_defaults.py +0 -0
  145. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/profile_service.py +0 -0
  146. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/streaming_callback.py +0 -0
  147. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/tools/__init__.py +0 -0
  148. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/tools/agent_bricks_tool.py +0 -0
  149. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/tools/ds_asset_tool.py +0 -0
  150. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/tools/genie_tool.py +0 -0
  151. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/tools/mcp_tool.py +0 -0
  152. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/tools/model_endpoint_tool.py +0 -0
  153. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/tools/vector_tool.py +0 -0
  154. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/services/validator.py +0 -0
  155. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/__init__.py +0 -0
  156. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/css_utils.py +0 -0
  157. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/ds_asset_utils.py +0 -0
  158. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/error_handling.py +0 -0
  159. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/html_safety.py +0 -0
  160. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/html_utils.py +0 -0
  161. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/image_utils.py +0 -0
  162. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/js_validator.py +0 -0
  163. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/logging_config.py +0 -0
  164. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/pi_filter.py +0 -0
  165. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/slide_hash.py +0 -0
  166. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/spotlight.py +0 -0
  167. {databricks_tellr_app-0.4.3.dev4 → databricks_tellr_app-0.4.3.dev5}/src/utils/text_caps.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.4.3.dev4
3
+ Version: 0.4.3.dev5
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.4.3.dev4
3
+ Version: 0.4.3.dev5
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "databricks-tellr-app"
7
- version = "0.4.3.dev4"
7
+ version = "0.4.3.dev5"
8
8
  description = "Tellr application package for Databricks Apps"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -384,7 +384,7 @@ async def send_message(
384
384
  logger.error(f"Chat request failed: {e}", exc_info=True)
385
385
  raise HTTPException(
386
386
  status_code=500,
387
- detail="Failed to process message",
387
+ detail=f"Failed to process message: {str(e)}",
388
388
  ) from e
389
389
  finally:
390
390
  await asyncio.to_thread(
@@ -662,7 +662,7 @@ async def submit_chat_async(
662
662
  session_manager.release_session_lock, request.session_id
663
663
  )
664
664
  logger.error(f"Failed to submit async chat: {e}")
665
- raise HTTPException(status_code=500, detail="Internal server error")
665
+ raise HTTPException(status_code=500, detail=str(e))
666
666
 
667
667
 
668
668
  @router.get("/chat/poll/{request_id}")
@@ -754,13 +754,13 @@ async def export_to_pptx(request: ExportPPTXRequest):
754
754
  temp_dir.rmdir()
755
755
  except Exception:
756
756
  pass
757
- raise HTTPException(status_code=500, detail="Export failed")
757
+ raise HTTPException(status_code=500, detail=f"Export failed: {str(e)}")
758
758
 
759
759
  except HTTPException:
760
760
  raise
761
761
  except Exception as e:
762
762
  logger.error("PPTX export failed", exc_info=True, extra={"error": str(e)})
763
- raise HTTPException(status_code=500, detail="Export failed")
763
+ raise HTTPException(status_code=500, detail=f"Export failed: {str(e)}")
764
764
 
765
765
 
766
766
  # =============================================================================
@@ -893,7 +893,7 @@ async def start_pptx_export_async(request: ExportPPTXRequest):
893
893
  except Exception as e:
894
894
  total_time = time.time() - start_time
895
895
  logger.error(f"Failed to start async export after {total_time:.2f}s: {e}", exc_info=True)
896
- raise HTTPException(status_code=500, detail="Failed to start export")
896
+ raise HTTPException(status_code=500, detail=str(e))
897
897
 
898
898
 
899
899
  @router.get("/pptx/poll/{job_id}", response_model=ExportJobResponse)
@@ -1038,7 +1038,7 @@ async def export_pptx_editable_from_records(request: ExportPPTXFromRecordsReques
1038
1038
  pptx_bytes = build_pptx(title, request.slides, font_mode=font_mode)
1039
1039
  except Exception as e:
1040
1040
  logger.exception(f"Editable PPTX emission failed: {e}")
1041
- raise HTTPException(status_code=500, detail="Editable PPTX export failed")
1041
+ raise HTTPException(status_code=500, detail=str(e))
1042
1042
 
1043
1043
  safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in title).strip("_") or "slides"
1044
1044
  from fastapi.responses import Response
@@ -10,10 +10,9 @@ user tokens are stored per-user in the ``google_oauth_tokens`` table.
10
10
  import json
11
11
  import logging
12
12
  import os
13
- import secrets
14
13
  from typing import Any, Optional
15
14
 
16
- from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response
15
+ from fastapi import APIRouter, Depends, HTTPException, Query, Request
17
16
  from fastapi.responses import HTMLResponse
18
17
  from pydantic import BaseModel
19
18
  from sqlalchemy.orm import Session
@@ -34,12 +33,6 @@ logger = logging.getLogger(__name__)
34
33
 
35
34
  router = APIRouter(prefix="/api/export/google-slides", tags=["google-slides"])
36
35
 
37
- # F-CR-3 (SDR-4437): the OAuth "state" carries an unguessable nonce that is also
38
- # stored in this short-lived cookie; the callback requires them to match before
39
- # exchanging the authorization code (anti-CSRF / anti-login-CSRF double-submit).
40
- _OAUTH_STATE_COOKIE = "g_oauth_state"
41
- _OAUTH_COOKIE_PATH = "/api/export/google-slides"
42
-
43
36
 
44
37
  # -------------------------------------------------------------------------
45
38
  # Helpers
@@ -105,76 +98,32 @@ class AuthUrlResponse(BaseModel):
105
98
  # -------------------------------------------------------------------------
106
99
 
107
100
 
108
- def _build_app_origin(request: Request) -> str:
109
- """Return the public origin (``scheme://host``) serving the app.
101
+ def _build_redirect_uri(request: Request) -> str:
102
+ """Build the OAuth callback redirect URI from the current request.
103
+
104
+ Google requires redirect URIs to match *exactly* what's registered in GCP,
105
+ with no query parameters.
110
106
 
111
107
  Behind a reverse proxy (e.g. Databricks Apps), ``request.base_url``
112
108
  returns the internal address (``http://localhost:8000``). We use the
113
109
  ``X-Forwarded-Host`` / ``X-Forwarded-Proto`` headers set by the proxy
114
- to reconstruct the public URL instead. For localhost, force ``http``.
110
+ to reconstruct the public URL instead.
111
+
112
+ For localhost, Google only allows ``http``.
115
113
  """
116
114
  forwarded_host = request.headers.get("x-forwarded-host")
117
115
  forwarded_proto = request.headers.get("x-forwarded-proto")
118
116
 
119
117
  if forwarded_host:
120
118
  scheme = forwarded_proto or "https"
121
- return f"{scheme}://{forwarded_host.split(',')[0].strip()}"
122
-
123
- base = str(request.base_url).rstrip("/")
124
- if "://localhost" in base or "://127.0.0.1" in base:
125
- base = base.replace("https://", "http://")
126
- return base
127
-
128
-
129
- def _build_redirect_uri(request: Request) -> str:
130
- """Build the OAuth callback redirect URI from the current request.
131
-
132
- Google requires redirect URIs to match *exactly* what's registered in GCP,
133
- with no query parameters.
134
- """
135
- return f"{_build_app_origin(request)}/api/export/google-slides/auth/callback"
136
-
137
-
138
- # Callback page template. The JS object braces are intentionally literal; the
139
- # %TOKENS% are substituted below so the postMessage targets the explicit app
140
- # origin (F-CR-3) instead of the wildcard '*'.
141
- _CALLBACK_TEMPLATE = """
142
- <html><body>
143
- <h2>%TITLE%</h2>
144
- <p>%MESSAGE%</p>
145
- <script>
146
- if (window.opener) {
147
- window.opener.postMessage({ type: 'google-slides-auth', success: %SUCCESS% }, %ORIGIN%);
148
- }
149
- setTimeout(() => window.close(), %DELAY%);
150
- </script>
151
- </body></html>
152
- """
153
-
154
-
155
- def _callback_response(app_origin: str, *, success: bool) -> HTMLResponse:
156
- """Render the popup callback page and clear the anti-CSRF state cookie.
157
-
158
- ``postMessage`` targets ``app_origin`` explicitly (F-CR-3); ``json.dumps``
159
- yields a safely-quoted JS string literal for the origin.
160
- """
161
- if success:
162
- title, message, delay = "Authorization Successful", "You can close this window.", "1500"
119
+ base = f"{scheme}://{forwarded_host.split(',')[0].strip()}"
163
120
  else:
164
- title = "Authorization Failed"
165
- message = "Authorization could not be completed. Please close this window and try again."
166
- delay = "3000"
167
- body = (
168
- _CALLBACK_TEMPLATE
169
- .replace("%TITLE%", title)
170
- .replace("%MESSAGE%", message)
171
- .replace("%SUCCESS%", "true" if success else "false")
172
- .replace("%ORIGIN%", json.dumps(app_origin))
173
- .replace("%DELAY%", delay)
174
- )
175
- resp = HTMLResponse(content=body, status_code=200)
176
- resp.delete_cookie(_OAUTH_STATE_COOKIE, path=_OAUTH_COOKIE_PATH)
177
- return resp
121
+ base = str(request.base_url).rstrip("/")
122
+ # Force http for localhost (Google rejects https://localhost)
123
+ if "://localhost" in base or "://127.0.0.1" in base:
124
+ base = base.replace("https://", "http://")
125
+
126
+ return f"{base}/api/export/google-slides/auth/callback"
178
127
 
179
128
 
180
129
  @router.get("/auth/status", response_model=AuthStatusResponse)
@@ -192,7 +141,6 @@ async def auth_status(db: Session = Depends(get_db)):
192
141
  @router.get("/auth/url", response_model=AuthUrlResponse)
193
142
  async def auth_url(
194
143
  request: Request,
195
- response: Response,
196
144
  db: Session = Depends(get_db),
197
145
  ):
198
146
  """Generate the Google OAuth consent URL.
@@ -201,26 +149,13 @@ async def auth_url(
201
149
  passed via the OAuth ``state`` parameter so the callback can persist the
202
150
  token for the correct user — the redirect URI itself stays clean
203
151
  (no query params) to satisfy Google's exact-match requirement.
204
-
205
- F-CR-3: an unguessable nonce is bound into both the ``state`` and a
206
- short-lived cookie; the callback requires them to match.
207
152
  """
208
153
  try:
209
154
  auth = _get_auth(db)
210
155
  redirect_uri = _build_redirect_uri(request)
211
- nonce = secrets.token_urlsafe(32)
212
- state = json.dumps({"user": _get_user_identity(), "nonce": nonce})
156
+ state = json.dumps({"user": _get_user_identity()})
213
157
  url = auth.get_auth_url(redirect_uri=redirect_uri, state=state)
214
158
 
215
- response.set_cookie(
216
- key=_OAUTH_STATE_COOKIE,
217
- value=nonce,
218
- max_age=600,
219
- httponly=True,
220
- secure=True,
221
- samesite="lax", # sent on Google's top-level redirect back to us
222
- path=_OAUTH_COOKIE_PATH,
223
- )
224
159
  return AuthUrlResponse(url=url)
225
160
  except GoogleSlidesAuthError as exc:
226
161
  raise HTTPException(status_code=400, detail=str(exc)) from exc
@@ -249,22 +184,12 @@ async def auth_callback(
249
184
  them in the ``google_oauth_tokens`` table. Returns a small HTML page
250
185
  that notifies the opener window and closes itself.
251
186
  """
252
- app_origin = _build_app_origin(request)
253
- cookie_nonce = request.cookies.get(_OAUTH_STATE_COOKIE)
254
187
  try:
255
188
  state_data = json.loads(state) if state else {}
256
189
  user = state_data.get("user")
257
190
  if not user:
258
191
  raise ValueError("Missing user in OAuth state")
259
192
 
260
- # F-CR-3: verify the anti-CSRF nonce BEFORE exchanging the code. The
261
- # state nonce must match the cookie set when the auth URL was issued.
262
- state_nonce = state_data.get("nonce")
263
- if not cookie_nonce or not state_nonce or not secrets.compare_digest(
264
- str(cookie_nonce), str(state_nonce)
265
- ):
266
- raise ValueError("OAuth state nonce mismatch")
267
-
268
193
  auth = _get_auth(db)
269
194
  redirect_uri = _build_redirect_uri(request)
270
195
  code_verifier = state_data.get("code_verifier")
@@ -273,13 +198,39 @@ async def auth_callback(
273
198
  "Google Slides OAuth callback successful",
274
199
  extra={"user": _get_user_identity()},
275
200
  )
276
- except Exception:
277
- # F-CR-2 (SDR-4437): never reflect the raw exception into the HTML page —
278
- # it is logged server-side above. Show a static, generic failure message.
201
+ except Exception as exc:
279
202
  logger.error("OAuth callback failed", exc_info=True)
280
- return _callback_response(app_origin, success=False)
203
+ return HTMLResponse(
204
+ content=f"""
205
+ <html><body>
206
+ <h2>Authorization Failed</h2>
207
+ <p>{exc}</p>
208
+ <script>
209
+ if (window.opener) {{
210
+ window.opener.postMessage({{ type: 'google-slides-auth', success: false }}, '*');
211
+ }}
212
+ setTimeout(() => window.close(), 3000);
213
+ </script>
214
+ </body></html>
215
+ """,
216
+ status_code=200,
217
+ )
281
218
 
282
- return _callback_response(app_origin, success=True)
219
+ return HTMLResponse(
220
+ content="""
221
+ <html><body>
222
+ <h2>Authorization Successful</h2>
223
+ <p>You can close this window.</p>
224
+ <script>
225
+ if (window.opener) {
226
+ window.opener.postMessage({ type: 'google-slides-auth', success: true }, '*');
227
+ }
228
+ setTimeout(() => window.close(), 1500);
229
+ </script>
230
+ </body></html>
231
+ """,
232
+ status_code=200,
233
+ )
283
234
 
284
235
 
285
236
  # -------------------------------------------------------------------------
@@ -333,7 +284,7 @@ async def start_google_slides_export(
333
284
  slide_deck = chat_service.get_slides(request_body.session_id)
334
285
  except Exception as exc:
335
286
  logger.error("Failed to fetch slides", exc_info=True)
336
- raise HTTPException(status_code=500, detail="Failed to fetch slides") from exc
287
+ raise HTTPException(status_code=500, detail=f"Failed to fetch slides: {exc}") from exc
337
288
 
338
289
  if not slide_deck or not slide_deck.get("slides"):
339
290
  raise HTTPException(status_code=404, detail="No slides available")
@@ -497,7 +448,7 @@ async def export_google_slides_from_records(
497
448
  )
498
449
  except Exception as exc:
499
450
  logger.error("Drive upload failed", exc_info=True)
500
- raise HTTPException(status_code=502, detail="Drive upload failed") from exc
451
+ raise HTTPException(status_code=502, detail=f"Drive upload failed: {exc}") from exc
501
452
 
502
453
  session_manager.set_google_slides_info(
503
454
  session_id=request.session_id,
@@ -647,7 +598,7 @@ async def export_google_slides_from_huashu(
647
598
  logger.error("Drive upload failed (huashu path)", exc_info=True)
648
599
  raise HTTPException(
649
600
  status_code=502,
650
- detail="Drive upload failed",
601
+ detail=f"Drive upload failed: {exc}",
651
602
  ) from exc
652
603
 
653
604
  session_manager.set_google_slides_info(
@@ -553,7 +553,7 @@ async def duplicate_session(
553
553
  logger.error(f"Failed to duplicate session: {e}", exc_info=True)
554
554
  raise HTTPException(
555
555
  status_code=500,
556
- detail="Failed to duplicate session",
556
+ detail=f"Failed to duplicate session: {str(e)}",
557
557
  ) from e
558
558
 
559
559
 
@@ -109,7 +109,7 @@ def list_users(
109
109
  logger.error(f"Failed to list users: {e}", exc_info=True)
110
110
  raise HTTPException(
111
111
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
112
- detail="Failed to list users",
112
+ detail=f"Failed to list users: {e}",
113
113
  )
114
114
 
115
115
 
@@ -149,7 +149,7 @@ def list_groups(
149
149
  logger.error(f"Failed to list groups: {e}", exc_info=True)
150
150
  raise HTTPException(
151
151
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
152
- detail="Failed to list groups",
152
+ detail=f"Failed to list groups: {e}",
153
153
  )
154
154
 
155
155
 
@@ -203,5 +203,5 @@ def search_identities(
203
203
  logger.error(f"Failed to search identities: {e}", exc_info=True)
204
204
  raise HTTPException(
205
205
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
206
- detail="Failed to search identities",
206
+ detail=f"Failed to search identities: {e}",
207
207
  )
@@ -123,7 +123,7 @@ async def configure_workspace(request: ConfigureWorkspaceRequest):
123
123
  logger.error(f"Failed to configure workspace: {e}")
124
124
  raise HTTPException(
125
125
  status_code=500,
126
- detail="Failed to save configuration"
126
+ detail=f"Failed to save configuration: {str(e)}"
127
127
  )
128
128
 
129
129
 
@@ -154,5 +154,5 @@ async def test_connection():
154
154
  logger.error(f"Connection test failed: {e}")
155
155
  raise HTTPException(
156
156
  status_code=400,
157
- detail="Connection failed"
157
+ detail=f"Connection failed: {str(e)}"
158
158
  )
@@ -105,7 +105,7 @@ async def get_slides(
105
105
  raise
106
106
  except Exception as e:
107
107
  logger.error(f"Failed to get slides: {e}", exc_info=True)
108
- raise HTTPException(status_code=500, detail="Internal server error")
108
+ raise HTTPException(status_code=500, detail=str(e))
109
109
 
110
110
 
111
111
  @router.put("/reorder")
@@ -167,7 +167,7 @@ async def reorder_slides(request: ReorderRequest, db: Session = Depends(get_db))
167
167
  raise HTTPException(status_code=400, detail=str(e))
168
168
  except Exception as e:
169
169
  logger.error(f"Failed to reorder slides: {e}", exc_info=True)
170
- raise HTTPException(status_code=500, detail="Internal server error")
170
+ raise HTTPException(status_code=500, detail=str(e))
171
171
  finally:
172
172
  await asyncio.to_thread(
173
173
  session_manager.release_session_lock,
@@ -235,7 +235,7 @@ async def update_slide(index: int, request: UpdateSlideRequest, db: Session = De
235
235
  raise HTTPException(status_code=400, detail=str(e))
236
236
  except Exception as e:
237
237
  logger.error(f"Failed to update slide: {e}", exc_info=True)
238
- raise HTTPException(status_code=500, detail="Internal server error")
238
+ raise HTTPException(status_code=500, detail=str(e))
239
239
  finally:
240
240
  await asyncio.to_thread(
241
241
  session_manager.release_session_lock,
@@ -303,7 +303,7 @@ async def duplicate_slide(index: int, request: SlideActionRequest, db: Session =
303
303
  raise HTTPException(status_code=400, detail=str(e))
304
304
  except Exception as e:
305
305
  logger.error(f"Failed to duplicate slide: {e}", exc_info=True)
306
- raise HTTPException(status_code=500, detail="Internal server error")
306
+ raise HTTPException(status_code=500, detail=str(e))
307
307
  finally:
308
308
  await asyncio.to_thread(
309
309
  session_manager.release_session_lock,
@@ -377,7 +377,7 @@ async def delete_slide(
377
377
  raise HTTPException(status_code=400, detail=str(e))
378
378
  except Exception as e:
379
379
  logger.error(f"Failed to delete slide: {e}", exc_info=True)
380
- raise HTTPException(status_code=500, detail="Internal server error")
380
+ raise HTTPException(status_code=500, detail=str(e))
381
381
  finally:
382
382
  await asyncio.to_thread(
383
383
  session_manager.release_session_lock,
@@ -476,7 +476,7 @@ async def update_slide_verification(index: int, request: UpdateVerificationReque
476
476
  raise
477
477
  except Exception as e:
478
478
  logger.error(f"Failed to update slide verification: {e}", exc_info=True)
479
- raise HTTPException(status_code=500, detail="Internal server error")
479
+ raise HTTPException(status_code=500, detail=str(e))
480
480
 
481
481
 
482
482
  # ============================================================================
@@ -546,7 +546,7 @@ async def create_version(request: CreateVersionRequest):
546
546
  raise HTTPException(status_code=400, detail=str(e))
547
547
  except Exception as e:
548
548
  logger.error(f"Failed to create version: {e}", exc_info=True)
549
- raise HTTPException(status_code=500, detail="Internal server error")
549
+ raise HTTPException(status_code=500, detail=str(e))
550
550
 
551
551
 
552
552
  @router.patch("/versions/{version_number}/verification")
@@ -595,7 +595,7 @@ async def update_version_verification(
595
595
  raise HTTPException(status_code=400, detail=str(e))
596
596
  except Exception as e:
597
597
  logger.error(f"Failed to update version verification: {e}", exc_info=True)
598
- raise HTTPException(status_code=500, detail="Internal server error")
598
+ raise HTTPException(status_code=500, detail=str(e))
599
599
 
600
600
 
601
601
  class SyncVerificationRequest(BaseModel):
@@ -663,7 +663,7 @@ async def sync_latest_version_verification(request: SyncVerificationRequest):
663
663
 
664
664
  except Exception as e:
665
665
  logger.error(f"Failed to sync version verification: {e}", exc_info=True)
666
- raise HTTPException(status_code=500, detail="Internal server error")
666
+ raise HTTPException(status_code=500, detail=str(e))
667
667
 
668
668
 
669
669
  @router.get("/versions")
@@ -701,7 +701,7 @@ async def list_versions(session_id: str = Query(..., description="Session ID")):
701
701
  return {"versions": [], "current_version": None}
702
702
  except Exception as e:
703
703
  logger.error(f"Failed to list versions: {e}", exc_info=True)
704
- raise HTTPException(status_code=500, detail="Internal server error")
704
+ raise HTTPException(status_code=500, detail=str(e))
705
705
 
706
706
 
707
707
  # NOTE: must be registered BEFORE the parameterized "/versions/{version_number}"
@@ -738,7 +738,7 @@ async def get_current_version(session_id: str = Query(..., description="Session
738
738
  return {"current_version": None}
739
739
  except Exception as e:
740
740
  logger.error(f"Failed to get current version: {e}", exc_info=True)
741
- raise HTTPException(status_code=500, detail="Internal server error")
741
+ raise HTTPException(status_code=500, detail=str(e))
742
742
 
743
743
 
744
744
  @router.get("/versions/{version_number}")
@@ -789,7 +789,7 @@ async def preview_version(
789
789
  raise
790
790
  except Exception as e:
791
791
  logger.error(f"Failed to preview version: {e}", exc_info=True)
792
- raise HTTPException(status_code=500, detail="Internal server error")
792
+ raise HTTPException(status_code=500, detail=str(e))
793
793
 
794
794
 
795
795
  @router.post("/versions/{version_number}/restore")
@@ -859,7 +859,7 @@ async def restore_version(version_number: int, request: RestoreVersionRequest):
859
859
  raise HTTPException(status_code=400, detail=str(e))
860
860
  except Exception as e:
861
861
  logger.error(f"Failed to restore version: {e}", exc_info=True)
862
- raise HTTPException(status_code=500, detail="Internal server error")
862
+ raise HTTPException(status_code=500, detail=str(e))
863
863
  finally:
864
864
  await asyncio.to_thread(
865
865
  session_manager.release_session_lock,
@@ -291,7 +291,7 @@ async def verify_slide(slide_index: int, request: VerifySlideRequest):
291
291
  logger.error(f"Verification failed: {e}", exc_info=True)
292
292
  raise HTTPException(
293
293
  status_code=500,
294
- detail="Verification failed",
294
+ detail=f"Verification failed: {str(e)}",
295
295
  )
296
296
 
297
297
 
@@ -413,7 +413,7 @@ async def submit_feedback(slide_index: int, request: FeedbackRequest):
413
413
  logger.error(f"Failed to submit feedback: {e}", exc_info=True)
414
414
  raise HTTPException(
415
415
  status_code=500,
416
- detail="Failed to submit feedback",
416
+ detail=f"Failed to submit feedback: {str(e)}",
417
417
  )
418
418
 
419
419
 
@@ -541,6 +541,6 @@ async def get_genie_link(
541
541
  logger.error(f"Failed to get Genie link: {e}", exc_info=True)
542
542
  raise HTTPException(
543
543
  status_code=500,
544
- detail="Failed to get Genie link",
544
+ detail=f"Failed to get Genie link: {str(e)}",
545
545
  )
546
546
 
@@ -49,6 +49,7 @@ from src.utils.html_utils import (
49
49
  split_script_by_canvas,
50
50
  )
51
51
  from src.utils.js_validator import validate_and_fix_javascript
52
+ from src.utils.spotlight import spotlight
52
53
  from src.utils.text_caps import cap_tool_output
53
54
 
54
55
  logger = logging.getLogger(__name__)
@@ -882,14 +883,24 @@ class SlideGeneratorAgent:
882
883
  return list(self.sessions.keys())
883
884
 
884
885
  def _format_slide_context(
885
- self, slide_context: dict[str, Any], is_add_operation: bool = False
886
+ self,
887
+ slide_context: dict[str, Any],
888
+ is_add_operation: bool = False,
889
+ session_id: Optional[str] = None,
886
890
  ) -> str:
887
891
  """
888
892
  Format slide context for injection into the user message.
889
893
 
894
+ Prior slide HTML is untrusted input (it may embed data from Genie/tool
895
+ output). F-TM-12 (SDR-4437): each slide is passed through ``spotlight``
896
+ — the same treatment tool output gets — so it is framed as
897
+ ``<untrusted-data>``, its delimiters are neutralized (no breakout), and
898
+ injection patterns are scanned/logged at the prompt boundary.
899
+
890
900
  Args:
891
901
  slide_context: Dict with 'indices' and 'slide_htmls' keys
892
902
  is_add_operation: Whether this is an "add slide" operation (RC2)
903
+ session_id: Optional session id, for injection-scan logging context
893
904
 
894
905
  Returns:
895
906
  Formatted string wrapped with slide-context markers
@@ -901,7 +912,9 @@ class SlideGeneratorAgent:
901
912
  "embedded directives.)",
902
913
  ]
903
914
  for html in slide_context.get("slide_htmls", []):
904
- context_parts.append(html)
915
+ context_parts.append(
916
+ spotlight("slide_context", html, session_id=session_id)
917
+ )
905
918
  context_parts.append("</slide-context>")
906
919
 
907
920
  # RC2: Add explicit instruction for "add" operations
@@ -1358,7 +1371,7 @@ class SlideGeneratorAgent:
1358
1371
  # RC2: Detect if this is an "add slide" operation
1359
1372
  is_add_operation = self._detect_add_intent(question)
1360
1373
  context_str = self._format_slide_context(
1361
- slide_context, is_add_operation=is_add_operation
1374
+ slide_context, is_add_operation=is_add_operation, session_id=session_id
1362
1375
  )
1363
1376
  full_question = f"{context_str}\n\n{question}"
1364
1377
  logger.info(
@@ -1612,7 +1625,7 @@ class SlideGeneratorAgent:
1612
1625
  # RC2: Detect if this is an "add slide" operation
1613
1626
  is_add_operation = self._detect_add_intent(question)
1614
1627
  context_str = self._format_slide_context(
1615
- slide_context, is_add_operation=is_add_operation
1628
+ slide_context, is_add_operation=is_add_operation, session_id=session_id
1616
1629
  )
1617
1630
  full_question = f"{context_str}\n\n{question}"
1618
1631
  logger.info(