django-unicom 25.2.31.dev0__tar.gz → 25.2.32.dev0__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 (193) hide show
  1. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/PKG-INFO +341 -133
  2. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/README.md +340 -132
  3. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/django_unicom.egg-info/PKG-INFO +341 -133
  4. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/django_unicom.egg-info/SOURCES.txt +6 -0
  5. django_unicom-25.2.32.dev0/send_test_buttons.py +54 -0
  6. django_unicom-25.2.32.dev0/test_comprehensive_buttons.py +323 -0
  7. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/models.py +6 -3
  8. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/services/llm_handler.py +27 -13
  9. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/_version.py +2 -2
  10. django_unicom-25.2.32.dev0/unicom/migrations/0014_rename_authorized_user_to_intended_account.py +18 -0
  11. django_unicom-25.2.32.dev0/unicom/migrations/0015_simplify_callback_execution.py +58 -0
  12. django_unicom-25.2.32.dev0/unicom/migrations/0016_callbackexecution_tool_call.py +19 -0
  13. django_unicom-25.2.32.dev0/unicom/models/callback_execution.py +37 -0
  14. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/create_inline_keyboard.py +36 -3
  15. django_unicom-25.2.32.dev0/unicom/services/telegram/handle_telegram_callback.py +103 -0
  16. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/signals.py +2 -2
  17. django_unicom-25.2.32.dev0/unicom_project/callback_handlers.py +174 -0
  18. django_unicom-25.2.32.dev0/unicom_project/definitions/tools/interactive_menu.py +91 -0
  19. django_unicom-25.2.32.dev0/unicom_project/test_button_handlers.py +165 -0
  20. django_unicom-25.2.31.dev0/unicom/models/callback_execution.py +0 -39
  21. django_unicom-25.2.31.dev0/unicom/services/telegram/handle_telegram_callback.py +0 -196
  22. django_unicom-25.2.31.dev0/unicom_project/callback_handlers.py +0 -222
  23. django_unicom-25.2.31.dev0/unicom_project/definitions/tools/interactive_menu.py +0 -104
  24. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/.cursor/rules/match_codebase_style.mdc +0 -0
  25. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/.cursor/rules/preserve_comments_and_irrelevant_code.mdc +0 -0
  26. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/.cursor/rules/require_context_before_changes.mdc +0 -0
  27. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/Dockerfile +0 -0
  28. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/MANIFEST.in +0 -0
  29. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/Makefile +0 -0
  30. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/conftest.py +0 -0
  31. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/data/definitions/bots/__init__.py +0 -0
  32. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/data/definitions/tools/__init__.py +0 -0
  33. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/django_unicom.egg-info/dependency_links.txt +0 -0
  34. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/django_unicom.egg-info/requires.txt +0 -0
  35. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/django_unicom.egg-info/top_level.txt +0 -0
  36. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/docker-compose.yaml +0 -0
  37. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/entrypoint.sh +0 -0
  38. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/manage.py +0 -0
  39. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/pyproject.toml +0 -0
  40. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/pytest.ini +0 -0
  41. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/requirements.txt +0 -0
  42. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/setup.cfg +0 -0
  43. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/setup.py +0 -0
  44. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/test_dkim_security.py +0 -0
  45. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/__init__.py +0 -0
  46. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/test_email_authentication.py +0 -0
  47. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/test_email_live.py +0 -0
  48. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/test_email_request_processing.py +0 -0
  49. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/test_telegram_live.py +0 -0
  50. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/utils.py +0 -0
  51. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/__init__.py +0 -0
  52. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/admin.py +0 -0
  53. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/apps.py +0 -0
  54. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/0001_initial.py +0 -0
  55. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/0002_tool_bot_tools.py +0 -0
  56. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/0003_encryptedcredential.py +0 -0
  57. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/0004_credentialfielddefinition_and_more.py +0 -0
  58. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/0005_alter_credentialfielddefinition_key.py +0 -0
  59. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/__init__.py +0 -0
  60. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/services/__init__.py +0 -0
  61. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/services/credentials.py +0 -0
  62. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/services/tool_result_handler.py +0 -0
  63. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/tests.py +0 -0
  64. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/urls.py +0 -0
  65. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/views.py +0 -0
  66. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/__init__.py +0 -0
  67. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/__init__.py +0 -0
  68. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/account_admin.py +0 -0
  69. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/channel_admin.py +0 -0
  70. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/chat_admin.py +0 -0
  71. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/draft_message_admin.py +0 -0
  72. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/email_inline_image_admin.py +0 -0
  73. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/filters.py +0 -0
  74. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/member_admin.py +0 -0
  75. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/message_admin.py +0 -0
  76. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/message_template_admin.py +0 -0
  77. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/request_admin.py +0 -0
  78. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/apps.py +0 -0
  79. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/__init__.py +0 -0
  80. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/commands/__init__.py +0 -0
  81. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/commands/mail_inbox_listen.py +0 -0
  82. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/commands/run_as_llm_chat.py +0 -0
  83. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/commands/send_scheduled_messages.py +0 -0
  84. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/commands/start_imap_listeners.py +0 -0
  85. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0001_initial.py +0 -0
  86. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0002_emailinlineimage.py +0 -0
  87. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0003_alter_emailinlineimage_email_message.py +0 -0
  88. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0004_messagetemplateinlineimage.py +0 -0
  89. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0005_emailinlineimage_hash_and_more.py +0 -0
  90. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0006_channel_created_at_channel_created_by_and_more.py +0 -0
  91. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0007_message_imap_uid.py +0 -0
  92. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0008_add_all_members_group.py +0 -0
  93. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0009_add_tool_call_types.py +0 -0
  94. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0010_request_initial_request_request_llm_calls_count_and_more.py +0 -0
  95. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0011_toolcall_add_message_reference.py +0 -0
  96. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0012_message_response_to_tool_call_and_more.py +0 -0
  97. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0013_callbackexecution.py +0 -0
  98. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/__init__.py +0 -0
  99. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/__init__.py +0 -0
  100. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/account.py +0 -0
  101. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/account_chat.py +0 -0
  102. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/channel.py +0 -0
  103. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/chat.py +0 -0
  104. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/constants.py +0 -0
  105. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/draft_message.py +0 -0
  106. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/fields.py +0 -0
  107. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/member.py +0 -0
  108. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/member_group.py +0 -0
  109. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/message.py +0 -0
  110. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/message_template.py +0 -0
  111. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/request.py +0 -0
  112. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/request_category.py +0 -0
  113. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/tool_call.py +0 -0
  114. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/update.py +0 -0
  115. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/__init__.py +0 -0
  116. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/chat_summary.py +0 -0
  117. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/crossplatform/__init__.py +0 -0
  118. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/crossplatform/reply_to_message.py +0 -0
  119. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/crossplatform/scheduler.py +0 -0
  120. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/crossplatform/send_message.py +0 -0
  121. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/decode_base64_image.py +0 -0
  122. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/IMAP_thread_manager.py +0 -0
  123. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/__init__.py +0 -0
  124. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/email_tracking.py +0 -0
  125. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/listen_to_IMAP.py +0 -0
  126. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/quote_filter.py +0 -0
  127. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/replace_cid_images_with_base64.py +0 -0
  128. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/save_email_message.py +0 -0
  129. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/send_email_message.py +0 -0
  130. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/validate_email_config.py +0 -0
  131. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/get_public_origin.py +0 -0
  132. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/html_inline_images.py +0 -0
  133. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/internal/__init__.py +0 -0
  134. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/internal/generate_text_message_data.py +0 -0
  135. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/internal/save_internal_message.py +0 -0
  136. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/internal/send_internal_message.py +0 -0
  137. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/llm/README.md +0 -0
  138. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/llm/__init__.py +0 -0
  139. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/llm/tool_calls.py +0 -0
  140. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/__init__.py +0 -0
  141. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/answer_callback_query.py +0 -0
  142. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/download_file.py +0 -0
  143. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/edit_telegram_message.py +0 -0
  144. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/escape_markdown.py +0 -0
  145. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/get_file_path.py +0 -0
  146. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/save_telegram_message.py +0 -0
  147. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/send_telegram_message.py +0 -0
  148. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/set_telegram_webhook.py +0 -0
  149. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/start_typing_in_telegram.py +0 -0
  150. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/stop_typing_in_telegram.py +0 -0
  151. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/whatsapp/__init__.py +0 -0
  152. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/whatsapp/get_template.py +0 -0
  153. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/whatsapp/save_whatsapp_message.py +0 -0
  154. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/whatsapp/save_whatsapp_message_status.py +0 -0
  155. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/whatsapp/send_whatsapp_message.py +0 -0
  156. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/static/unicom/css/bootstrap_scoped.css +0 -0
  157. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/static/unicom/css/draft_message_mobile.css +0 -0
  158. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/static/unicom/js/channel_config.js +0 -0
  159. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/static/unicom/js/tinymce_ai_template.js +0 -0
  160. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/static/unicom/js/tinymce_init.js +0 -0
  161. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/chat/change_list.html +0 -0
  162. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/chat/compose.html +0 -0
  163. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/chat_history.html +0 -0
  164. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/forms/email_message_form.html +0 -0
  165. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/forms/text_message_form.html +0 -0
  166. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/includes/ai_template_modal.html +0 -0
  167. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/includes/loading_indicators.html +0 -0
  168. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/includes/message_actions_menu.html +0 -0
  169. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/messagetemplate/change_form.html +0 -0
  170. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/code_templates/category_processor.py +0 -0
  171. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/urls.py +0 -0
  172. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/__init__.py +0 -0
  173. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/chat_history_view.py +0 -0
  174. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/compose_view.py +0 -0
  175. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/email_tracking.py +0 -0
  176. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/inline_image.py +0 -0
  177. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/message_template.py +0 -0
  178. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/telegram_webhook.py +0 -0
  179. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/whatsapp_webhook.py +0 -0
  180. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/__init__.py +0 -0
  181. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/apps.py +0 -0
  182. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/asgi.py +0 -0
  183. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/__init__.py +0 -0
  184. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/bots/__init__.py +0 -0
  185. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/bots/assistant_bot.py +0 -0
  186. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/tools/__init__.py +0 -0
  187. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/tools/interval_alarm.py +0 -0
  188. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/tools/ip_lookup.py +0 -0
  189. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/tools/simple_timer.py +0 -0
  190. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/tools/system_info.py +0 -0
  191. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/settings.py +0 -0
  192. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/urls.py +0 -0
  193. {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/wsgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-unicom
3
- Version: 25.2.31.dev0
3
+ Version: 25.2.32.dev0
4
4
  Summary: Unified communication layer for Django (Telegram, WhatsApp, Email)
5
5
  Home-page: https://github.com/meena-erian/unicom
6
6
  Author: Meena (Menas) Erian
@@ -61,6 +61,9 @@ Dynamic: summary
61
61
  - [Telegram-Specific Features](#telegram-specific-features)
62
62
  - [Typing Indicators](#-typing-indicators)
63
63
  - [Interactive Buttons & Callbacks](#-interactive-messages-with-action-buttons)
64
+ - [Handling Button Clicks](#-handling-button-clicks)
65
+ - [Tool-Generated Buttons](#-tool-generated-buttons-advanced)
66
+ - [Button Routing Best Practices](#-button-routing-best-practices)
64
67
  - [Editing Messages](#-editing-telegram-messages)
65
68
  - [File Downloads and Voice Messages](#-file-downloads-and-voice-messages)
66
69
  - [LLM Integration](#llm-integration)
@@ -287,17 +290,19 @@ message = email_channel.send_message({
287
290
  })
288
291
 
289
292
  # 📱 Telegram only: Message with interactive buttons
290
- from unicom.services.telegram.create_inline_keyboard import create_simple_keyboard
293
+ from unicom.services.telegram.create_inline_keyboard import create_callback_button, create_inline_keyboard
291
294
 
295
+ # For initial messages, use legacy mode (no message parameter)
296
+ # Buttons will work but won't be linked to CallbackExecution
292
297
  message = telegram_channel.send_message({
293
298
  'chat_id': 'user_chat_id',
294
299
  'text': 'Choose an option:',
295
- 'reply_markup': create_simple_keyboard(
296
- "Confirm", "confirm_yes",
297
- "Cancel", "confirm_no"
298
- )
300
+ 'reply_markup': create_inline_keyboard([
301
+ [create_callback_button("Confirm", {"action": "confirm"})],
302
+ [create_callback_button("Cancel", {"action": "cancel"})]
303
+ ])
299
304
  })
300
- # See "Interactive Buttons & Callbacks" section for handling button clicks
305
+ # See "Interactive Buttons & Callbacks" section for full details
301
306
  ```
302
307
 
303
308
  ### Message Model
@@ -441,14 +446,14 @@ reply = message.reply_with({
441
446
  })
442
447
 
443
448
  # 📱 Telegram only: Reply with interactive buttons
444
- from unicom.services.telegram.create_inline_keyboard import create_simple_keyboard
449
+ from unicom.services.telegram.create_inline_keyboard import create_inline_keyboard, create_callback_button
445
450
 
446
451
  reply = message.reply_with({
447
452
  'text': 'Would you like to continue?',
448
- 'reply_markup': create_simple_keyboard(
449
- "Yes", "continue_yes",
450
- "No", "continue_no"
451
- )
453
+ 'reply_markup': create_inline_keyboard([
454
+ [create_callback_button("Yes", {"action": "continue", "value": True}, message=message)],
455
+ [create_callback_button("No", {"action": "continue", "value": False}, message=message)]
456
+ ])
452
457
  })
453
458
  # See "Interactive Buttons & Callbacks" section for handling button clicks
454
459
  ```
@@ -660,39 +665,46 @@ message = telegram_channel.send_message({
660
665
 
661
666
  #### 📱 Interactive Messages with Action Buttons
662
667
 
663
- Telegram channels support inline keyboard buttons for interactive messages:
668
+ Telegram channels support inline keyboard buttons. Pass any JSON-serializable `callback_data`:
664
669
 
665
670
  ```python
666
671
  from unicom.services.telegram.create_inline_keyboard import (
667
- create_inline_keyboard, create_callback_button, create_url_button, create_simple_keyboard
672
+ create_inline_keyboard, create_callback_button, create_url_button
668
673
  )
669
674
 
670
- # Send message with custom inline keyboard
671
- reply_markup = create_inline_keyboard([
672
- [create_callback_button("Yes", "yes_action"), create_callback_button("No", "no_action")],
673
- [create_url_button("Visit Website", "https://example.com")]
674
- ])
675
-
676
- message = telegram_channel.send_message({
677
- 'chat_id': 'telegram_chat_id',
675
+ # When replying to existing messages, pass message and account for full features
676
+ # This creates CallbackExecution records and enables security + tool integration
677
+ incoming_message.reply_with({
678
678
  'text': 'Do you want to continue?',
679
- 'reply_markup': reply_markup
679
+ 'reply_markup': create_inline_keyboard([
680
+ [create_callback_button("Yes", {"action": "confirm"}, message=incoming_message)],
681
+ [create_callback_button("No", {"action": "cancel"}, message=incoming_message)],
682
+ [create_url_button("Visit Website", "https://example.com")]
683
+ ])
680
684
  })
681
685
 
682
- # Quick helper for simple button layouts
683
- simple_keyboard = create_simple_keyboard("Option 1", "opt1", "Option 2", "opt2")
684
- message = telegram_channel.send_message({
685
- 'chat_id': 'telegram_chat_id',
686
- 'text': 'Choose an option:',
687
- 'reply_markup': simple_keyboard
686
+ # Rich data structures with any JSON-serializable data
687
+ incoming_message.reply_with({
688
+ 'text': 'Choose a product:',
689
+ 'reply_markup': create_inline_keyboard([
690
+ [create_callback_button("Product A", {"product_id": 123, "price": 29.99, "stock": 5}, message=incoming_message)],
691
+ [create_callback_button("Product B", {"product_id": 456, "price": 49.99, "stock": 12}, message=incoming_message)]
692
+ ])
688
693
  })
689
694
 
690
- # Works with all wrapper functions like reply_with
691
- reply = message.reply_with({
692
- 'text': 'Here are your options:',
695
+ # Optional expiration for time-limited offers
696
+ from django.utils import timezone
697
+ from datetime import timedelta
698
+
699
+ incoming_message.reply_with({
700
+ 'text': 'Limited time offer!',
693
701
  'reply_markup': create_inline_keyboard([
694
- [create_callback_button("Confirm", "confirm_action")],
695
- [create_callback_button("Cancel", "cancel_action")]
702
+ [create_callback_button(
703
+ "Claim Offer",
704
+ {"offer_id": 789, "discount": 0.5},
705
+ message=incoming_message,
706
+ expires_at=timezone.now() + timedelta(hours=24)
707
+ )]
696
708
  ])
697
709
  })
698
710
  ```
@@ -706,62 +718,53 @@ from django.dispatch import receiver
706
718
  from unicom.signals import telegram_callback_received
707
719
 
708
720
  @receiver(telegram_callback_received)
709
- def handle_button_clicks(sender, callback_execution, **kwargs):
721
+ def handle_button_clicks(sender, callback_execution, clicking_account, original_message, tool_call, **kwargs):
710
722
  """
711
- Handle all button clicks with automatic security validation and idempotency.
712
- Each callback is processed exactly once across all Django processes.
713
-
714
- Available data in callback_execution:
715
- - callback_data: The button's callback_data string
716
- - authorized_user: The validated user (Account object)
717
- - callback_message: Message object for sending responses
718
- - original_message: The message that contained the buttons
719
- """
720
- # Get the button data and validated user
721
- button_data = callback_execution.callback_data # e.g., "confirm_action"
722
- user = callback_execution.authorized_user # Security-validated user
723
- callback_msg = callback_execution.callback_message # For replying
724
- original_msg = callback_execution.original_message # Original message with buttons
725
-
726
- if button_data == 'confirm_action':
727
- # Process confirmation
728
- process_user_confirmation(user)
729
- callback_msg.reply_with({'text': '✅ Confirmed!'})
730
-
731
- elif button_data == 'cancel_action':
732
- # Process cancellation
733
- callback_msg.reply_with({'text': '❌ Cancelled'})
734
-
735
- elif button_data.startswith('product_'):
736
- # Handle dynamic data (e.g., "product_123")
737
- product_id = button_data.split('_')[1]
738
- product = get_product(product_id)
739
-
740
- # Send response with new buttons
741
- callback_msg.reply_with({
742
- 'text': f'Product: {product.name}\nPrice: ${product.price}',
743
- 'reply_markup': create_simple_keyboard(
744
- 'Buy Now', f'buy_{product_id}',
745
- 'Add to Cart', f'cart_{product_id}'
746
- )
747
- })
723
+ Handle button clicks.
748
724
 
749
- elif button_data == 'show_menu':
750
- # Update the original message (edit in place)
751
- from unicom.services.telegram.create_inline_keyboard import create_inline_keyboard, create_callback_button
752
- callback_msg.edit_original_message({
753
- 'text': '📋 Main Menu:',
754
- 'reply_markup': create_inline_keyboard([
755
- [create_callback_button("Option 1", "opt1")],
756
- [create_callback_button("Option 2", "opt2")]
757
- ])
758
- })
725
+ Args:
726
+ callback_execution: CallbackExecution instance with callback_data
727
+ clicking_account: The unicom.Account that clicked the button
728
+ original_message: The Message containing the buttons
729
+ tool_call: Optional ToolCall if button was from a tool (None otherwise)
759
730
 
760
- # Security is automatic:
761
- # - Only authorized users can click buttons (prevents group chat abuse)
762
- # - Each callback processes exactly once (prevents duplicate actions)
763
- # - Built-in protection against forwarded messages and replay attacks
764
- # - Loading indicators stop automatically (answerCallbackQuery)
731
+ Note: unicom.Account represents a platform user (e.g., Telegram user, email address).
732
+ To access Django auth.User: clicking_account.member.user (if member exists)
733
+ """
734
+ data = callback_execution.callback_data
735
+
736
+ # Handle dict callback_data
737
+ if isinstance(data, dict):
738
+ if data.get('action') == 'confirm':
739
+ process_confirmation(clicking_account)
740
+ original_message.reply_with({'text': '✅ Confirmed!'})
741
+
742
+ elif data.get('action') == 'buy_product':
743
+ product_id = data['product_id']
744
+ product = get_product(product_id)
745
+
746
+ # Create new buttons with callback_data
747
+ original_message.reply_with({
748
+ 'text': f'Product: {product.name}\nPrice: ${product.price}',
749
+ 'reply_markup': create_inline_keyboard([
750
+ [create_callback_button('Confirm Purchase', {'action': 'confirm_purchase', 'product_id': product_id}, message=original_message, account=clicking_account)],
751
+ [create_callback_button('Cancel', {'action': 'cancel'}, message=original_message, account=clicking_account)]
752
+ ])
753
+ })
754
+
755
+ # Handle string callback_data
756
+ elif data == 'cancel':
757
+ original_message.reply_with({'text': '❌ Cancelled'})
758
+
759
+ # Access Django User if needed
760
+ if clicking_account.member and clicking_account.member.user:
761
+ django_user = clicking_account.member.user
762
+ # Do something with django_user
763
+
764
+ # If button was from a tool, you can respond to the tool call
765
+ if tool_call and data.get('action') == 'confirm':
766
+ # Inform the LLM that the user confirmed
767
+ tool_call.respond({'confirmed': True, 'user_id': clicking_account.id})
765
768
  ```
766
769
 
767
770
  **Where to put your callback handler:**
@@ -781,24 +784,188 @@ class YourAppConfig(AppConfig):
781
784
 
782
785
  Make sure your app is in `INSTALLED_APPS` in settings.py.
783
786
 
784
- **Security Features (Automatic):**
787
+ **Key Features:**
788
+
789
+ - **Flexible Data**: Store any JSON-serializable data (dict, list, str, int, bool, None)
790
+ - **Security**: Only the intended account can click the button
791
+ - **Expiration**: Optional `expires_at` parameter for time-limited buttons
792
+ - **Reusable**: Buttons can be clicked multiple times (developers control behavior)
793
+ - **Efficient**: Callback data stored in DB, only ID sent to Telegram
794
+ - **No Message Creation**: Button clicks don't create Message objects - they only trigger handlers
795
+ - **Tool Integration**: When buttons are from tools, handlers can use `tool_call.respond()` to inform the LLM
796
+
797
+ #### 📱 Tool-Generated Buttons (Advanced)
798
+
799
+ When a tool sends buttons, you can link them to the ToolCall so handlers can respond to the LLM:
800
+
801
+ ```python
802
+ # In your tool code (e.g., unibot Tool model)
803
+ def my_interactive_tool(question: str) -> str:
804
+ """Ask user a question with buttons and wait for response."""
805
+
806
+ # tool_call is available in tool context - no need to query!
807
+ # Just use it directly
808
+
809
+ # Send message with buttons linked to this tool call
810
+ message.reply_with({
811
+ 'text': f'Question: {question}',
812
+ 'reply_markup': create_inline_keyboard([
813
+ [create_callback_button(
814
+ "Yes",
815
+ {"tool": "my_interactive_tool", "action": "answer", "value": "yes"},
816
+ message=message,
817
+ tool_call=tool_call # Use the context variable
818
+ )],
819
+ [create_callback_button(
820
+ "No",
821
+ {"tool": "my_interactive_tool", "action": "answer", "value": "no"},
822
+ message=message,
823
+ tool_call=tool_call
824
+ )]
825
+ ])
826
+ })
827
+
828
+ # Return None to defer response - tool will respond when user clicks
829
+ return None
830
+
831
+ # In your callback handler (callback_handlers.py)
832
+ @receiver(telegram_callback_received)
833
+ def handle_tool_buttons(sender, callback_execution, clicking_account, original_message, tool_call, **kwargs):
834
+ data = callback_execution.callback_data
835
+
836
+ # Route to correct handler based on tool name
837
+ if isinstance(data, dict) and data.get('tool') == 'my_interactive_tool':
838
+ if data.get('action') == 'answer':
839
+ # User clicked a button from the tool
840
+ answer = data['value']
841
+
842
+ # Respond to the tool call - this will notify the LLM
843
+ if tool_call:
844
+ tool_call.respond({
845
+ 'question_answered': True,
846
+ 'answer': answer,
847
+ 'user_id': clicking_account.id
848
+ })
849
+
850
+ # Also send confirmation to user
851
+ original_message.reply_with({
852
+ 'text': f'✅ You answered: {answer}'
853
+ })
854
+ ```
855
+
856
+ #### 📱 Button Routing Best Practices
857
+
858
+ When building applications with multiple button types, use a consistent routing strategy:
859
+
860
+ **Recommended Pattern: Use a "type" or "handler" field**
861
+
862
+ ```python
863
+ # Define button types as constants for consistency
864
+ BUTTON_TYPES = {
865
+ 'PRODUCT': 'product_handler',
866
+ 'NAVIGATION': 'nav_handler',
867
+ 'SETTINGS': 'settings_handler',
868
+ 'TOOL': 'tool_handler'
869
+ }
870
+
871
+ # Create buttons with type field
872
+ create_callback_button(
873
+ "Buy Product A",
874
+ {
875
+ "type": "product_handler", # Routes to product handler
876
+ "action": "buy",
877
+ "product_id": 123
878
+ },
879
+ message=message
880
+ )
881
+
882
+ create_callback_button(
883
+ "Settings",
884
+ {
885
+ "type": "settings_handler", # Routes to settings handler
886
+ "action": "show_settings"
887
+ },
888
+ message=message
889
+ )
890
+
891
+ # In your callback handler - route based on type
892
+ @receiver(telegram_callback_received)
893
+ def handle_all_buttons(sender, callback_execution, clicking_account, original_message, tool_call, **kwargs):
894
+ data = callback_execution.callback_data
895
+
896
+ if not isinstance(data, dict):
897
+ return # Skip non-dict data
898
+
899
+ # Route to appropriate handler based on type
900
+ handler_type = data.get('type')
901
+
902
+ if handler_type == 'product_handler':
903
+ handle_product_buttons(data, clicking_account, original_message)
904
+ elif handler_type == 'settings_handler':
905
+ handle_settings_buttons(data, clicking_account, original_message)
906
+ elif handler_type == 'tool_handler':
907
+ handle_tool_buttons(data, clicking_account, original_message, tool_call)
908
+ else:
909
+ # Unknown type - log or handle gracefully
910
+ print(f"Unknown button type: {handler_type}")
911
+
912
+ def handle_product_buttons(data, account, message):
913
+ """Handle product-related button clicks"""
914
+ if data.get('action') == 'buy':
915
+ product_id = data['product_id']
916
+ # Process purchase...
917
+ message.reply_with({'text': f'Processing purchase for product {product_id}'})
918
+
919
+ def handle_settings_buttons(data, account, message):
920
+ """Handle settings-related button clicks"""
921
+ if data.get('action') == 'show_settings':
922
+ # Show settings menu...
923
+ message.edit_original_message({'text': '⚙️ Settings Menu'})
924
+
925
+ def handle_tool_buttons(data, account, message, tool_call):
926
+ """Handle tool-generated button clicks"""
927
+ if tool_call and data.get('action') == 'confirm':
928
+ tool_call.respond({'confirmed': True})
929
+ message.reply_with({'text': '✅ Confirmed'})
930
+ ```
931
+
932
+ **Alternative Pattern: Multiple Signal Receivers**
785
933
 
786
- - **Authorization**: Only the intended recipient can click buttons
787
- - In private chats: Only that user can click
788
- - In group chats: Only the original message recipient can click
789
- - Forwarded messages: Buttons are automatically disabled
934
+ ```python
935
+ # Register separate handlers for different button types
936
+ @receiver(telegram_callback_received)
937
+ def handle_product_buttons(sender, callback_execution, **kwargs):
938
+ data = callback_execution.callback_data
939
+ # Only handle product buttons
940
+ if isinstance(data, dict) and data.get('type') == 'product':
941
+ # Handle product actions...
942
+ pass
790
943
 
791
- - **Idempotency**: Each button click processes exactly once
792
- - Safe across multiple Django processes
793
- - Database-backed coordination
794
- - Prevents duplicate actions (e.g., double charges)
944
+ @receiver(telegram_callback_received)
945
+ def handle_navigation_buttons(sender, callback_execution, **kwargs):
946
+ data = callback_execution.callback_data
947
+ # Only handle navigation buttons
948
+ if isinstance(data, dict) and data.get('type') == 'nav':
949
+ # Handle navigation...
950
+ pass
951
+ ```
795
952
 
796
- - **User Experience**:
797
- - Loading indicators stop immediately
798
- - Fast response times
799
- - Graceful network error handling
953
+ **Scalable Data Structure Example:**
954
+
955
+ ```python
956
+ # Well-structured callback data for complex applications
957
+ callback_data = {
958
+ "type": "product_handler", # Routes to correct handler
959
+ "action": "add_to_cart", # Specific action
960
+ "entity_type": "product", # Type of entity
961
+ "entity_id": 123, # Entity identifier
962
+ "metadata": { # Additional context
963
+ "source": "search_results",
964
+ "page": 2
965
+ }
966
+ }
967
+ ```
800
968
 
801
- **For more examples and best practices**, see the [detailed Telegram buttons guide](docs/telegram_buttons_guide.md).
802
969
 
803
970
  #### 📱 Editing Telegram Messages
804
971
 
@@ -818,24 +985,22 @@ edit_telegram_message(telegram_channel, message, {
818
985
  })
819
986
 
820
987
  # Edit messages with buttons (common in callback handlers)
821
- # Use the edit_original_message() method on callback messages
822
988
  from django.dispatch import receiver
823
989
  from unicom.signals import telegram_callback_received
824
990
  from unicom.services.telegram.create_inline_keyboard import create_inline_keyboard, create_callback_button
825
991
 
826
992
  @receiver(telegram_callback_received)
827
- def handle_navigation(sender, callback_execution, **kwargs):
993
+ def handle_navigation(sender, callback_execution, clicking_account, original_message, tool_call, **kwargs):
828
994
  button_data = callback_execution.callback_data
829
- callback_msg = callback_execution.callback_message
830
995
 
831
996
  if button_data == 'show_settings':
832
997
  # Edit the original message to show settings
833
- callback_msg.edit_original_message({
998
+ original_message.edit_original_message({
834
999
  'text': '⚙️ Settings Menu',
835
1000
  'reply_markup': create_inline_keyboard([
836
- [create_callback_button("Account", "settings_account")],
837
- [create_callback_button("Privacy", "settings_privacy")],
838
- [create_callback_button("🔙 Back", "main_menu")]
1001
+ [create_callback_button("Account", "settings_account", message=original_message, account=clicking_account)],
1002
+ [create_callback_button("Privacy", "settings_privacy", message=original_message, account=clicking_account)],
1003
+ [create_callback_button("🔙 Back", "main_menu", message=original_message, account=clicking_account)]
839
1004
  ])
840
1005
  })
841
1006
  ```
@@ -929,35 +1094,78 @@ chat.log_tool_interaction(
929
1094
 
930
1095
  The LLM system supports delayed tool calls that can take hours or days to complete, perfect for reminders, monitoring, and long-running processes.
931
1096
 
1097
+ **Tool Implementation - Return `None` to Defer Response:**
1098
+
932
1099
  ```python
933
- from unicom.models import Request, ToolCall
1100
+ # In your tool definition (e.g., unibot Tool model)
1101
+ def set_reminder(text: str, delay_hours: int) -> str:
1102
+ """Schedule a reminder for later"""
1103
+ from django.utils import timezone
1104
+ from datetime import timedelta
1105
+
1106
+ # Schedule the reminder using the tool_call context variable
1107
+ reminder_time = timezone.now() + timedelta(hours=delay_hours)
1108
+
1109
+ # Store tool_call.id for later response
1110
+ # (e.g., in a database, Redis, or scheduler)
1111
+ schedule_reminder(
1112
+ tool_call_id=tool_call.call_id,
1113
+ message=text,
1114
+ scheduled_time=reminder_time
1115
+ )
934
1116
 
935
- # Submit multiple tool calls from a request (atomic operation)
936
- request = Request.objects.get(id='request_id')
937
- tool_calls = request.submit_tool_calls([
938
- {
939
- "name": "set_reminder",
940
- "arguments": {"text": "Meeting tomorrow", "delay_hours": 24},
941
- "id": "call_123" # Optional, auto-generated if omitted
1117
+ # Return None to defer the response
1118
+ # System automatically marks tool_call as IN_PROGRESS
1119
+ return None
1120
+
1121
+ tool_definition = {
1122
+ "name": "set_reminder",
1123
+ "description": "Set a reminder for a specific time in the future",
1124
+ "parameters": {
1125
+ "text": {"type": "string", "description": "Reminder text"},
1126
+ "delay_hours": {"type": "integer", "description": "Hours to wait"}
942
1127
  },
943
- {
944
- "name": "monitor_system",
945
- "arguments": {"threshold": 90}
946
- }
947
- ])
1128
+ "run": set_reminder
1129
+ }
1130
+ ```
1131
+
1132
+ **Responding Later from Any Process:**
1133
+
1134
+ ```python
1135
+ from unicom.models import ToolCall
1136
+
1137
+ # Hours or days later, in a background job or scheduled task...
1138
+ tool_call = ToolCall.objects.get(call_id="call_123")
1139
+
1140
+ # Respond to the tool call
1141
+ msg, child_request = tool_call.respond("Reminder: Meeting in 1 hour")
1142
+ # This creates a new child request for the LLM to process
1143
+
1144
+ # The LLM receives the response and can continue the conversation
1145
+ ```
1146
+
1147
+ **For Periodic/Ongoing Tools (e.g., Monitoring):**
1148
+
1149
+ ```python
1150
+ def monitor_system(threshold: int) -> str:
1151
+ """Monitor system continuously and report status"""
1152
+
1153
+ # Mark as ACTIVE for periodic responses
1154
+ tool_call.mark_active()
1155
+
1156
+ # Start background monitoring
1157
+ start_monitoring_task(tool_call_id=tool_call.call_id, threshold=threshold)
1158
+
1159
+ return None # Or return initial status
1160
+
1161
+ # Later, in your monitoring task...
1162
+ tool_call = ToolCall.objects.get(call_id="monitor_123", status='ACTIVE')
1163
+
1164
+ # Send periodic updates without creating child requests
1165
+ tool_call.respond("CPU usage: 95%") # Just logs, no child request
1166
+ tool_call.respond("CPU usage: 92%") # Just logs, no child request
948
1167
 
949
- # Days later... respond to tool calls
950
- reminder_call = ToolCall.objects.get(call_id="call_123")
951
- msg, child_request = reminder_call.respond("Reminder: Meeting in 1 hour")
952
- # Creates new child request for further processing
953
-
954
- # For periodic/ongoing tools, set status to ACTIVE
955
- monitor_call = tool_calls[1]
956
- monitor_call.status = 'ACTIVE'
957
- monitor_call.save()
958
- # Now it can respond indefinitely without creating child requests
959
- monitor_call.respond("CPU usage: 95%") # Just logs, no child request
960
- monitor_call.respond("CPU usage: 92%") # Just logs, no child request
1168
+ # Tool call remains ACTIVE and can respond indefinitely
961
1169
  ```
962
1170
 
963
1171
  #### 🤖 Request Hierarchy and Final Response Logic