dhisana 0.0.1.dev240__tar.gz → 0.0.1.dev242__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 (121) hide show
  1. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/PKG-INFO +1 -1
  2. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/setup.py +1 -1
  3. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/schemas/common.py +1 -0
  4. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/schemas/sales.py +1 -0
  5. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/generate_content.py +3 -1
  6. dhisana-0.0.1.dev242/src/dhisana/utils/generate_custom_message.py +271 -0
  7. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/google_oauth_tools.py +6 -0
  8. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/google_workspace_tools.py +7 -0
  9. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/mailgun_tools.py +6 -0
  10. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/microsoft365_tools.py +8 -0
  11. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/sendgrid_tools.py +10 -0
  12. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/smtp_email_tools.py +6 -0
  13. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana.egg-info/PKG-INFO +1 -1
  14. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana.egg-info/SOURCES.txt +1 -0
  15. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/README.md +0 -0
  16. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/pyproject.toml +0 -0
  17. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/setup.cfg +0 -0
  18. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/__init__.py +0 -0
  19. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/cli/__init__.py +0 -0
  20. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/cli/cli.py +0 -0
  21. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/cli/datasets.py +0 -0
  22. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/cli/models.py +0 -0
  23. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/cli/predictions.py +0 -0
  24. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/schemas/__init__.py +0 -0
  25. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/ui/__init__.py +0 -0
  26. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/ui/components.py +0 -0
  27. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/__init__.py +0 -0
  28. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/add_mapping.py +0 -0
  29. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/agent_tools.py +0 -0
  30. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/apollo_tools.py +0 -0
  31. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/assistant_tool_tag.py +0 -0
  32. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/built_with_api_tools.py +0 -0
  33. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/cache_output_tools.py +0 -0
  34. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/cache_output_tools_local.py +0 -0
  35. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/check_email_validity_tools.py +0 -0
  36. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/check_for_intent_signal.py +0 -0
  37. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
  38. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/clay_tools.py +0 -0
  39. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/clean_properties.py +0 -0
  40. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/company_utils.py +0 -0
  41. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/compose_salesnav_query.py +0 -0
  42. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/compose_search_query.py +0 -0
  43. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
  44. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/composite_tools.py +0 -0
  45. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/dataframe_tools.py +0 -0
  46. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/domain_parser.py +0 -0
  47. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/email_body_utils.py +0 -0
  48. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/email_parse_helpers.py +0 -0
  49. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/email_provider.py +0 -0
  50. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/enrich_lead_information.py +0 -0
  51. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
  52. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/fetch_openai_config.py +0 -0
  53. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/field_validators.py +0 -0
  54. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/g2_tools.py +0 -0
  55. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/generate_email.py +0 -0
  56. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/generate_email_response.py +0 -0
  57. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/generate_flow.py +0 -0
  58. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
  59. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
  60. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
  61. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
  62. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/google_custom_search.py +0 -0
  63. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/hubspot_clearbit.py +0 -0
  64. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
  65. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/instantly_tools.py +0 -0
  66. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/linkedin_crawler.py +0 -0
  67. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/lusha_tools.py +0 -0
  68. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/mailreach_tools.py +0 -0
  69. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
  70. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/openai_helpers.py +0 -0
  71. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
  72. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
  73. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
  74. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
  75. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
  76. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
  77. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/profile.py +0 -0
  78. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/proxy_curl_tools.py +0 -0
  79. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
  80. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/python_function_to_tools.py +0 -0
  81. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/research_lead.py +0 -0
  82. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
  83. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
  84. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/search_router.py +0 -0
  85. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/search_router_jobs.py +0 -0
  86. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/serarch_router_local_business.py +0 -0
  87. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
  88. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
  89. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/serpapi_google_search.py +0 -0
  90. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
  91. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/serpapi_search_tools.py +0 -0
  92. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
  93. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/serperdev_local_business.py +0 -0
  94. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/serperdev_search.py +0 -0
  95. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/test_connect.py +0 -0
  96. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/trasform_json.py +0 -0
  97. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/web_download_parse_tools.py +0 -0
  98. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/workflow_code_model.py +0 -0
  99. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/utils/zoominfo_tools.py +0 -0
  100. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/workflow/__init__.py +0 -0
  101. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/workflow/agent.py +0 -0
  102. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/workflow/flow.py +0 -0
  103. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/workflow/task.py +0 -0
  104. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana/workflow/test.py +0 -0
  105. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana.egg-info/dependency_links.txt +0 -0
  106. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana.egg-info/entry_points.txt +0 -0
  107. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana.egg-info/requires.txt +0 -0
  108. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/src/dhisana.egg-info/top_level.txt +0 -0
  109. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_agent_tools.py +0 -0
  110. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_apollo_company_search.py +0 -0
  111. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_apollo_lead_search.py +0 -0
  112. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_connectivity.py +0 -0
  113. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_email_body_utils.py +0 -0
  114. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_google_document.py +0 -0
  115. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_hubspot_call_logs.py +0 -0
  116. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_linkedin_serper.py +0 -0
  117. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_mailreach.py +0 -0
  118. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_mcp_connectivity.py +0 -0
  119. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_proxycurl_get_company_search_id.py +0 -0
  120. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_proxycurl_job_count.py +0 -0
  121. {dhisana-0.0.1.dev240 → dhisana-0.0.1.dev242}/tests/test_structured_output_with_mcp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev240
3
+ Version: 0.0.1.dev242
4
4
  Summary: A Python SDK for Dhisana AI Platform
5
5
  Home-page: https://github.com/dhisana-ai/dhisana-python-sdk
6
6
  Author: Admin
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='dhisana',
5
- version='0.0.1-dev240',
5
+ version='0.0.1-dev242',
6
6
  description='A Python SDK for Dhisana AI Platform',
7
7
  author='Admin',
8
8
  author_email='contact@dhisana.ai',
@@ -378,6 +378,7 @@ class SendEmailContext(BaseModel):
378
378
  sender_email: str
379
379
  labels: Optional[List[str]]
380
380
  body_format: BodyFormat = BodyFormat.AUTO
381
+ headers: Optional[Dict[str, str]] = None
381
382
 
382
383
  class QueryEmailContext(BaseModel):
383
384
  start_time: str
@@ -270,6 +270,7 @@ class ChannelType(str, Enum):
270
270
  LINKEDIN_CONNECT_MESSAGE = "linkedin_connect_message"
271
271
  REPLY_EMAIL = "reply_email"
272
272
  LINKEDIN_USER_MESSAGE = "linkedin_user_message"
273
+ CUSTOM_MESSAGE = "custom_message"
273
274
 
274
275
  class SenderInfo(BaseModel):
275
276
  """
@@ -5,6 +5,7 @@ from dhisana.utils.generate_linkedin_connect_message import generate_personalize
5
5
  from dhisana.utils.assistant_tool_tag import assistant_tool
6
6
  from dhisana.utils.generate_email import generate_personalized_email
7
7
  from dhisana.utils.generate_linkedin_response_message import get_linkedin_response_message_variations
8
+ from dhisana.utils.generate_custom_message import generate_custom_message
8
9
 
9
10
 
10
11
  @assistant_tool
@@ -36,4 +37,5 @@ async def generate_content(
36
37
  elif generation_context.target_channel_type == ChannelType.LINKEDIN_USER_MESSAGE.value:
37
38
  return await get_linkedin_response_message_variations(generation_context, number_of_variations, tool_config)
38
39
  else:
39
- raise ValueError("Invalid target channel type.")
40
+ # Default to CUSTOM_MESSAGE for any unrecognized channel type
41
+ return await generate_custom_message(generation_context, number_of_variations, tool_config)
@@ -0,0 +1,271 @@
1
+ # Import necessary modules
2
+ import html as html_lib
3
+ import re
4
+ from typing import Dict, List, Optional
5
+ from pydantic import BaseModel
6
+
7
+ from dhisana.schemas.sales import CampaignContext, ContentGenerationContext, ConversationContext, Lead, MessageGenerationInstructions, MessageItem, SenderInfo
8
+ from dhisana.utils.generate_structured_output_internal import (
9
+ get_structured_output_internal,
10
+ get_structured_output_with_assistant_and_vector_store
11
+ )
12
+ from datetime import datetime
13
+ from pydantic import BaseModel, ConfigDict
14
+
15
+ # -----------------------------------------------------------------------------
16
+ # Custom Message schema
17
+ # -----------------------------------------------------------------------------
18
+ class CustomMessageCopy(BaseModel):
19
+ subject: str
20
+ body: str
21
+ body_html: Optional[str] = None
22
+
23
+ model_config = ConfigDict(extra="forbid")
24
+
25
+
26
+ def _html_to_plain_text(html_content: str) -> str:
27
+ """Simple HTML to text conversion to backfill plain body."""
28
+ if not html_content:
29
+ return ""
30
+ # Remove tags and normalize whitespace
31
+ text = re.sub(r"<[^>]+>", " ", html_content)
32
+ text = html_lib.unescape(text)
33
+ # Collapse repeated whitespace/newlines
34
+ lines = [line.strip() for line in text.splitlines()]
35
+ return "\n".join([line for line in lines if line])
36
+
37
+ # -----------------------------------------------------------------------------
38
+ # Utility to Clean Up Context (if needed)
39
+ # -----------------------------------------------------------------------------
40
+ def cleanup_message_context(message_context: ContentGenerationContext) -> ContentGenerationContext:
41
+ """
42
+ Return a copy of ContentGenerationContext without sensitive or irrelevant fields.
43
+ Modify or remove fields as necessary for your project.
44
+ """
45
+ clone_context = message_context.copy(deep=True)
46
+ # For demonstration, no sensitive fields in new classes by default.
47
+ # Adjust if you want to remove certain fields (like 'sender_bio', etc.).
48
+ if clone_context.external_known_data:
49
+ clone_context.external_known_data.external_openai_vector_store_id = None
50
+ return clone_context
51
+
52
+ # -----------------------------------------------------------------------------
53
+ # Known Framework Variations (fallback if user instructions are not provided)
54
+ # -----------------------------------------------------------------------------
55
+ FRAMEWORK_VARIATIONS = [
56
+ "Write a summary of the input having key highlights.",
57
+ ]
58
+
59
+ # -----------------------------------------------------------------------------
60
+ # Core function to generate a custom message copy
61
+ # -----------------------------------------------------------------------------
62
+ async def generate_custom_message_copy(
63
+ message_context: ContentGenerationContext,
64
+ message_instructions: MessageGenerationInstructions,
65
+ variation_text: str,
66
+ tool_config: Optional[List[Dict]] = None,
67
+ ) -> dict:
68
+ """
69
+ Generate a personalized custom message using the provided context and instructions.
70
+
71
+ Steps:
72
+ 1. Build a prompt referencing 6 main info:
73
+ (a) Lead Info
74
+ (b) Sender Info
75
+ (c) Campaign Info
76
+ (d) Messaging Instructions
77
+ (e) Additional Data (vector store) if any
78
+ (f) Current Conversation Context
79
+ 2. Generate an initial draft with or without vector store usage.
80
+ 3. Optionally refine if a vector store was used and user instructions were not provided.
81
+ 4. Return the final subject & body.
82
+ """
83
+ cleaned_context = cleanup_message_context(message_context)
84
+
85
+ # Check if user provided custom instructions
86
+ user_custom_instructions = (message_instructions.instructions_to_generate_message or "").strip()
87
+ use_custom_instructions = bool(user_custom_instructions)
88
+
89
+ # Decide final instructions: user-provided or fallback variation
90
+ if use_custom_instructions:
91
+ selected_instructions = user_custom_instructions
92
+ else:
93
+ selected_instructions = variation_text
94
+
95
+ # Pull out fields or fallback to empty if None
96
+ lead_data = cleaned_context.lead_info or Lead()
97
+ sender_data = cleaned_context.sender_info or SenderInfo()
98
+ campaign_data = cleaned_context.campaign_context or CampaignContext()
99
+ conversation_data = cleaned_context.current_conversation_context or ConversationContext()
100
+
101
+ html_note = (
102
+ f"\n Provide the HTML body using this guidance/template when possible:\n {message_instructions.html_template}"
103
+ if getattr(message_instructions, "html_template", None)
104
+ else ""
105
+ )
106
+
107
+ important_requirements = """
108
+ IMPORTANT REQUIREMENTS:
109
+ - Output must be JSON with "subject", "body", and "body_html" fields.
110
+ - "body_html" should be clean HTML suitable for the message (no external assets), inline styles welcome.
111
+ - "body" must be the plain-text equivalent of "body_html".
112
+ - Keep it concise and relevant. No placeholders or extra instructions.
113
+ - Do not include PII or internal references, guids or content identifiers in the message.
114
+ - Use conversational names for company/person placeholders when provided.
115
+ - Do Not Make up information. Use the information provided in the context and instructions only.
116
+ - Do Not use em dash in the generated output.
117
+ - Follow the user instructions carefully regarding format, tone, and structure.
118
+ """
119
+
120
+ if not getattr(message_instructions, "allow_html", False):
121
+ important_requirements = """
122
+ IMPORTANT REQUIREMENTS:
123
+ - Output must be JSON with "subject" and "body" fields only.
124
+ - In the subject or body DO NOT include any HTML tags like <a>, <b>, <i>, etc.
125
+ - The body and subject should be in plain text.
126
+ - If there is a link provided use it as is. Don't wrap it in any HTML tags.
127
+ - Keep it concise and relevant. No placeholders or extra instructions.
128
+ - Do not include PII or internal references, guids or content identifiers in the message.
129
+ - Use conversational name for company name if used.
130
+ - Do Not Make up information. Use the information provided in the context and instructions only.
131
+ - Make sure the body text is well-formatted and that newline and carriage-return characters are correctly present and preserved in the message body.
132
+ - Do Not use em dash in the generated output.
133
+ - Follow the user instructions carefully regarding format, tone, and structure.
134
+ """
135
+
136
+ # Construct the consolidated prompt
137
+ initial_prompt = f"""
138
+ Hi AI Assistant,
139
+
140
+ Below is the context in 6 main sections. Use it to craft a concise, professional message:
141
+
142
+ 1) Lead Information:
143
+ {lead_data.dict()}
144
+
145
+ 2) Sender Information:
146
+ Full Name: {sender_data.sender_full_name or ''}
147
+ First Name: {sender_data.sender_first_name or ''}
148
+ Last Name: {sender_data.sender_last_name or ''}
149
+ Bio: {sender_data.sender_bio or ''}
150
+
151
+ 3) Campaign Information:
152
+ Product Name: {campaign_data.product_name or ''}
153
+ Value Proposition: {campaign_data.value_prop or ''}
154
+ Call To Action: {campaign_data.call_to_action or ''}
155
+ Pain Points: {campaign_data.pain_points or []}
156
+ Proof Points: {campaign_data.proof_points or []}
157
+ Triage Guidelines (Email): {campaign_data.email_triage_guidelines or ''}
158
+ Triage Guidelines (LinkedIn): {campaign_data.linkedin_triage_guidelines or ''}
159
+
160
+ 4) Messaging Instructions (template/framework):
161
+ {selected_instructions}{html_note}
162
+
163
+ 5) External Data / Vector Store:
164
+ (I will be provided with file_search tool if present.)
165
+
166
+ 6) Current Conversation Context:
167
+ Email Thread: {conversation_data.current_email_thread or ''}
168
+ LinkedIn Thread: {conversation_data.current_linkedin_thread or ''}
169
+
170
+ {important_requirements}
171
+ """
172
+
173
+ # Check if a vector store is available
174
+ vector_store_id = (message_context.external_known_data.external_openai_vector_store_id
175
+ if message_context.external_known_data else None)
176
+
177
+ initial_response = None
178
+ initial_status = ""
179
+
180
+ # Generate initial draft
181
+ if vector_store_id:
182
+ initial_response, initial_status = await get_structured_output_with_assistant_and_vector_store(
183
+ prompt=initial_prompt,
184
+ response_format=CustomMessageCopy,
185
+ vector_store_id=vector_store_id,
186
+ model="gpt-5.1-chat",
187
+ tool_config=tool_config,
188
+ use_cache=message_context.message_instructions.use_cache if message_context.message_instructions else True
189
+ )
190
+ else:
191
+ # Otherwise, generate the initial draft internally
192
+ initial_response, initial_status = await get_structured_output_internal(
193
+ prompt=initial_prompt,
194
+ response_format=CustomMessageCopy,
195
+ model="gpt-5.1-chat",
196
+ tool_config=tool_config,
197
+ use_cache=message_context.message_instructions.use_cache if message_context.message_instructions else True
198
+ )
199
+
200
+ if initial_status != "SUCCESS":
201
+ raise Exception("Error: Could not generate initial draft for the custom message.")
202
+
203
+ plain_body = initial_response.body
204
+ html_body = getattr(initial_response, "body_html", None)
205
+ if not plain_body and html_body:
206
+ plain_body = _html_to_plain_text(html_body)
207
+
208
+ response_item = MessageItem(
209
+ message_id="", # or some real ID if you have it
210
+ thread_id="",
211
+ sender_name=message_context.sender_info.sender_full_name or "",
212
+ sender_email=message_context.sender_info.sender_email or "",
213
+ receiver_name=message_context.lead_info.full_name or "",
214
+ receiver_email=message_context.lead_info.email or "",
215
+ iso_datetime=datetime.utcnow().isoformat(),
216
+ subject="",
217
+ body=plain_body,
218
+ html_body=html_body if getattr(message_instructions, "allow_html", False) else None,
219
+ )
220
+ return response_item.model_dump()
221
+
222
+ # -----------------------------------------------------------------------------
223
+ # Primary function to generate multiple variations
224
+ # -----------------------------------------------------------------------------
225
+ async def generate_custom_message(
226
+ generation_context: ContentGenerationContext,
227
+ number_of_variations: int = 3,
228
+ tool_config: Optional[List[Dict]] = None
229
+ ) -> List[dict]:
230
+ """
231
+ Generates multiple custom message variations based on the given context and instructions.
232
+
233
+ Parameters:
234
+ - generation_context: The consolidated context for message generation.
235
+ - number_of_variations: How many message variations to produce.
236
+ - tool_config: Optional tool configuration for the LLM calls.
237
+
238
+ Returns:
239
+ A list of dictionaries, each containing:
240
+ {
241
+ "subject": "string",
242
+ "body": "string"
243
+ }
244
+ """
245
+ message_variations = []
246
+ message_instructions = generation_context.message_instructions
247
+ user_instructions_exist = bool(
248
+ (message_instructions.instructions_to_generate_message or "").strip()
249
+ )
250
+
251
+ for i in range(number_of_variations):
252
+ try:
253
+ # If user provided instructions, use them for each variation
254
+ # (skip the internal frameworks).
255
+ if user_instructions_exist:
256
+ variation_text = message_instructions.instructions_to_generate_message or ""
257
+ else:
258
+ # Otherwise, pick from known frameworks (circular indexing)
259
+ variation_text = FRAMEWORK_VARIATIONS[i % len(FRAMEWORK_VARIATIONS)]
260
+
261
+ message_copy = await generate_custom_message_copy(
262
+ message_context=generation_context,
263
+ message_instructions=message_instructions,
264
+ variation_text=variation_text,
265
+ tool_config=tool_config
266
+ )
267
+ message_variations.append(message_copy)
268
+
269
+ except Exception as e:
270
+ raise e
271
+ return message_variations
@@ -145,6 +145,12 @@ async def send_email_using_google_oauth_async(
145
145
  message["from"] = f"{send_email_context.sender_name} <{send_email_context.sender_email}>"
146
146
  message["subject"] = send_email_context.subject
147
147
 
148
+ extra_headers = getattr(send_email_context, "headers", None) or {}
149
+ for header, value in extra_headers.items():
150
+ if not header or value is None:
151
+ continue
152
+ message[header] = str(value)
153
+
148
154
  raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
149
155
 
150
156
  payload: Dict[str, Any] = {"raw": raw_message}
@@ -179,6 +179,12 @@ async def send_email_using_service_account_async(
179
179
  message['from'] = f"{send_email_context.sender_name} <{send_email_context.sender_email}>"
180
180
  message['subject'] = send_email_context.subject
181
181
 
182
+ extra_headers = getattr(send_email_context, "headers", None) or {}
183
+ for header, value in extra_headers.items():
184
+ if not header or value is None:
185
+ continue
186
+ message[header] = str(value)
187
+
182
188
  # Base64-encode the message
183
189
  raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
184
190
 
@@ -531,6 +537,7 @@ class SendEmailContext(BaseModel):
531
537
  sender_email: str
532
538
  labels: Optional[List[str]]
533
539
  body_format: BodyFormat = BodyFormat.AUTO
540
+ headers: Optional[Dict[str, str]] = None
534
541
 
535
542
  @assistant_tool
536
543
  async def send_email_using_service_account_async(
@@ -126,6 +126,12 @@ async def send_email_using_mailgun_async(
126
126
  "html": html_body,
127
127
  }
128
128
 
129
+ extra_headers = getattr(send_email_context, "headers", None) or {}
130
+ for header, value in extra_headers.items():
131
+ if not header or value is None:
132
+ continue
133
+ data[f"h:{header}"] = str(value)
134
+
129
135
  async with aiohttp.ClientSession() as session:
130
136
  async with session.post(
131
137
  f"https://api.mailgun.net/v3/{domain}/messages",
@@ -168,6 +168,14 @@ async def send_email_using_microsoft_graph_async(
168
168
  ],
169
169
  }
170
170
 
171
+ extra_headers = getattr(send_email_context, "headers", None) or {}
172
+ if extra_headers:
173
+ message_payload["internetMessageHeaders"] = [
174
+ {"name": header, "value": str(value)}
175
+ for header, value in extra_headers.items()
176
+ if header and value is not None
177
+ ]
178
+
171
179
  headers = {
172
180
  "Authorization": f"Bearer {token}",
173
181
  "Content-Type": "application/json",
@@ -59,6 +59,7 @@ async def send_email_with_sendgrid(
59
59
  message: str,
60
60
  tool_config: Optional[List[Dict]] = None,
61
61
  body_format: Optional[str] = None,
62
+ custom_headers: Optional[Dict[str, str]] = None,
62
63
  ):
63
64
  """
64
65
  Send an email using SendGrid's v3 Mail Send API.
@@ -69,6 +70,7 @@ async def send_email_with_sendgrid(
69
70
  - subject: Subject string.
70
71
  - message: HTML body content.
71
72
  - tool_config: Optional integration configuration list.
73
+ - custom_headers: Optional mapping of header names to values.
72
74
  """
73
75
  api_key = get_sendgrid_api_key(tool_config)
74
76
 
@@ -98,6 +100,13 @@ async def send_email_with_sendgrid(
98
100
  "content": content,
99
101
  }
100
102
 
103
+ if custom_headers:
104
+ payload["headers"] = {
105
+ header: str(value)
106
+ for header, value in custom_headers.items()
107
+ if header and value is not None
108
+ }
109
+
101
110
  headers = {
102
111
  "Authorization": f"Bearer {api_key}",
103
112
  "Content-Type": "application/json",
@@ -143,6 +152,7 @@ async def send_email_using_sendgrid_async(
143
152
  message=ctx.body or "",
144
153
  body_format=getattr(ctx, "body_format", None),
145
154
  tool_config=tool_config,
155
+ custom_headers=getattr(ctx, "headers", None),
146
156
  )
147
157
  # Normalise output to a string id-like value
148
158
  if isinstance(result, dict) and result.get("status") == 202:
@@ -174,6 +174,12 @@ async def send_email_via_smtp_async(
174
174
  generated_id = f"<{uuid.uuid4()}@{domain_part}>"
175
175
  msg["Message-ID"] = generated_id
176
176
 
177
+ extra_headers = getattr(ctx, "headers", None) or {}
178
+ for header, value in extra_headers.items():
179
+ if not header or value is None:
180
+ continue
181
+ msg[header] = str(value)
182
+
177
183
  smtp_kwargs = dict(
178
184
  hostname=smtp_server,
179
185
  port=smtp_port,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev240
3
+ Version: 0.0.1.dev242
4
4
  Summary: A Python SDK for Dhisana AI Platform
5
5
  Home-page: https://github.com/dhisana-ai/dhisana-python-sdk
6
6
  Author: Admin
@@ -47,6 +47,7 @@ src/dhisana/utils/fetch_openai_config.py
47
47
  src/dhisana/utils/field_validators.py
48
48
  src/dhisana/utils/g2_tools.py
49
49
  src/dhisana/utils/generate_content.py
50
+ src/dhisana/utils/generate_custom_message.py
50
51
  src/dhisana/utils/generate_email.py
51
52
  src/dhisana/utils/generate_email_response.py
52
53
  src/dhisana/utils/generate_flow.py
File without changes
File without changes