django-unicom 25.2.27.dev3__tar.gz → 25.2.27.dev4__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.
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/PKG-INFO +1 -1
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/django_unicom.egg-info/PKG-INFO +1 -1
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/django_unicom.egg-info/SOURCES.txt +1 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/_version.py +2 -2
- django_unicom-25.2.27.dev4/unicom/migrations/0012_message_response_to_tool_call_and_more.py +25 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/message.py +100 -6
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/message_template.py +0 -4
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/request.py +1 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/tool_call.py +8 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/IMAP_thread_manager.py +0 -2
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/listen_to_IMAP.py +2 -8
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/send_email_message.py +1 -4
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/html_inline_images.py +0 -4
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/send_telegram_message.py +1 -4
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/.cursor/rules/match_codebase_style.mdc +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/.cursor/rules/preserve_comments_and_irrelevant_code.mdc +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/.cursor/rules/require_context_before_changes.mdc +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/Dockerfile +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/MANIFEST.in +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/Makefile +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/README.md +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/conftest.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/django_unicom.egg-info/dependency_links.txt +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/django_unicom.egg-info/requires.txt +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/django_unicom.egg-info/top_level.txt +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/docker-compose.yaml +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/entrypoint.sh +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/manage.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/pyproject.toml +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/pytest.ini +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/requirements.txt +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/setup.cfg +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/setup.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/test_dkim_security.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/tests/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/tests/test_email_authentication.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/tests/test_email_live.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/tests/test_email_request_processing.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/tests/test_telegram_live.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/tests/utils.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/account_admin.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/channel_admin.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/chat_admin.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/draft_message_admin.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/email_inline_image_admin.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/filters.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/member_admin.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/message_admin.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/message_template_admin.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/request_admin.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/apps.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/management/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/management/commands/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/management/commands/mail_inbox_listen.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/management/commands/run_as_llm_chat.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/management/commands/send_scheduled_messages.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/management/commands/start_imap_listeners.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0001_initial.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0002_emailinlineimage.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0003_alter_emailinlineimage_email_message.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0004_messagetemplateinlineimage.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0005_emailinlineimage_hash_and_more.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0006_channel_created_at_channel_created_by_and_more.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0007_message_imap_uid.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0008_add_all_members_group.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0009_add_tool_call_types.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0010_request_initial_request_request_llm_calls_count_and_more.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0011_toolcall_add_message_reference.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/account.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/account_chat.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/channel.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/chat.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/constants.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/draft_message.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/fields.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/member.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/member_group.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/request_category.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/models/update.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/chat_summary.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/crossplatform/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/crossplatform/reply_to_message.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/crossplatform/scheduler.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/crossplatform/send_message.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/decode_base64_image.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/email_tracking.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/quote_filter.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/replace_cid_images_with_base64.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/save_email_message.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/validate_email_config.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/get_public_origin.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/internal/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/internal/generate_text_message_data.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/internal/save_internal_message.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/internal/send_internal_message.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/llm/README.md +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/llm/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/llm/tool_calls.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/download_file.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/escape_markdown.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/get_file_path.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/save_telegram_message.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/set_telegram_webhook.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/start_typing_in_telegram.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/stop_typing_in_telegram.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/whatsapp/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/whatsapp/get_template.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/whatsapp/save_whatsapp_message.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/whatsapp/save_whatsapp_message_status.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/whatsapp/send_whatsapp_message.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/signals.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/static/unicom/css/bootstrap_scoped.css +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/static/unicom/css/draft_message_mobile.css +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/static/unicom/js/channel_config.js +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/static/unicom/js/tinymce_ai_template.js +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/static/unicom/js/tinymce_init.js +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/templates/admin/unicom/chat/change_list.html +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/templates/admin/unicom/chat/compose.html +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/templates/admin/unicom/chat_history.html +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/templates/admin/unicom/forms/email_message_form.html +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/templates/admin/unicom/forms/text_message_form.html +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/templates/admin/unicom/includes/ai_template_modal.html +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/templates/admin/unicom/includes/loading_indicators.html +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/templates/admin/unicom/includes/message_actions_menu.html +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/templates/admin/unicom/messagetemplate/change_form.html +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/templates/code_templates/category_processor.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/urls.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/views/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/views/chat_history_view.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/views/compose_view.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/views/email_tracking.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/views/inline_image.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/views/message_template.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/views/telegram_webhook.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/views/whatsapp_webhook.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom_project/__init__.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom_project/asgi.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom_project/settings.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom_project/urls.py +0 -0
- {django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom_project/wsgi.py +0 -0
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/django_unicom.egg-info/SOURCES.txt
RENAMED
|
@@ -58,6 +58,7 @@ unicom/migrations/0008_add_all_members_group.py
|
|
|
58
58
|
unicom/migrations/0009_add_tool_call_types.py
|
|
59
59
|
unicom/migrations/0010_request_initial_request_request_llm_calls_count_and_more.py
|
|
60
60
|
unicom/migrations/0011_toolcall_add_message_reference.py
|
|
61
|
+
unicom/migrations/0012_message_response_to_tool_call_and_more.py
|
|
61
62
|
unicom/migrations/__init__.py
|
|
62
63
|
unicom/models/__init__.py
|
|
63
64
|
unicom/models/account.py
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '25.2.27.
|
|
32
|
-
__version_tuple__ = version_tuple = (25, 2, 27, '
|
|
31
|
+
__version__ = version = '25.2.27.dev4'
|
|
32
|
+
__version_tuple__ = version_tuple = (25, 2, 27, 'dev4')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Generated by Django 4.2.20 on 2025-09-27 14:19
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('unicom', '0011_toolcall_add_message_reference'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name='message',
|
|
16
|
+
name='response_to_tool_call',
|
|
17
|
+
field=models.ForeignKey(blank=True, help_text='The ToolCall that this message is responding to (for assistant messages from tool responses)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='response_messages', to='unicom.toolcall'),
|
|
18
|
+
),
|
|
19
|
+
migrations.AddField(
|
|
20
|
+
model_name='toolcall',
|
|
21
|
+
name='initial_user_message',
|
|
22
|
+
field=models.ForeignKey(default=1, help_text='The original user message that this tool call is responding to', on_delete=django.db.models.deletion.CASCADE, related_name='triggered_tool_calls', to='unicom.message'),
|
|
23
|
+
preserve_default=False,
|
|
24
|
+
),
|
|
25
|
+
]
|
|
@@ -68,6 +68,11 @@ class Message(models.Model):
|
|
|
68
68
|
media = models.FileField(upload_to='media/', blank=True, null=True)
|
|
69
69
|
reply_to_message = models.ForeignKey(
|
|
70
70
|
'self', on_delete=models.SET_NULL, null=True, blank=True, related_name='replies')
|
|
71
|
+
response_to_tool_call = models.ForeignKey(
|
|
72
|
+
'unicom.ToolCall', on_delete=models.SET_NULL, null=True, blank=True,
|
|
73
|
+
related_name='response_messages',
|
|
74
|
+
help_text="The ToolCall that this message is responding to (for assistant messages from tool responses)"
|
|
75
|
+
)
|
|
71
76
|
timestamp = models.DateTimeField()
|
|
72
77
|
time_sent = models.DateTimeField(null=True, blank=True)
|
|
73
78
|
time_delivered = models.DateTimeField(null=True, blank=True)
|
|
@@ -100,7 +105,33 @@ class Message(models.Model):
|
|
|
100
105
|
"""
|
|
101
106
|
Reply to this message with a dictionary containing the response.
|
|
102
107
|
The dictionary can contain 'text', 'html', 'file_path', etc.
|
|
108
|
+
|
|
109
|
+
For tool response messages, this will send the reply to the original user message
|
|
110
|
+
while maintaining the chain by setting reply_to_message to this tool response.
|
|
103
111
|
"""
|
|
112
|
+
# Handle tool response messages - send to original user but maintain chain
|
|
113
|
+
if self.media_type == 'tool_response':
|
|
114
|
+
# Get the request using reverse lookup
|
|
115
|
+
request = self.request_set.first() # tool response message -> request
|
|
116
|
+
if request:
|
|
117
|
+
initial_request = request.initial_request or request
|
|
118
|
+
target_message = initial_request.message
|
|
119
|
+
|
|
120
|
+
# Get the ToolCall that this response came from
|
|
121
|
+
tool_call = request.tool_calls.first()
|
|
122
|
+
|
|
123
|
+
# Send reply to original user message
|
|
124
|
+
reply_msg = target_message.reply_with(msg_dict)
|
|
125
|
+
|
|
126
|
+
# Set the reply chain and tool call reference
|
|
127
|
+
reply_msg.reply_to_message = self
|
|
128
|
+
if tool_call:
|
|
129
|
+
reply_msg.response_to_tool_call = tool_call
|
|
130
|
+
reply_msg.save(update_fields=['reply_to_message', 'response_to_tool_call'])
|
|
131
|
+
|
|
132
|
+
return reply_msg
|
|
133
|
+
|
|
134
|
+
# Normal case - reply to this message directly
|
|
104
135
|
from unicom.services.crossplatform.reply_to_message import reply_to_message
|
|
105
136
|
return reply_to_message(self.channel, self, msg_dict)
|
|
106
137
|
|
|
@@ -177,6 +208,71 @@ class Message(models.Model):
|
|
|
177
208
|
img_tag['src'] = f'data:{mime};base64,{b64}'
|
|
178
209
|
return str(soup)
|
|
179
210
|
|
|
211
|
+
def _process_chain_with_tool_grouping(self, chain, msg_to_dict):
|
|
212
|
+
"""
|
|
213
|
+
Process message chain with intelligent tool call grouping.
|
|
214
|
+
Groups parallel tool calls (same initial_user_message, unique call_id) into single LLM messages.
|
|
215
|
+
"""
|
|
216
|
+
messages = []
|
|
217
|
+
i = 0
|
|
218
|
+
|
|
219
|
+
while i < len(chain):
|
|
220
|
+
msg = chain[i]
|
|
221
|
+
|
|
222
|
+
# Check if this is an assistant message responding to a tool call
|
|
223
|
+
if (msg.is_outgoing and msg.response_to_tool_call and
|
|
224
|
+
msg.response_to_tool_call.initial_user_message):
|
|
225
|
+
|
|
226
|
+
# This assistant message was created from a tool response
|
|
227
|
+
# Find all parallel tool calls from the same initial user message
|
|
228
|
+
initial_user_msg = msg.response_to_tool_call.initial_user_message
|
|
229
|
+
|
|
230
|
+
# Get all tool calls triggered by the same initial user message
|
|
231
|
+
# Group by unique call_id to handle periodic responses properly
|
|
232
|
+
tool_calls_dict = {}
|
|
233
|
+
for tool_call in initial_user_msg.triggered_tool_calls.all():
|
|
234
|
+
if tool_call.call_id not in tool_calls_dict:
|
|
235
|
+
tool_calls_dict[tool_call.call_id] = tool_call
|
|
236
|
+
|
|
237
|
+
# Create grouped tool call message
|
|
238
|
+
if tool_calls_dict:
|
|
239
|
+
tool_calls_list = []
|
|
240
|
+
for tool_call in tool_calls_dict.values():
|
|
241
|
+
arguments = tool_call.arguments
|
|
242
|
+
if isinstance(arguments, dict):
|
|
243
|
+
import json
|
|
244
|
+
arguments = json.dumps(arguments)
|
|
245
|
+
|
|
246
|
+
tool_calls_list.append({
|
|
247
|
+
"id": tool_call.call_id,
|
|
248
|
+
"type": "function",
|
|
249
|
+
"function": {
|
|
250
|
+
"name": tool_call.tool_name,
|
|
251
|
+
"arguments": arguments
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
# Create single assistant message with all parallel tool calls
|
|
256
|
+
grouped_msg = {
|
|
257
|
+
"role": "assistant",
|
|
258
|
+
"content": None,
|
|
259
|
+
"tool_calls": tool_calls_list
|
|
260
|
+
}
|
|
261
|
+
messages.append(grouped_msg)
|
|
262
|
+
|
|
263
|
+
# Add the current assistant message (response to tool calls)
|
|
264
|
+
messages.append(msg_to_dict(msg))
|
|
265
|
+
|
|
266
|
+
else:
|
|
267
|
+
# Regular message - just convert normally
|
|
268
|
+
# Skip tool_call and tool_response messages as they're handled above
|
|
269
|
+
if msg.media_type not in ['tool_call', 'tool_response']:
|
|
270
|
+
messages.append(msg_to_dict(msg))
|
|
271
|
+
|
|
272
|
+
i += 1
|
|
273
|
+
|
|
274
|
+
return messages
|
|
275
|
+
|
|
180
276
|
def as_llm_chat(self, depth=10, mode="chat", system_instruction=None, multimodal=True):
|
|
181
277
|
"""
|
|
182
278
|
Returns a list of dicts for LLM chat APIs (OpenAI, Gemini, etc), each with 'role' and 'content'.
|
|
@@ -338,8 +434,8 @@ class Message(models.Model):
|
|
|
338
434
|
system_instruction=system_instruction,
|
|
339
435
|
multimodal=multimodal)
|
|
340
436
|
|
|
341
|
-
|
|
342
|
-
|
|
437
|
+
# Process selected messages with tool call grouping
|
|
438
|
+
messages = self._process_chain_with_tool_grouping(selected, msg_to_dict)
|
|
343
439
|
elif mode == "thread":
|
|
344
440
|
chain = []
|
|
345
441
|
cur = self
|
|
@@ -375,8 +471,8 @@ class Message(models.Model):
|
|
|
375
471
|
system_instruction=system_instruction,
|
|
376
472
|
multimodal=multimodal)
|
|
377
473
|
|
|
378
|
-
|
|
379
|
-
|
|
474
|
+
# Process chain with tool call grouping
|
|
475
|
+
messages = self._process_chain_with_tool_grouping(chain, msg_to_dict)
|
|
380
476
|
else:
|
|
381
477
|
raise ValueError(f"Unknown mode: {mode}")
|
|
382
478
|
if system_instruction:
|
|
@@ -498,8 +594,6 @@ class Message(models.Model):
|
|
|
498
594
|
m = re.match(r"data:(.*?);base64,(.*)", url)
|
|
499
595
|
if m:
|
|
500
596
|
mime, b64data = m.groups()
|
|
501
|
-
if not isinstance(mime, str):
|
|
502
|
-
raise ValueError(f"Expected mime to be string, got {type(mime)}: {mime}")
|
|
503
597
|
ext = mime.split("/")[-1]
|
|
504
598
|
image_file_name = f"media/{uuid.uuid4()}.{ext}"
|
|
505
599
|
with open(image_file_name, "wb") as f:
|
|
@@ -108,11 +108,7 @@ class MessageTemplate(models.Model):
|
|
|
108
108
|
for img in soup.find_all('img'):
|
|
109
109
|
src = img.get('src', '')
|
|
110
110
|
if src.startswith('data:image/') and ';base64,' in src:
|
|
111
|
-
if not isinstance(src, str):
|
|
112
|
-
raise ValueError(f"Expected src to be string, got {type(src)}: {src}")
|
|
113
111
|
header, b64data = src.split(';base64,', 1)
|
|
114
|
-
if not isinstance(header, str):
|
|
115
|
-
raise ValueError(f"Expected header to be string, got {type(header)}: {header}")
|
|
116
112
|
mime = header.split(':')[1]
|
|
117
113
|
ext = mimetypes.guess_extension(mime) or '.png'
|
|
118
114
|
data = base64.b64decode(b64data)
|
|
@@ -37,6 +37,14 @@ class ToolCall(models.Model):
|
|
|
37
37
|
help_text="The tool_call message that this ToolCall object represents"
|
|
38
38
|
)
|
|
39
39
|
|
|
40
|
+
# Reference to the initial user message that triggered this tool call
|
|
41
|
+
initial_user_message = models.ForeignKey(
|
|
42
|
+
'unicom.Message',
|
|
43
|
+
on_delete=models.CASCADE,
|
|
44
|
+
related_name='triggered_tool_calls',
|
|
45
|
+
help_text="The original user message that this tool call is responding to"
|
|
46
|
+
)
|
|
47
|
+
|
|
40
48
|
# Timestamps
|
|
41
49
|
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
|
|
42
50
|
started_at = models.DateTimeField(null=True, blank=True)
|
|
@@ -66,8 +66,6 @@ class IMAPThreadManager:
|
|
|
66
66
|
channel.listen_to_IMAP()
|
|
67
67
|
except Exception as e:
|
|
68
68
|
logger.exception(f"Listener for Channel {channel.pk} crashed: {e}")
|
|
69
|
-
import traceback
|
|
70
|
-
logger.error(f"Full traceback: {traceback.format_exc()}")
|
|
71
69
|
time.sleep(10)
|
|
72
70
|
finally:
|
|
73
71
|
# ensure no DB connections are leaked by this thread
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/listen_to_IMAP.py
RENAMED
|
@@ -41,8 +41,6 @@ def listen_to_IMAP(channel):
|
|
|
41
41
|
# logger.info(f"Channel {channel.pk}: Found email {msg.id} (uid={uid})")
|
|
42
42
|
except Exception as e:
|
|
43
43
|
logger.error(f"Channel {channel.pk}: Failed to process UID {uid}: {e}")
|
|
44
|
-
import traceback
|
|
45
|
-
logger.error(f"Full traceback: {traceback.format_exc()}")
|
|
46
44
|
|
|
47
45
|
if mark_seen_on == 'on_save':
|
|
48
46
|
if uids:
|
|
@@ -87,17 +85,13 @@ def listen_to_IMAP(channel):
|
|
|
87
85
|
server.add_flags(uid, [SEEN])
|
|
88
86
|
logger.debug(f"Incoming email - Message-ID: {msg.id}, In-Reply-To: {msg.raw.get('In-Reply-To') if msg.raw else 'None'}")
|
|
89
87
|
logger.debug(f"Associated with chat: {msg.chat_id}")
|
|
90
|
-
except Exception
|
|
91
|
-
logger.error(f"Channel {channel.pk}: Failed to process UID {uid}
|
|
92
|
-
import traceback
|
|
93
|
-
logger.error(f"Full traceback: {traceback.format_exc()}")
|
|
88
|
+
except Exception:
|
|
89
|
+
logger.error(f"Channel {channel.pk}: Failed to process UID {uid}")
|
|
94
90
|
finally:
|
|
95
91
|
connections.close_all()
|
|
96
92
|
|
|
97
93
|
except Exception as e:
|
|
98
94
|
logger.error(f"Channel {channel.pk}: Fatal IMAP error: {e}, reconnecting in 30s…")
|
|
99
|
-
import traceback
|
|
100
|
-
logger.error(f"Full traceback: {traceback.format_exc()}")
|
|
101
95
|
time.sleep(3)
|
|
102
96
|
finally:
|
|
103
97
|
# Ensure we close all connections to avoid leaks
|
|
@@ -223,10 +223,7 @@ def send_email_message(channel: Channel, params: dict, user: User=None):
|
|
|
223
223
|
if parent:
|
|
224
224
|
# First add any existing References from parent
|
|
225
225
|
if parent.raw and 'References' in parent.raw:
|
|
226
|
-
|
|
227
|
-
if not isinstance(references_header, str):
|
|
228
|
-
raise ValueError(f"Expected References header to be string, got {type(references_header)}: {references_header}")
|
|
229
|
-
references.extend((references_header or '').split())
|
|
226
|
+
references.extend(parent.raw['References'].split())
|
|
230
227
|
# Then add the parent's Message-ID
|
|
231
228
|
references.append(params['reply_to_message_id'])
|
|
232
229
|
else:
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/html_inline_images.py
RENAMED
|
@@ -41,11 +41,7 @@ def html_base64_images_to_shortlinks(html: str) -> tuple[str, list[int]]:
|
|
|
41
41
|
for img in soup.find_all('img'):
|
|
42
42
|
src = img.get('src', '')
|
|
43
43
|
if src.startswith('data:image/') and ';base64,' in src:
|
|
44
|
-
if not isinstance(src, str):
|
|
45
|
-
raise ValueError(f"Expected src to be string, got {type(src)}: {src}")
|
|
46
44
|
header, b64data = src.split(';base64,', 1)
|
|
47
|
-
if not isinstance(header, str):
|
|
48
|
-
raise ValueError(f"Expected header to be string, got {type(header)}: {header}")
|
|
49
45
|
mime = header.split(':')[1]
|
|
50
46
|
ext = mimetypes.guess_extension(mime) or '.png'
|
|
51
47
|
data = base64.b64decode(b64data)
|
|
@@ -125,10 +125,7 @@ def send_telegram_message(channel: Channel, params: dict, user: User=None, retry
|
|
|
125
125
|
params[text_field_key] = params[text_field_key][:4095 - len(cropping_footer)] + cropping_footer
|
|
126
126
|
elif "Can't find end of the entity starting at byte offset" in ret.get('description', ''):
|
|
127
127
|
# Extract mentioned byte offset from error message
|
|
128
|
-
|
|
129
|
-
if not isinstance(description, str):
|
|
130
|
-
raise ValueError(f"Expected description to be string, got {type(description)}: {description}")
|
|
131
|
-
byte_offset = int(description.split("byte offset")[1].split()[0])
|
|
128
|
+
byte_offset = int(ret['description'].split("byte offset")[1].split()[0])
|
|
132
129
|
print(f"Byte offset: {byte_offset}")
|
|
133
130
|
mentioned_char = params[text_field_key][byte_offset]
|
|
134
131
|
print(f"Mentioned char that's causing the error: \"{mentioned_char}\"")
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/.cursor/rules/match_codebase_style.mdc
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/django_unicom.egg-info/requires.txt
RENAMED
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/django_unicom.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/tests/test_email_authentication.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/tests/test_email_request_processing.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/draft_message_admin.py
RENAMED
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/email_inline_image_admin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/admin/message_template_admin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/management/commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0002_emailinlineimage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/migrations/0007_message_imap_uid.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/crossplatform/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/crossplatform/scheduler.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/decode_base64_image.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/email_tracking.py
RENAMED
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/email/quote_filter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/get_public_origin.py
RENAMED
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/internal/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/__init__.py
RENAMED
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/download_file.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/telegram/get_file_path.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/whatsapp/__init__.py
RENAMED
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/services/whatsapp/get_template.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/static/unicom/js/channel_config.js
RENAMED
|
File without changes
|
|
File without changes
|
{django_unicom-25.2.27.dev3 → django_unicom-25.2.27.dev4}/unicom/static/unicom/js/tinymce_init.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|