dhisana 0.0.1.dev218__tar.gz → 0.0.1.dev220__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.
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/PKG-INFO +1 -1
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/setup.py +1 -1
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/schemas/sales.py +67 -27
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/apollo_tools.py +90 -39
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/test_connect.py +109 -1
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana.egg-info/PKG-INFO +1 -1
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/tests/test_apollo_company_search.py +2 -2
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/tests/test_connectivity.py +23 -4
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/README.md +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/pyproject.toml +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/setup.cfg +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/__init__.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/cli/__init__.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/cli/cli.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/cli/datasets.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/cli/models.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/cli/predictions.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/schemas/__init__.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/schemas/common.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/ui/__init__.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/ui/components.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/__init__.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/add_mapping.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/agent_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/assistant_tool_tag.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/built_with_api_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/cache_output_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/cache_output_tools_local.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/check_email_validity_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/check_for_intent_signal.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/clay_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/clean_properties.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/company_utils.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/compose_salesnav_query.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/compose_search_query.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/composite_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/dataframe_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/domain_parser.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/email_parse_helpers.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/email_provider.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/enrich_lead_information.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/fetch_openai_config.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/field_validators.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/g2_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/generate_content.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/generate_email.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/generate_email_response.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/generate_flow.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/generate_linkedin_connect_message.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/google_custom_search.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/google_oauth_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/google_workspace_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/hubspot_clearbit.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/instantly_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/linkedin_crawler.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/lusha_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/mailgun_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/microsoft365_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/openai_helpers.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/profile.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/proxy_curl_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/python_function_to_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/research_lead.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/search_router.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/search_router_jobs.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/sendgrid_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serarch_router_local_business.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serpapi_google_search.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serpapi_search_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serperdev_local_business.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serperdev_search.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/smtp_email_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/trasform_json.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/web_download_parse_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/workflow_code_model.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/zoominfo_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/workflow/__init__.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/workflow/agent.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/workflow/flow.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/workflow/task.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/workflow/test.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana.egg-info/SOURCES.txt +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana.egg-info/dependency_links.txt +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana.egg-info/entry_points.txt +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana.egg-info/requires.txt +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana.egg-info/top_level.txt +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/tests/test_agent_tools.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/tests/test_google_document.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/tests/test_hubspot_call_logs.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/tests/test_linkedin_serper.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/tests/test_mcp_connectivity.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/tests/test_proxycurl_get_company_search_id.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/tests/test_proxycurl_job_count.py +0 -0
- {dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/tests/test_structured_output_with_mcp.py +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
1
3
|
from uuid import UUID
|
|
2
4
|
from pydantic import BaseModel, Field, field_validator
|
|
3
5
|
from typing import List, Optional, Dict, Any
|
|
@@ -10,14 +12,20 @@ from typing import Optional, List, Dict, Literal
|
|
|
10
12
|
# -----------------------------
|
|
11
13
|
|
|
12
14
|
class Lead(BaseModel):
|
|
13
|
-
id: Optional[
|
|
15
|
+
id: Optional[UUID] = None
|
|
14
16
|
full_name: Optional[str] = None
|
|
15
17
|
first_name: Optional[str] = None
|
|
16
18
|
last_name: Optional[str] = None
|
|
17
19
|
email: Optional[str] = None
|
|
18
20
|
user_linkedin_url: Optional[str] = None
|
|
19
21
|
user_linkedin_salesnav_url: Optional[str] = None
|
|
22
|
+
organization_linkedin_url: Optional[str] = None
|
|
23
|
+
organization_linkedin_salesnav_url: Optional[str] = None
|
|
24
|
+
linkedin_follower_count: Optional[int] = None
|
|
20
25
|
primary_domain_of_organization: Optional[str] = None
|
|
26
|
+
twitter_handle: Optional[str] = None
|
|
27
|
+
twitch_handle: Optional[str] = None
|
|
28
|
+
github_handle: Optional[str] = None
|
|
21
29
|
job_title: Optional[str] = None
|
|
22
30
|
phone: Optional[str] = None
|
|
23
31
|
headline: Optional[str] = None
|
|
@@ -25,36 +33,68 @@ class Lead(BaseModel):
|
|
|
25
33
|
organization_name: Optional[str] = None
|
|
26
34
|
organization_website: Optional[str] = None
|
|
27
35
|
summary_about_lead: Optional[str] = None
|
|
36
|
+
|
|
37
|
+
qualification_score: Optional[float] = None
|
|
38
|
+
qualification_reason: Optional[str] = None
|
|
39
|
+
revenue: Optional[str] = None
|
|
40
|
+
company_size: Optional[str] = None
|
|
41
|
+
industry: Optional[str] = None
|
|
42
|
+
|
|
43
|
+
keywords: Optional[Any] = None
|
|
44
|
+
tags: List[str] = []
|
|
45
|
+
notes: List[str] = []
|
|
46
|
+
additional_properties: Optional[Dict[str, Any]] = {}
|
|
28
47
|
workflow_stage: Optional[str] = None
|
|
29
|
-
|
|
30
|
-
engaged:
|
|
48
|
+
|
|
49
|
+
engaged: bool = False
|
|
31
50
|
last_contact: Optional[int] = None
|
|
32
|
-
additional_properties: Optional[Dict[str, Any]] = None
|
|
33
51
|
research_summary: Optional[str] = None
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
] = None
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
email_validation_status: Optional[str] = None
|
|
53
|
+
linkedin_validation_status: Optional[str] = None
|
|
54
|
+
research_status: Optional[str] = None
|
|
55
|
+
enchrichment_status: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@field_validator("linkedin_follower_count", mode="before")
|
|
59
|
+
@classmethod
|
|
60
|
+
def parse_linkedin_follower_count(cls, v):
|
|
61
|
+
if v is None or v == "":
|
|
62
|
+
return None
|
|
63
|
+
if isinstance(v, str):
|
|
64
|
+
v = v.strip()
|
|
65
|
+
if v == "":
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
return int(v)
|
|
69
|
+
except ValueError:
|
|
70
|
+
raise ValueError("linkedin_follower_count must be an integer")
|
|
71
|
+
return v
|
|
72
|
+
|
|
73
|
+
@field_validator("notes", mode="before")
|
|
55
74
|
@classmethod
|
|
56
|
-
def
|
|
57
|
-
|
|
75
|
+
def ensure_notes_list(cls, v):
|
|
76
|
+
"""Coerce notes to a list of strings.
|
|
77
|
+
Handles legacy cases where the DB may contain a scalar or JSON string.
|
|
78
|
+
"""
|
|
79
|
+
if v is None:
|
|
80
|
+
return []
|
|
81
|
+
if isinstance(v, list):
|
|
82
|
+
# Ensure all elements are strings
|
|
83
|
+
return [str(item) if not isinstance(item, str) else item for item in v]
|
|
84
|
+
if isinstance(v, str):
|
|
85
|
+
# Try to parse JSON array; if not, wrap as single-note list
|
|
86
|
+
try:
|
|
87
|
+
parsed = json.loads(v)
|
|
88
|
+
if isinstance(parsed, list):
|
|
89
|
+
return [str(item) if not isinstance(item, str) else item for item in parsed]
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
return [v]
|
|
93
|
+
# Fallback: wrap any other scalar/object as a single string entry
|
|
94
|
+
try:
|
|
95
|
+
return [json.dumps(v)]
|
|
96
|
+
except Exception:
|
|
97
|
+
return [str(v)]
|
|
58
98
|
|
|
59
99
|
|
|
60
100
|
class LeadList(BaseModel):
|
|
@@ -11,7 +11,7 @@ from dhisana.schemas.sales import LeadsQueryFilters, CompanyQueryFilters
|
|
|
11
11
|
from dhisana.utils.cache_output_tools import cache_output, retrieve_output
|
|
12
12
|
from dhisana.utils.assistant_tool_tag import assistant_tool
|
|
13
13
|
from urllib.parse import urlparse, parse_qs
|
|
14
|
-
from typing import Any, Dict, List, Optional, Union
|
|
14
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
15
15
|
|
|
16
16
|
from dhisana.utils.clean_properties import cleanup_properties
|
|
17
17
|
|
|
@@ -19,50 +19,81 @@ logging.basicConfig(level=logging.INFO)
|
|
|
19
19
|
logger = logging.getLogger(__name__)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def get_apollo_access_token(tool_config: Optional[List[Dict]] = None) -> str:
|
|
22
|
+
def get_apollo_access_token(tool_config: Optional[List[Dict]] = None) -> Tuple[str, bool]:
|
|
23
23
|
"""
|
|
24
|
-
Retrieves
|
|
24
|
+
Retrieves an Apollo access token from tool configuration or environment variables.
|
|
25
25
|
|
|
26
26
|
Args:
|
|
27
|
-
tool_config (list):
|
|
28
|
-
Each dictionary should have a "name" key and a "configuration" key,
|
|
29
|
-
where "configuration" is a list of dictionaries containing "name" and "value" keys.
|
|
27
|
+
tool_config (list): Optional tool configuration payload provided to the tool.
|
|
30
28
|
|
|
31
29
|
Returns:
|
|
32
|
-
str:
|
|
30
|
+
Tuple[str, bool]: A tuple containing the token string and a boolean flag indicating
|
|
31
|
+
whether the token represents an OAuth bearer token (``True``) or an API key (``False``).
|
|
33
32
|
|
|
34
33
|
Raises:
|
|
35
34
|
ValueError: If the Apollo integration has not been configured.
|
|
36
35
|
"""
|
|
37
|
-
|
|
36
|
+
token: Optional[str] = None
|
|
37
|
+
is_oauth = False
|
|
38
38
|
|
|
39
39
|
if tool_config:
|
|
40
|
-
logger.debug(f"Tool config provided: {tool_config}")
|
|
41
40
|
apollo_config = next(
|
|
42
41
|
(item for item in tool_config if item.get("name") == "apollo"), None
|
|
43
42
|
)
|
|
44
43
|
if apollo_config:
|
|
45
44
|
config_map = {
|
|
46
|
-
item["name"]: item
|
|
45
|
+
item["name"]: item.get("value")
|
|
47
46
|
for item in apollo_config.get("configuration", [])
|
|
48
47
|
if item
|
|
49
48
|
}
|
|
50
|
-
|
|
49
|
+
|
|
50
|
+
raw_oauth = config_map.get("oauth_tokens")
|
|
51
|
+
if isinstance(raw_oauth, str):
|
|
52
|
+
try:
|
|
53
|
+
raw_oauth = json.loads(raw_oauth)
|
|
54
|
+
except Exception:
|
|
55
|
+
raw_oauth = None
|
|
56
|
+
if isinstance(raw_oauth, dict):
|
|
57
|
+
token = (
|
|
58
|
+
raw_oauth.get("access_token")
|
|
59
|
+
or raw_oauth.get("token")
|
|
60
|
+
)
|
|
61
|
+
if token:
|
|
62
|
+
is_oauth = True
|
|
63
|
+
|
|
64
|
+
if not token:
|
|
65
|
+
direct_access_token = config_map.get("access_token")
|
|
66
|
+
if direct_access_token:
|
|
67
|
+
token = direct_access_token
|
|
68
|
+
is_oauth = True
|
|
69
|
+
|
|
70
|
+
if not token:
|
|
71
|
+
api_key = config_map.get("apiKey") or config_map.get("api_key")
|
|
72
|
+
if api_key:
|
|
73
|
+
token = api_key
|
|
74
|
+
is_oauth = False
|
|
51
75
|
else:
|
|
52
76
|
logger.warning("No 'apollo' config item found in tool_config.")
|
|
53
|
-
else:
|
|
54
|
-
logger.debug("No tool_config provided or it's None.")
|
|
55
77
|
|
|
56
|
-
|
|
57
|
-
|
|
78
|
+
if not token:
|
|
79
|
+
env_oauth_token = os.getenv("APOLLO_ACCESS_TOKEN")
|
|
80
|
+
if env_oauth_token:
|
|
81
|
+
token = env_oauth_token
|
|
82
|
+
is_oauth = True
|
|
83
|
+
|
|
84
|
+
if not token:
|
|
85
|
+
env_api_key = os.getenv("APOLLO_API_KEY")
|
|
86
|
+
if env_api_key:
|
|
87
|
+
token = env_api_key
|
|
88
|
+
is_oauth = False
|
|
58
89
|
|
|
59
|
-
if not
|
|
90
|
+
if not token:
|
|
60
91
|
logger.error("Apollo integration is not configured.")
|
|
61
92
|
raise ValueError(
|
|
62
93
|
"Apollo integration is not configured. Please configure the connection to Apollo in Integrations."
|
|
63
94
|
)
|
|
64
95
|
|
|
65
|
-
return
|
|
96
|
+
return token, is_oauth
|
|
66
97
|
|
|
67
98
|
|
|
68
99
|
@assistant_tool
|
|
@@ -94,16 +125,17 @@ async def enrich_person_info_from_apollo(
|
|
|
94
125
|
"""
|
|
95
126
|
logger.info("Entering enrich_person_info_from_apollo")
|
|
96
127
|
|
|
97
|
-
|
|
128
|
+
token, is_oauth = get_apollo_access_token(tool_config)
|
|
98
129
|
|
|
99
130
|
if not linkedin_url and not email and not phone:
|
|
100
131
|
logger.warning("No linkedin_url, email, or phone provided. At least one is required.")
|
|
101
132
|
return {'error': "At least one of linkedin_url, email, or phone must be provided"}
|
|
102
133
|
|
|
103
|
-
headers = {
|
|
104
|
-
|
|
105
|
-
"
|
|
106
|
-
|
|
134
|
+
headers = {"Content-Type": "application/json"}
|
|
135
|
+
if is_oauth:
|
|
136
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
137
|
+
else:
|
|
138
|
+
headers["X-Api-Key"] = token
|
|
107
139
|
|
|
108
140
|
data = {}
|
|
109
141
|
if linkedin_url:
|
|
@@ -186,11 +218,12 @@ async def lookup_person_in_apollo_by_name(
|
|
|
186
218
|
logger.warning("No full_name provided.")
|
|
187
219
|
return {'error': "Full name is required"}
|
|
188
220
|
|
|
189
|
-
|
|
190
|
-
headers = {
|
|
191
|
-
|
|
192
|
-
"
|
|
193
|
-
|
|
221
|
+
token, is_oauth = get_apollo_access_token(tool_config)
|
|
222
|
+
headers = {"Content-Type": "application/json"}
|
|
223
|
+
if is_oauth:
|
|
224
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
225
|
+
else:
|
|
226
|
+
headers["X-Api-Key"] = token
|
|
194
227
|
|
|
195
228
|
# Construct the query payload
|
|
196
229
|
data = {
|
|
@@ -263,18 +296,21 @@ async def enrich_organization_info_from_apollo(
|
|
|
263
296
|
"""
|
|
264
297
|
logger.info("Entering enrich_organization_info_from_apollo")
|
|
265
298
|
|
|
266
|
-
|
|
299
|
+
token, is_oauth = get_apollo_access_token(tool_config)
|
|
267
300
|
|
|
268
301
|
if not organization_domain:
|
|
269
302
|
logger.warning("No organization domain provided.")
|
|
270
303
|
return {'error': "organization domain must be provided"}
|
|
271
304
|
|
|
272
305
|
headers = {
|
|
273
|
-
"X-Api-Key": f"{APOLLO_API_KEY}",
|
|
274
306
|
"Content-Type": "application/json",
|
|
275
307
|
"Cache-Control": "no-cache",
|
|
276
308
|
"accept": "application/json"
|
|
277
309
|
}
|
|
310
|
+
if is_oauth:
|
|
311
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
312
|
+
else:
|
|
313
|
+
headers["X-Api-Key"] = token
|
|
278
314
|
|
|
279
315
|
cached_response = retrieve_output("enrich_organization_info_from_apollo", organization_domain)
|
|
280
316
|
if cached_response is not None:
|
|
@@ -364,12 +400,15 @@ async def search_people_with_apollo(
|
|
|
364
400
|
logger.warning("No payload given; returning empty result.")
|
|
365
401
|
return []
|
|
366
402
|
|
|
367
|
-
|
|
403
|
+
token, is_oauth = get_apollo_access_token(tool_config)
|
|
368
404
|
headers = {
|
|
369
405
|
"Cache-Control": "no-cache",
|
|
370
406
|
"Content-Type": "application/json",
|
|
371
|
-
"X-Api-Key": api_key,
|
|
372
407
|
}
|
|
408
|
+
if is_oauth:
|
|
409
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
410
|
+
else:
|
|
411
|
+
headers["X-Api-Key"] = token
|
|
373
412
|
|
|
374
413
|
url = "https://api.apollo.io/api/v1/mixed_people/search"
|
|
375
414
|
logger.info(f"Sending payload to Apollo (single page): {json.dumps(dynamic_payload, indent=2)}")
|
|
@@ -840,12 +879,15 @@ async def search_leads_with_apollo_page(
|
|
|
840
879
|
f" Payload: {json.dumps(page_payload, indent=2)}")
|
|
841
880
|
|
|
842
881
|
# Get the full Apollo API response with pagination metadata
|
|
843
|
-
|
|
882
|
+
token, is_oauth = get_apollo_access_token(tool_config)
|
|
844
883
|
headers = {
|
|
845
884
|
"Cache-Control": "no-cache",
|
|
846
885
|
"Content-Type": "application/json",
|
|
847
|
-
"X-Api-Key": api_key,
|
|
848
886
|
}
|
|
887
|
+
if is_oauth:
|
|
888
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
889
|
+
else:
|
|
890
|
+
headers["X-Api-Key"] = token
|
|
849
891
|
|
|
850
892
|
url = "https://api.apollo.io/api/v1/mixed_people/search"
|
|
851
893
|
|
|
@@ -950,17 +992,20 @@ async def get_organization_details_from_apollo(
|
|
|
950
992
|
"""
|
|
951
993
|
logger.info("Entering get_organization_details_from_apollo")
|
|
952
994
|
|
|
953
|
-
|
|
995
|
+
token, is_oauth = get_apollo_access_token(tool_config)
|
|
954
996
|
if not organization_id:
|
|
955
997
|
logger.warning("No organization_id provided.")
|
|
956
998
|
return {'error': "Organization ID must be provided"}
|
|
957
999
|
|
|
958
1000
|
headers = {
|
|
959
|
-
"X-Api-Key": APOLLO_API_KEY,
|
|
960
1001
|
"Content-Type": "application/json",
|
|
961
1002
|
"Cache-Control": "no-cache",
|
|
962
1003
|
"Accept": "application/json"
|
|
963
1004
|
}
|
|
1005
|
+
if is_oauth:
|
|
1006
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
1007
|
+
else:
|
|
1008
|
+
headers["X-Api-Key"] = token
|
|
964
1009
|
|
|
965
1010
|
cached_response = retrieve_output("get_organization_details_from_apollo", organization_id)
|
|
966
1011
|
if cached_response is not None:
|
|
@@ -1202,12 +1247,15 @@ async def search_companies_with_apollo(
|
|
|
1202
1247
|
logger.warning("No payload given; returning empty result.")
|
|
1203
1248
|
return []
|
|
1204
1249
|
|
|
1205
|
-
|
|
1250
|
+
token, is_oauth = get_apollo_access_token(tool_config)
|
|
1206
1251
|
headers = {
|
|
1207
1252
|
"Cache-Control": "no-cache",
|
|
1208
1253
|
"Content-Type": "application/json",
|
|
1209
|
-
"X-Api-Key": api_key,
|
|
1210
1254
|
}
|
|
1255
|
+
if is_oauth:
|
|
1256
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
1257
|
+
else:
|
|
1258
|
+
headers["X-Api-Key"] = token
|
|
1211
1259
|
|
|
1212
1260
|
url = "https://api.apollo.io/api/v1/organizations/search"
|
|
1213
1261
|
logger.info(f"Sending payload to Apollo organizations endpoint (single page): {json.dumps(dynamic_payload, indent=2)}")
|
|
@@ -1508,12 +1556,15 @@ async def search_companies_with_apollo_page(
|
|
|
1508
1556
|
f" Payload: {json.dumps(cleaned_payload, indent=2)}")
|
|
1509
1557
|
|
|
1510
1558
|
# Get the full Apollo API response with pagination metadata
|
|
1511
|
-
|
|
1559
|
+
token, is_oauth = get_apollo_access_token(tool_config)
|
|
1512
1560
|
headers = {
|
|
1513
1561
|
"Cache-Control": "no-cache",
|
|
1514
1562
|
"Content-Type": "application/json",
|
|
1515
|
-
"X-Api-Key": api_key,
|
|
1516
1563
|
}
|
|
1564
|
+
if is_oauth:
|
|
1565
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
1566
|
+
else:
|
|
1567
|
+
headers["X-Api-Key"] = token
|
|
1517
1568
|
|
|
1518
1569
|
url = "https://api.apollo.io/api/v1/organizations/search"
|
|
1519
1570
|
|
|
@@ -2,6 +2,7 @@ import json
|
|
|
2
2
|
import logging
|
|
3
3
|
import asyncio
|
|
4
4
|
import os
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
5
6
|
from typing import Awaitable, Callable, Dict, List, Any, Optional
|
|
6
7
|
import requests
|
|
7
8
|
|
|
@@ -420,6 +421,112 @@ async def test_sendgrid(api_key: str) -> Dict[str, Any]:
|
|
|
420
421
|
return {"success": False, "status_code": 0, "error_message": str(e)}
|
|
421
422
|
|
|
422
423
|
|
|
424
|
+
async def test_samgov(api_key: str) -> Dict[str, Any]:
|
|
425
|
+
"""Test SAM.gov connectivity by fetching a single opportunity."""
|
|
426
|
+
|
|
427
|
+
url = "https://api.sam.gov/opportunities/v2/search"
|
|
428
|
+
now = datetime.now(timezone.utc)
|
|
429
|
+
posted_to = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
430
|
+
posted_from = (now - timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
431
|
+
|
|
432
|
+
params = {
|
|
433
|
+
"limit": 1,
|
|
434
|
+
"offset": 0,
|
|
435
|
+
"keyword": "software",
|
|
436
|
+
"status": "active",
|
|
437
|
+
"includeCount": "true",
|
|
438
|
+
"postedFrom": posted_from,
|
|
439
|
+
"postedTo": posted_to,
|
|
440
|
+
"api_key": api_key,
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
try:
|
|
444
|
+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
|
|
445
|
+
|
|
446
|
+
async def perform(request_params: Dict[str, Any]):
|
|
447
|
+
async with session.get(url, params=request_params) as response:
|
|
448
|
+
status = response.status
|
|
449
|
+
body_text = await response.text()
|
|
450
|
+
data: Optional[Dict[str, Any]] = None
|
|
451
|
+
|
|
452
|
+
try:
|
|
453
|
+
parsed = json.loads(body_text)
|
|
454
|
+
if isinstance(parsed, dict):
|
|
455
|
+
data = parsed
|
|
456
|
+
except json.JSONDecodeError:
|
|
457
|
+
data = None
|
|
458
|
+
|
|
459
|
+
return status, data, body_text
|
|
460
|
+
|
|
461
|
+
status, data, body_text = await perform(params)
|
|
462
|
+
|
|
463
|
+
def extract_error_message(payload: Optional[Dict[str, Any]], fallback_text: str) -> Optional[str]:
|
|
464
|
+
if not payload:
|
|
465
|
+
return fallback_text[:200] if fallback_text else None
|
|
466
|
+
|
|
467
|
+
errors = payload.get("errors") or payload.get("error")
|
|
468
|
+
if isinstance(errors, list):
|
|
469
|
+
parts = [
|
|
470
|
+
err.get("message") if isinstance(err, dict) else str(err)
|
|
471
|
+
for err in errors
|
|
472
|
+
if err
|
|
473
|
+
]
|
|
474
|
+
return "; ".join(parts) if parts else fallback_text[:200]
|
|
475
|
+
if isinstance(errors, dict):
|
|
476
|
+
return errors.get("message") or str(errors)
|
|
477
|
+
if errors:
|
|
478
|
+
return str(errors)
|
|
479
|
+
|
|
480
|
+
for key in ("message", "errorMessage", "detail", "description"):
|
|
481
|
+
if key in payload and payload[key]:
|
|
482
|
+
return str(payload[key])
|
|
483
|
+
|
|
484
|
+
return fallback_text[:200] if fallback_text else None
|
|
485
|
+
|
|
486
|
+
error_message = extract_error_message(data, body_text)
|
|
487
|
+
|
|
488
|
+
if status == 400 and error_message and "Invalid Date Entered" in error_message:
|
|
489
|
+
fallback_params = dict(params)
|
|
490
|
+
fallback_params["postedFrom"] = (now - timedelta(days=7)).strftime("%m/%d/%Y")
|
|
491
|
+
fallback_params["postedTo"] = now.strftime("%m/%d/%Y")
|
|
492
|
+
status, data, body_text = await perform(fallback_params)
|
|
493
|
+
error_message = extract_error_message(data, body_text)
|
|
494
|
+
|
|
495
|
+
if status != 200:
|
|
496
|
+
return {
|
|
497
|
+
"success": False,
|
|
498
|
+
"status_code": status,
|
|
499
|
+
"error_message": error_message or f"SAM.gov non-200: {status}",
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if not data:
|
|
503
|
+
return {
|
|
504
|
+
"success": False,
|
|
505
|
+
"status_code": status,
|
|
506
|
+
"error_message": "SAM.gov returned invalid JSON response.",
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if data.get("errors"):
|
|
510
|
+
return {
|
|
511
|
+
"success": False,
|
|
512
|
+
"status_code": status,
|
|
513
|
+
"error_message": extract_error_message(data, body_text) or "SAM.gov reported errors.",
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if data.get("opportunitiesData") or data.get("totalRecords") is not None:
|
|
517
|
+
return {"success": True, "status_code": status, "error_message": None}
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
"success": False,
|
|
521
|
+
"status_code": status,
|
|
522
|
+
"error_message": "Unexpected SAM.gov response payload.",
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
except Exception as e:
|
|
526
|
+
logger.error(f"SAM.gov test failed: {e}")
|
|
527
|
+
return {"success": False, "status_code": 0, "error_message": str(e)}
|
|
528
|
+
|
|
529
|
+
|
|
423
530
|
async def test_salesforce(
|
|
424
531
|
username: str,
|
|
425
532
|
password: str,
|
|
@@ -1086,8 +1193,9 @@ async def test_connectivity(tool_config: List[Dict[str, Any]]) -> Dict[str, Dict
|
|
|
1086
1193
|
"clay": test_clay,
|
|
1087
1194
|
"mcpServer": test_mcp_server,
|
|
1088
1195
|
"slack": test_slack,
|
|
1089
|
-
"mailgun":
|
|
1196
|
+
"mailgun": test_mailgun,
|
|
1090
1197
|
"sendgrid": test_sendgrid,
|
|
1198
|
+
"samgov": test_samgov,
|
|
1091
1199
|
}
|
|
1092
1200
|
|
|
1093
1201
|
results: Dict[str, Dict[str, Any]] = {}
|
|
@@ -116,7 +116,7 @@ class TestApolloCompanySearch(unittest.TestCase):
|
|
|
116
116
|
@patch('src.dhisana.utils.apollo_tools.get_apollo_access_token')
|
|
117
117
|
async def test_search_companies_with_apollo_page(self, mock_get_token, mock_fetch_data):
|
|
118
118
|
"""Test the paginated company search function."""
|
|
119
|
-
mock_get_token.return_value = "test_api_key"
|
|
119
|
+
mock_get_token.return_value = ("test_api_key", False)
|
|
120
120
|
mock_fetch_data.return_value = self.mock_apollo_response
|
|
121
121
|
|
|
122
122
|
query = CompanyQueryFilters(
|
|
@@ -189,7 +189,7 @@ class TestApolloCompanySearch(unittest.TestCase):
|
|
|
189
189
|
@patch('src.dhisana.utils.apollo_tools.get_apollo_access_token')
|
|
190
190
|
async def test_search_companies_with_apollo_basic(self, mock_get_token, mock_fetch_data):
|
|
191
191
|
"""Test the basic company search function."""
|
|
192
|
-
mock_get_token.return_value = "test_api_key"
|
|
192
|
+
mock_get_token.return_value = ("test_api_key", False)
|
|
193
193
|
mock_fetch_data.return_value = self.mock_apollo_response
|
|
194
194
|
|
|
195
195
|
payload = {
|
|
@@ -2,7 +2,7 @@ import asyncio
|
|
|
2
2
|
import unittest
|
|
3
3
|
from unittest.mock import AsyncMock, patch
|
|
4
4
|
|
|
5
|
-
from src.dhisana.utils.test_connect import test_connectivity
|
|
5
|
+
from src.dhisana.utils.test_connect import test_connectivity as run_test_connectivity
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TestConnectivity(unittest.TestCase):
|
|
@@ -17,7 +17,7 @@ class TestConnectivity(unittest.TestCase):
|
|
|
17
17
|
]
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
|
-
result = await
|
|
20
|
+
result = await run_test_connectivity(tool_config)
|
|
21
21
|
self.assertIn('jinaai', result)
|
|
22
22
|
self.assertTrue(result['jinaai']['success'])
|
|
23
23
|
asyncio.run(runner())
|
|
@@ -33,7 +33,7 @@ class TestConnectivity(unittest.TestCase):
|
|
|
33
33
|
]
|
|
34
34
|
}
|
|
35
35
|
]
|
|
36
|
-
result = await
|
|
36
|
+
result = await run_test_connectivity(tool_config)
|
|
37
37
|
self.assertIn('firecrawl', result)
|
|
38
38
|
self.assertTrue(result['firecrawl']['success'])
|
|
39
39
|
asyncio.run(runner())
|
|
@@ -57,11 +57,30 @@ class TestConnectivity(unittest.TestCase):
|
|
|
57
57
|
]
|
|
58
58
|
}
|
|
59
59
|
]
|
|
60
|
-
result = await
|
|
60
|
+
result = await run_test_connectivity(tool_config)
|
|
61
61
|
self.assertIn('salesforce', result)
|
|
62
62
|
self.assertTrue(result['salesforce']['success'])
|
|
63
63
|
asyncio.run(runner())
|
|
64
64
|
|
|
65
|
+
def test_samgov_dispatch(self):
|
|
66
|
+
async def runner():
|
|
67
|
+
with patch(
|
|
68
|
+
'src.dhisana.utils.test_connect.test_samgov',
|
|
69
|
+
new=AsyncMock(return_value={'success': True, 'status_code': 200, 'error_message': None})
|
|
70
|
+
):
|
|
71
|
+
tool_config = [
|
|
72
|
+
{
|
|
73
|
+
'name': 'samgov',
|
|
74
|
+
'configuration': [
|
|
75
|
+
{'name': 'apiKey', 'value': 'dummy'}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
result = await run_test_connectivity(tool_config)
|
|
80
|
+
self.assertIn('samgov', result)
|
|
81
|
+
self.assertTrue(result['samgov']['success'])
|
|
82
|
+
asyncio.run(runner())
|
|
83
|
+
|
|
65
84
|
|
|
66
85
|
if __name__ == '__main__':
|
|
67
86
|
unittest.main()
|
|
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
|
{dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/check_email_validity_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/check_linkedin_url_validity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/compose_three_step_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/extract_email_content_for_llm.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
|
{dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/generate_linkedin_connect_message.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
|
{dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/openai_assistant_and_file_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/openapi_tool/openapi_tool.py
RENAMED
|
File without changes
|
{dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/parse_linkedin_messages_txt.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
|
{dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serarch_router_local_business.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dhisana-0.0.1.dev218 → dhisana-0.0.1.dev220}/src/dhisana/utils/serpapi_local_business_search.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|