trodo-python 1.0.0__py3-none-any.whl → 1.2.0__py3-none-any.whl

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.
@@ -1,74 +1,74 @@
1
- """Server session creation utilities."""
2
-
3
- from __future__ import annotations
4
-
5
- import time
6
- import uuid
7
- from datetime import datetime, timezone
8
- from typing import Any, Dict, Optional
9
-
10
- from ..types import ServerSession
11
-
12
-
13
- def now_iso() -> str:
14
- return datetime.now(timezone.utc).isoformat()
15
-
16
-
17
- def create_server_session(
18
- site_id: str,
19
- distinct_id: str,
20
- session_id: Optional[str] = None,
21
- ) -> ServerSession:
22
- return ServerSession(
23
- session_id=session_id or str(uuid.uuid4()),
24
- site_id=site_id,
25
- distinct_id=distinct_id,
26
- start_time=now_iso(),
27
- last_activity=time.time(),
28
- confirmed=False,
29
- )
30
-
31
-
32
- def build_session_payload(session: ServerSession) -> Dict[str, Any]:
33
- return {
34
- "session_id": session.session_id,
35
- "site_id": session.site_id,
36
- "user_id": session.distinct_id,
37
- "distinct_id": session.distinct_id,
38
- "team_id": None,
39
- "start_time": session.start_time,
40
- "end_time": None,
41
- "last_activity": int(session.last_activity * 1000),
42
- "duration": 0,
43
- "pages_viewed": 0,
44
- "is_bounce": False,
45
- "previous_session_id": None,
46
- "time_since_last_session": None,
47
- "entry_page": None,
48
- "exit_page": None,
49
- "referrer": "server",
50
- "ip_address": None,
51
- "city": None,
52
- "region": None,
53
- "country": None,
54
- "browser_name": None,
55
- "browser_version": None,
56
- "device_type": "server",
57
- "os": None,
58
- "resolution": None,
59
- "user_agent": None,
60
- "language": None,
61
- "wallet_address": None,
62
- "wallet_type": None,
63
- "chain_name": None,
64
- "is_web3_user": False,
65
- "wallet_connected": False,
66
- "utm_source": None,
67
- "utm_medium": None,
68
- "utm_campaign": None,
69
- "utm_term": None,
70
- "utm_content": None,
71
- "utm_id": None,
72
- "visited_pages": [],
73
- "active_time_ms": 0,
74
- }
1
+ """Server session creation utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ import uuid
7
+ from datetime import datetime, timezone
8
+ from typing import Any, Dict, Optional
9
+
10
+ from ..types import ServerSession
11
+
12
+
13
+ def now_iso() -> str:
14
+ return datetime.now(timezone.utc).isoformat()
15
+
16
+
17
+ def create_server_session(
18
+ site_id: str,
19
+ distinct_id: str,
20
+ session_id: Optional[str] = None,
21
+ ) -> ServerSession:
22
+ return ServerSession(
23
+ session_id=session_id or str(uuid.uuid4()),
24
+ site_id=site_id,
25
+ distinct_id=distinct_id,
26
+ start_time=now_iso(),
27
+ last_activity=time.time(),
28
+ confirmed=False,
29
+ )
30
+
31
+
32
+ def build_session_payload(session: ServerSession) -> Dict[str, Any]:
33
+ return {
34
+ "session_id": session.session_id,
35
+ "site_id": session.site_id,
36
+ "user_id": session.distinct_id,
37
+ "distinct_id": session.distinct_id,
38
+ "team_id": None,
39
+ "start_time": session.start_time,
40
+ "end_time": None,
41
+ "last_activity": int(session.last_activity * 1000),
42
+ "duration": 0,
43
+ "pages_viewed": 0,
44
+ "is_bounce": False,
45
+ "previous_session_id": None,
46
+ "time_since_last_session": None,
47
+ "entry_page": None,
48
+ "exit_page": None,
49
+ "referrer": "server",
50
+ "ip_address": None,
51
+ "city": None,
52
+ "region": None,
53
+ "country": None,
54
+ "browser_name": None,
55
+ "browser_version": None,
56
+ "device_type": "server",
57
+ "os": None,
58
+ "resolution": None,
59
+ "user_agent": None,
60
+ "language": None,
61
+ "wallet_address": None,
62
+ "wallet_type": None,
63
+ "chain_name": None,
64
+ "is_web3_user": False,
65
+ "wallet_connected": False,
66
+ "utm_source": None,
67
+ "utm_medium": None,
68
+ "utm_campaign": None,
69
+ "utm_term": None,
70
+ "utm_content": None,
71
+ "utm_id": None,
72
+ "visited_pages": [],
73
+ "active_time_ms": 0,
74
+ }
@@ -1,74 +1,74 @@
1
- """Thread-safe in-process session cache."""
2
-
3
- from __future__ import annotations
4
-
5
- import threading
6
- from typing import Dict, Optional
7
-
8
- from ..types import ServerSession
9
- from .server_session import build_session_payload, create_server_session
10
-
11
-
12
- class SessionManager:
13
- def __init__(self) -> None:
14
- self._sessions: Dict[str, ServerSession] = {}
15
- self._lock = threading.Lock()
16
- self._confirmation_locks: Dict[str, threading.Event] = {}
17
- self._confirmation_started: Dict[str, bool] = {}
18
-
19
- def get_or_create(
20
- self,
21
- distinct_id: str,
22
- site_id: str,
23
- session_id: Optional[str] = None,
24
- ) -> ServerSession:
25
- with self._lock:
26
- if distinct_id in self._sessions:
27
- return self._sessions[distinct_id]
28
- session = create_server_session(site_id, distinct_id, session_id)
29
- self._sessions[distinct_id] = session
30
- return session
31
-
32
- def ensure_confirmed(self, session: ServerSession, http_client: object) -> None:
33
- """
34
- Block until the session has been confirmed via POST /api/sdk/track.
35
- Idempotent — concurrent callers share a single confirmation attempt.
36
- """
37
- if session.confirmed:
38
- return
39
-
40
- key = session.distinct_id
41
-
42
- with self._lock:
43
- if session.confirmed:
44
- return
45
- # Check if confirmation already in-flight
46
- if key not in self._confirmation_locks:
47
- event = threading.Event()
48
- self._confirmation_locks[key] = event
49
- should_run = True
50
- else:
51
- event = self._confirmation_locks[key]
52
- should_run = False
53
-
54
- if should_run:
55
- try:
56
- payload = build_session_payload(session)
57
- http_client.post_track(payload) # type: ignore[attr-defined]
58
- except Exception:
59
- pass # Confirmation failure is non-fatal
60
- finally:
61
- session.confirmed = True
62
- with self._lock:
63
- self._confirmation_locks.pop(key, None)
64
- event.set()
65
- else:
66
- event.wait(timeout=10)
67
-
68
- def invalidate(self, distinct_id: str) -> None:
69
- with self._lock:
70
- self._sessions.pop(distinct_id, None)
71
- self._confirmation_locks.pop(distinct_id, None)
72
-
73
- def get_global_session(self, site_id: str) -> ServerSession:
74
- return self.get_or_create("server_global", site_id)
1
+ """Thread-safe in-process session cache."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import threading
6
+ from typing import Dict, Optional
7
+
8
+ from ..types import ServerSession
9
+ from .server_session import build_session_payload, create_server_session
10
+
11
+
12
+ class SessionManager:
13
+ def __init__(self) -> None:
14
+ self._sessions: Dict[str, ServerSession] = {}
15
+ self._lock = threading.Lock()
16
+ self._confirmation_locks: Dict[str, threading.Event] = {}
17
+ self._confirmation_started: Dict[str, bool] = {}
18
+
19
+ def get_or_create(
20
+ self,
21
+ distinct_id: str,
22
+ site_id: str,
23
+ session_id: Optional[str] = None,
24
+ ) -> ServerSession:
25
+ with self._lock:
26
+ if distinct_id in self._sessions:
27
+ return self._sessions[distinct_id]
28
+ session = create_server_session(site_id, distinct_id, session_id)
29
+ self._sessions[distinct_id] = session
30
+ return session
31
+
32
+ def ensure_confirmed(self, session: ServerSession, http_client: object) -> None:
33
+ """
34
+ Block until the session has been confirmed via POST /api/sdk/track.
35
+ Idempotent — concurrent callers share a single confirmation attempt.
36
+ """
37
+ if session.confirmed:
38
+ return
39
+
40
+ key = session.distinct_id
41
+
42
+ with self._lock:
43
+ if session.confirmed:
44
+ return
45
+ # Check if confirmation already in-flight
46
+ if key not in self._confirmation_locks:
47
+ event = threading.Event()
48
+ self._confirmation_locks[key] = event
49
+ should_run = True
50
+ else:
51
+ event = self._confirmation_locks[key]
52
+ should_run = False
53
+
54
+ if should_run:
55
+ try:
56
+ payload = build_session_payload(session)
57
+ http_client.post_track(payload) # type: ignore[attr-defined]
58
+ except Exception:
59
+ pass # Confirmation failure is non-fatal
60
+ finally:
61
+ session.confirmed = True
62
+ with self._lock:
63
+ self._confirmation_locks.pop(key, None)
64
+ event.set()
65
+ else:
66
+ event.wait(timeout=10)
67
+
68
+ def invalidate(self, distinct_id: str) -> None:
69
+ with self._lock:
70
+ self._sessions.pop(distinct_id, None)
71
+ self._confirmation_locks.pop(distinct_id, None)
72
+
73
+ def get_global_session(self, site_id: str) -> ServerSession:
74
+ return self.get_or_create("server_global", site_id)
trodo/types.py CHANGED
@@ -1,79 +1,154 @@
1
- """Type definitions for the Trodo Python SDK."""
2
-
3
- from __future__ import annotations
4
-
5
- from dataclasses import dataclass, field
6
- from typing import Any, Dict, List, Optional, Union
7
-
8
- # ----------------------------------------------------------------------------
9
- # Configuration
10
- # ----------------------------------------------------------------------------
11
-
12
- @dataclass
13
- class TrodoConfig:
14
- site_id: str
15
- api_base: str = "https://sdkapi.trodo.ai"
16
- timeout: int = 10
17
- retries: int = 2
18
- batch_enabled: bool = False
19
- batch_size: int = 50
20
- batch_flush_interval: float = 5.0
21
- auto_events: bool = False
22
- on_error: Optional[Any] = None # Callable[[Exception], None]
23
- debug: bool = False
24
-
25
-
26
- # ----------------------------------------------------------------------------
27
- # Session
28
- # ----------------------------------------------------------------------------
29
-
30
- @dataclass
31
- class ServerSession:
32
- session_id: str
33
- site_id: str
34
- distinct_id: str
35
- start_time: str # ISO 8601
36
- last_activity: float # epoch seconds
37
- confirmed: bool = False
38
-
39
-
40
- # ----------------------------------------------------------------------------
41
- # Events
42
- # ----------------------------------------------------------------------------
43
-
44
- @dataclass
45
- class EventPayload:
46
- event_type: str # 'custom' | 'auto'
47
- event_name: str
48
- event_category: str
49
- session_id: str
50
- user_id: str
51
- custom_properties: Dict[str, Any] = field(default_factory=dict)
52
- page_data: None = None
53
- auto_event_data: Optional[Dict[str, Any]] = None
54
- element_data: None = None
55
- override_data: None = None
56
-
57
- def to_dict(self) -> Dict[str, Any]:
58
- return {
59
- "event_type": self.event_type,
60
- "event_name": self.event_name,
61
- "event_category": self.event_category,
62
- "session_id": self.session_id,
63
- "user_id": self.user_id,
64
- "custom_properties": self.custom_properties,
65
- "page_data": self.page_data,
66
- "auto_event_data": self.auto_event_data,
67
- "element_data": self.element_data,
68
- "override_data": self.override_data,
69
- }
70
-
71
-
72
- # ----------------------------------------------------------------------------
73
- # Results
74
- # ----------------------------------------------------------------------------
75
-
76
- ApiResult = Dict[str, Any]
77
- IdentifyResult = Dict[str, Any]
78
- WalletAddressResult = Dict[str, Any]
79
- ResetResult = Dict[str, Any]
1
+ """Type definitions for the Trodo Python SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Dict, List, Optional, Union
7
+
8
+ # ----------------------------------------------------------------------------
9
+ # Configuration
10
+ # ----------------------------------------------------------------------------
11
+
12
+ @dataclass
13
+ class TrodoConfig:
14
+ site_id: str
15
+ api_base: str = "https://sdkapi.trodo.ai"
16
+ timeout: int = 10
17
+ retries: int = 2
18
+ batch_enabled: bool = False
19
+ batch_size: int = 50
20
+ batch_flush_interval: float = 5.0
21
+ auto_events: bool = False
22
+ on_error: Optional[Any] = None # Callable[[Exception], None]
23
+ debug: bool = False
24
+
25
+
26
+ # ----------------------------------------------------------------------------
27
+ # Session
28
+ # ----------------------------------------------------------------------------
29
+
30
+ @dataclass
31
+ class ServerSession:
32
+ session_id: str
33
+ site_id: str
34
+ distinct_id: str
35
+ start_time: str # ISO 8601
36
+ last_activity: float # epoch seconds
37
+ confirmed: bool = False
38
+
39
+
40
+ # ----------------------------------------------------------------------------
41
+ # Events
42
+ # ----------------------------------------------------------------------------
43
+
44
+ @dataclass
45
+ class EventPayload:
46
+ event_type: str # 'custom' | 'auto'
47
+ event_name: str
48
+ event_category: str
49
+ session_id: str
50
+ user_id: str
51
+ custom_properties: Dict[str, Any] = field(default_factory=dict)
52
+ page_data: None = None
53
+ auto_event_data: Optional[Dict[str, Any]] = None
54
+ element_data: None = None
55
+ override_data: None = None
56
+
57
+ def to_dict(self) -> Dict[str, Any]:
58
+ return {
59
+ "event_type": self.event_type,
60
+ "event_name": self.event_name,
61
+ "event_category": self.event_category,
62
+ "session_id": self.session_id,
63
+ "user_id": self.user_id,
64
+ "custom_properties": self.custom_properties,
65
+ "page_data": self.page_data,
66
+ "auto_event_data": self.auto_event_data,
67
+ "element_data": self.element_data,
68
+ "override_data": self.override_data,
69
+ }
70
+
71
+
72
+ # ----------------------------------------------------------------------------
73
+ # Results
74
+ # ----------------------------------------------------------------------------
75
+
76
+ ApiResult = Dict[str, Any]
77
+ IdentifyResult = Dict[str, Any]
78
+ WalletAddressResult = Dict[str, Any]
79
+ ResetResult = Dict[str, Any]
80
+
81
+
82
+ # ----------------------------------------------------------------------------
83
+ # Agent Analytics
84
+ # ----------------------------------------------------------------------------
85
+
86
+ @dataclass
87
+ class AgentCallProps:
88
+ agent_id: str
89
+ conversation_id: str
90
+ message_id: str
91
+ distinct_id: Optional[str] = None
92
+ prompt: Optional[str] = None
93
+ model: Optional[str] = None
94
+ temperature: Optional[float] = None
95
+ system_prompt_version: Optional[str] = None
96
+ provider: Optional[str] = None
97
+ timestamp: Optional[str] = None
98
+ properties: Dict[str, Any] = field(default_factory=dict)
99
+
100
+
101
+ @dataclass
102
+ class ToolUseProps:
103
+ agent_id: str
104
+ conversation_id: str
105
+ message_id: str
106
+ tool_name: str
107
+ distinct_id: Optional[str] = None
108
+ input: Optional[Any] = None
109
+ output: Optional[Any] = None
110
+ latency_ms: Optional[int] = None
111
+ status: Optional[str] = None # 'success' | 'failure'
112
+ timestamp: Optional[str] = None
113
+ properties: Dict[str, Any] = field(default_factory=dict)
114
+
115
+
116
+ @dataclass
117
+ class AgentResponseProps:
118
+ agent_id: str
119
+ conversation_id: str
120
+ message_id: str
121
+ distinct_id: Optional[str] = None
122
+ output: Optional[str] = None
123
+ model: Optional[str] = None
124
+ completion_tokens: Optional[int] = None
125
+ prompt_tokens: Optional[int] = None
126
+ total_tokens: Optional[int] = None
127
+ finish_reason: Optional[str] = None
128
+ timestamp: Optional[str] = None
129
+ properties: Dict[str, Any] = field(default_factory=dict)
130
+
131
+
132
+ @dataclass
133
+ class AgentErrorProps:
134
+ agent_id: str
135
+ conversation_id: str
136
+ message_id: str
137
+ distinct_id: Optional[str] = None
138
+ error_type: Optional[str] = None
139
+ error_message: Optional[str] = None
140
+ failed_tool: Optional[str] = None
141
+ traceback: Optional[str] = None
142
+ timestamp: Optional[str] = None
143
+ properties: Dict[str, Any] = field(default_factory=dict)
144
+
145
+
146
+ @dataclass
147
+ class FeedbackProps:
148
+ agent_id: str
149
+ conversation_id: str
150
+ message_id: str
151
+ feedback: str # 'positive' | 'negative' | 'unreact'
152
+ distinct_id: Optional[str] = None
153
+ timestamp: Optional[str] = None
154
+ properties: Dict[str, Any] = field(default_factory=dict)