johnbridges commited on
Commit
9e1fc4c
·
1 Parent(s): 7255aeb
Files changed (1) hide show
  1. models.py +388 -60
models.py CHANGED
@@ -1,10 +1,20 @@
1
  # models.py
2
- from typing import Any, Optional, List, Dict
 
3
  from datetime import datetime, timezone
4
  from pydantic import BaseModel, Field, ConfigDict
 
 
 
 
5
 
 
 
 
 
6
 
7
- # ---------- CloudEvent (Pydantic v2) ----------
 
8
  class CloudEvent(BaseModel):
9
  specversion: str = "1.0"
10
  id: str
@@ -29,15 +39,23 @@ class CloudEvent(BaseModel):
29
  )
30
 
31
 
32
- # ---------- FunctionCallData (from your C#) ----------
33
  class FunctionCallData(BaseModel):
34
  function: Optional[str] = None
35
  name: Optional[str] = None
36
  arguments: Optional[Dict[str, Any]] = None
37
  parameters: Optional[Dict[str, Any]] = None
38
 
 
 
 
 
 
 
 
 
39
 
40
- # ---------- FunctionState (from your C#) ----------
41
  class FunctionState(BaseModel):
42
  IsFunctionCall: bool = False
43
  IsFunctionCallResponse: bool = False
@@ -45,8 +63,111 @@ class FunctionState(BaseModel):
45
  IsFunctionCallStatus: bool = False
46
  IsFunctionStillRunning: bool = False
47
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- # ---------- UserInfo (from your C#; omitting JsonIgnored/NotMapped collections) ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  class UserInfo(BaseModel):
51
  UserID: Optional[str] = None
52
  DateCreated: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
@@ -70,99 +191,306 @@ class UserInfo(BaseModel):
70
  CustomerId: Optional[str] = ""
71
  CancelAt: Optional[datetime] = None
72
  TokensUsed: int = 0
73
-
74
- # NotMapped in C#: model as loose dict so it round-trips safely
75
  LoadServer: Dict[str, Any] = Field(default_factory=dict)
76
 
77
- # JsonIgnore MonitorIPs in C#: we do not include it here
 
 
 
 
78
 
79
 
 
80
  class LLMServiceObj(BaseModel):
81
- # allow using field names or aliases
 
 
 
82
  model_config = ConfigDict(populate_by_name=True)
83
 
84
- # strings
 
 
85
  SessionId: str = ""
86
- JsonFunction: str = ""
87
- LlmMessage: str = ""
88
- ResultMessage: str = ""
89
  UserInput: str = ""
90
- RequestSessionId: str = ""
91
  FunctionName: str = ""
92
- TimeZone: str = ""
93
  LLMRunnerType: str = "TurboLLM"
94
  SourceLlm: str = ""
95
  DestinationLlm: str = ""
96
- MessageID: str = ""
 
 
97
  LlmSessionStartName: str = ""
98
- SwapFunctionName: str = ""
99
  ChatAgentLocation: str = ""
100
  ToolsDefinitionId: Optional[str] = None
101
  JsonToolsBuilderSpec: Optional[str] = None
102
 
103
- # ints / bools
104
  TokensUsed: int = 0
105
  IsUserLoggedIn: bool = False
 
106
  IsFuncAck: bool = False
107
  IsProcessed: bool = False
108
  IsSystemLlm: bool = False
109
  Timeout: Optional[int] = None
110
 
111
- # complex (rename attrs; keep JSON names via alias)
112
  FunctionCallId: str = ""
113
- function_call_data: FunctionCallData = Field(default_factory=FunctionCallData, alias="FunctionCallData")
114
- user_info: UserInfo = Field(default_factory=UserInfo, alias="UserInfo")
115
- StartTimeUTC: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
116
 
117
- # stacks
118
  LlmStack: List[str] = Field(default_factory=list)
119
  FunctionCallIdStack: List[str] = Field(default_factory=list)
120
  FunctionNameStack: List[str] = Field(default_factory=list)
121
- IsProcessedStack: List[bool] = Field(default_factory=list)
122
  MessageIDStack: List[str] = Field(default_factory=list)
 
123
 
124
- # function state flags
125
- IsFunctionCall: bool = False
126
- IsFunctionCallResponse: bool = False
127
- IsFunctionCallError: bool = False
128
- IsFunctionCallStatus: bool = False
129
- IsFunctionStillRunning: bool = False
130
-
131
- def set_as_call(self) -> "LLMServiceObj":
132
- self.IsFunctionCall = True
133
- self.IsFunctionCallResponse = False
134
- self.IsFunctionCallError = False
135
- self.IsFunctionCallStatus = False
136
- self.IsFunctionStillRunning = False
137
- return self
138
 
139
- def set_as_call_error(self) -> "LLMServiceObj":
140
- self.IsFunctionCall = True
141
- self.IsFunctionCallResponse = False
142
- self.IsFunctionCallError = True
143
- self.IsFunctionCallStatus = False
144
- self.IsFunctionStillRunning = False
145
- return self
146
 
147
- def set_as_response_complete(self) -> "LLMServiceObj":
148
- self.IsFunctionCall = False
149
- self.IsFunctionCallResponse = True
150
- self.IsFunctionCallError = False
151
- self.IsFunctionCallStatus = False
152
- self.IsFunctionStillRunning = False
153
- self.IsProcessed = True
154
- return self
155
 
156
- def set_as_not_call(self) -> "LLMServiceObj":
157
- self.IsFunctionCall = False
158
- self.IsFunctionCallResponse = False
159
- self.IsFunctionCallError = False
160
- self.IsFunctionCallStatus = False
161
- self.IsFunctionStillRunning = False
162
- return self
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
 
 
 
 
 
164
 
165
 
 
166
  class ResultObj(BaseModel):
167
  Message: str = ""
168
  Success: bool = False
 
1
  # models.py
2
+ from __future__ import annotations
3
+ from typing import Any, Optional, List, Dict, Callable
4
  from datetime import datetime, timezone
5
  from pydantic import BaseModel, Field, ConfigDict
6
+ from dataclasses import dataclass
7
+ from zoneinfo import ZoneInfo
8
+ import secrets
9
+ import string
10
 
11
+ # ---------------- Nanoid-ish helper ----------------
12
+ _ALPHABET = string.ascii_letters + string.digits
13
+ def _nanoid(size: int = 21) -> str:
14
+ return "".join(secrets.choice(_ALPHABET) for _ in range(size))
15
 
16
+
17
+ # ---------- CloudEvent (kept for convenience; you can move to events.py) ----------
18
  class CloudEvent(BaseModel):
19
  specversion: str = "1.0"
20
  id: str
 
39
  )
40
 
41
 
42
+ # ---------- FunctionCallData ----------
43
  class FunctionCallData(BaseModel):
44
  function: Optional[str] = None
45
  name: Optional[str] = None
46
  arguments: Optional[Dict[str, Any]] = None
47
  parameters: Optional[Dict[str, Any]] = None
48
 
49
+ def copy_from(self, other: "FunctionCallData") -> None:
50
+ if other is None:
51
+ return
52
+ self.function = other.function
53
+ self.name = other.name
54
+ self.arguments = None if other.arguments is None else dict(other.arguments)
55
+ self.parameters = None if other.parameters is None else dict(other.parameters)
56
+
57
 
58
+ # ---------- FunctionState (full API parity with your C#) ----------
59
  class FunctionState(BaseModel):
60
  IsFunctionCall: bool = False
61
  IsFunctionCallResponse: bool = False
 
63
  IsFunctionCallStatus: bool = False
64
  IsFunctionStillRunning: bool = False
65
 
66
+ # --- C#-style mutators ---
67
+ def SetAsCall(self) -> "FunctionState":
68
+ self.IsFunctionCall = True
69
+ self.IsFunctionCallResponse = False
70
+ self.IsFunctionCallError = False
71
+ self.IsFunctionCallStatus = False
72
+ self.IsFunctionStillRunning = False
73
+ return self
74
+
75
+ # Note: C# calls SetAsCallError() -> SetAsCall(); mirroring that quirk.
76
+ def SetAsCallError(self) -> "FunctionState":
77
+ return self.SetAsCall()
78
 
79
+ def SetAsNotCall(self) -> "FunctionState":
80
+ self.IsFunctionCall = False
81
+ self.IsFunctionCallResponse = False
82
+ self.IsFunctionCallError = False
83
+ self.IsFunctionCallStatus = False
84
+ self.IsFunctionStillRunning = False
85
+ return self
86
+
87
+ def SetAsResponseComplete(self) -> "FunctionState":
88
+ self.IsFunctionCall = False
89
+ self.IsFunctionCallResponse = True
90
+ self.IsFunctionCallError = False
91
+ self.IsFunctionCallStatus = False
92
+ self.IsFunctionStillRunning = False
93
+ return self
94
+
95
+ def SetOnlyResponse(self) -> "FunctionState":
96
+ # Response arrived, not explicitly "complete"
97
+ self.IsFunctionCall = False
98
+ self.IsFunctionCallResponse = True
99
+ self.IsFunctionCallError = False
100
+ self.IsFunctionCallStatus = False
101
+ # leave StillRunning as-is
102
+ return self
103
+
104
+ def SetAsResponseRunning(self) -> "FunctionState":
105
+ self.IsFunctionCall = False
106
+ self.IsFunctionCallResponse = False
107
+ self.IsFunctionCallError = False
108
+ self.IsFunctionCallStatus = False
109
+ self.IsFunctionStillRunning = True
110
+ return self
111
+
112
+ def SetAsResponseStatus(self) -> "FunctionState":
113
+ self.IsFunctionCall = False
114
+ self.IsFunctionCallResponse = False
115
+ self.IsFunctionCallError = False
116
+ self.IsFunctionCallStatus = True
117
+ self.IsFunctionStillRunning = True
118
+ return self
119
+
120
+ def SetAsResponseStatusOnly(self) -> "FunctionState":
121
+ self.IsFunctionCall = False
122
+ self.IsFunctionCallResponse = False
123
+ self.IsFunctionCallError = False
124
+ self.IsFunctionCallStatus = True
125
+ self.IsFunctionStillRunning = False
126
+ return self
127
+
128
+ def SetAsResponseError(self) -> "FunctionState":
129
+ self.IsFunctionCall = False
130
+ self.IsFunctionCallResponse = False
131
+ self.IsFunctionCallError = True
132
+ self.IsFunctionCallStatus = False
133
+ self.IsFunctionStillRunning = False
134
+ return self
135
+
136
+ def SetAsResponseErrorComplete(self) -> "FunctionState":
137
+ # error, and mark "complete" by not running
138
+ self.IsFunctionCall = False
139
+ self.IsFunctionCallResponse = True
140
+ self.IsFunctionCallError = True
141
+ self.IsFunctionCallStatus = False
142
+ self.IsFunctionStillRunning = False
143
+ return self
144
+
145
+ def SetFunctionState(
146
+ self,
147
+ functionCall: bool,
148
+ functionCallResponse: bool,
149
+ functionCallError: bool,
150
+ functionCallStatus: bool,
151
+ functionStillRunning: bool,
152
+ ) -> "FunctionState":
153
+ self.IsFunctionCall = functionCall
154
+ self.IsFunctionCallResponse = functionCallResponse
155
+ self.IsFunctionCallError = functionCallError
156
+ self.IsFunctionCallStatus = functionCallStatus
157
+ self.IsFunctionStillRunning = functionStillRunning
158
+ return self
159
+
160
+ def StatesString(self) -> str:
161
+ return (
162
+ f"IsFunctionCall={self.IsFunctionCall}, "
163
+ f"IsFunctionCallResponse={self.IsFunctionCallResponse}, "
164
+ f"IsFunctionCallError={self.IsFunctionCallError}, "
165
+ f"IsFunctionCallStatus={self.IsFunctionCallStatus}, "
166
+ f"IsFunctionStillRunning={self.IsFunctionStillRunning}"
167
+ )
168
+
169
+
170
+ # ---------- UserInfo ----------
171
  class UserInfo(BaseModel):
172
  UserID: Optional[str] = None
173
  DateCreated: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
 
191
  CustomerId: Optional[str] = ""
192
  CancelAt: Optional[datetime] = None
193
  TokensUsed: int = 0
 
 
194
  LoadServer: Dict[str, Any] = Field(default_factory=dict)
195
 
196
+ def copy_from(self, other: "UserInfo") -> None:
197
+ if other is None:
198
+ return
199
+ for k, v in other.model_dump().items():
200
+ setattr(self, k, v)
201
 
202
 
203
+ # ---------- LLMServiceObj (full C# parity) ----------
204
  class LLMServiceObj(BaseModel):
205
+ """
206
+ Python port of NetworkMonitor.Objects.ServiceMessage.LLMServiceObj
207
+ retaining field names and behavior (stacks, function state methods, time helpers).
208
+ """
209
  model_config = ConfigDict(populate_by_name=True)
210
 
211
+ # --- core strings ---
212
+ MessageID: str = Field(default_factory=_nanoid)
213
+ RequestSessionId: str = ""
214
  SessionId: str = ""
 
 
 
215
  UserInput: str = ""
216
+ JsonFunction: str = ""
217
  FunctionName: str = ""
218
+ SwapFunctionName: str = ""
219
  LLMRunnerType: str = "TurboLLM"
220
  SourceLlm: str = ""
221
  DestinationLlm: str = ""
222
+ TimeZone: str = ""
223
+ LlmMessage: str = ""
224
+ ResultMessage: str = ""
225
  LlmSessionStartName: str = ""
 
226
  ChatAgentLocation: str = ""
227
  ToolsDefinitionId: Optional[str] = None
228
  JsonToolsBuilderSpec: Optional[str] = None
229
 
230
+ # --- flags / ints ---
231
  TokensUsed: int = 0
232
  IsUserLoggedIn: bool = False
233
+ ResultSuccess: bool = False
234
  IsFuncAck: bool = False
235
  IsProcessed: bool = False
236
  IsSystemLlm: bool = False
237
  Timeout: Optional[int] = None
238
 
239
+ # --- complex ---
240
  FunctionCallId: str = ""
241
+ FunctionCallData: FunctionCallData = Field(default_factory=FunctionCallData)
242
+ UserInfo: UserInfo = Field(default_factory=UserInfo)
 
243
 
244
+ # --- stacks ---
245
  LlmStack: List[str] = Field(default_factory=list)
246
  FunctionCallIdStack: List[str] = Field(default_factory=list)
247
  FunctionNameStack: List[str] = Field(default_factory=list)
 
248
  MessageIDStack: List[str] = Field(default_factory=list)
249
+ IsProcessedStack: List[bool] = Field(default_factory=list)
250
 
251
+ # --- function state ---
252
+ functionState: FunctionState = Field(default_factory=FunctionState)
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
+ # --- timing ---
255
+ StartTimeUTC: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
 
 
 
 
 
256
 
257
+ # ---------------- Constructors / copy semantics ----------------
258
+ @classmethod
259
+ def from_other(cls, other: "LLMServiceObj") -> "LLMServiceObj":
260
+ inst = cls()
261
+ inst.copy_from(other)
262
+ return inst
 
 
263
 
264
+ @classmethod
265
+ def from_other_with_state(cls, other: "LLMServiceObj", configure: Callable[[FunctionState], None]) -> "LLMServiceObj":
266
+ inst = cls()
267
+ inst.functionState = FunctionState()
268
+ inst.copy_from(other)
269
+ if configure:
270
+ configure(inst.functionState)
271
+ return inst
272
+
273
+ def copy_from(self, other: "LLMServiceObj") -> None:
274
+ if other is None:
275
+ return
276
+ # primitives
277
+ self.RequestSessionId = other.RequestSessionId
278
+ self.SessionId = other.SessionId
279
+ self.UserInput = other.UserInput
280
+ self.IsUserLoggedIn = other.IsUserLoggedIn
281
+ self.JsonFunction = other.JsonFunction
282
+
283
+ # function state
284
+ self.functionState = FunctionState().SetFunctionState(
285
+ other.IsFunctionCall,
286
+ other.IsFunctionCallResponse,
287
+ other.IsFunctionCallError,
288
+ other.IsFunctionCallStatus,
289
+ other.IsFunctionStillRunning,
290
+ )
291
+
292
+ # more primitives
293
+ self.FunctionName = other.FunctionName
294
+ self.FunctionCallId = other.FunctionCallId
295
+ self.LLMRunnerType = other.LLMRunnerType
296
+ self.SourceLlm = other.SourceLlm
297
+ self.DestinationLlm = other.DestinationLlm
298
+ self.TokensUsed = other.TokensUsed
299
+ self.LlmMessage = other.LlmMessage
300
+ self.IsSystemLlm = other.IsSystemLlm
301
+ self.ResultSuccess = other.ResultSuccess
302
+ self.ResultMessage = other.ResultMessage
303
+ self.TimeZone = other.TimeZone
304
+ self.LlmSessionStartName = other.LlmSessionStartName
305
+ self.MessageID = other.MessageID
306
+ self.StartTimeUTC = other.StartTimeUTC
307
+ self.ChatAgentLocation = other.ChatAgentLocation
308
+ self.ToolsDefinitionId = other.ToolsDefinitionId
309
+ self.JsonToolsBuilderSpec = other.JsonToolsBuilderSpec
310
+ self.Timeout = other.Timeout
311
+ self.SwapFunctionName = other.SwapFunctionName
312
+ self.IsFuncAck = other.IsFuncAck
313
+ self.IsProcessed = other.IsProcessed
314
+
315
+ # deep copies
316
+ self.FunctionCallData = FunctionCallData()
317
+ self.FunctionCallData.copy_from(other.FunctionCallData)
318
+
319
+ self.UserInfo = UserInfo()
320
+ self.UserInfo.copy_from(other.UserInfo)
321
+
322
+ self.LlmStack = list(other.LlmStack)
323
+ self.FunctionCallIdStack = list(other.FunctionCallIdStack)
324
+ self.FunctionNameStack = list(other.FunctionNameStack)
325
+ self.MessageIDStack = list(other.MessageIDStack)
326
+ self.IsProcessedStack = list(other.IsProcessedStack)
327
+
328
+ # ---------------- Function state passthroughs (C# names) ----------------
329
+ def GetFunctionStateString(self) -> str:
330
+ return self.functionState.StatesString()
331
+
332
+ def SetAsCall(self) -> None: self.functionState.SetAsCall()
333
+ def SetAsCallError(self) -> None: self.functionState.SetAsCallError()
334
+ def SetAsNotCall(self) -> None: self.functionState.SetAsNotCall()
335
+ def SetAsResponseComplete(self) -> None: self.functionState.SetAsResponseComplete()
336
+ def SetOnlyResponse(self) -> None: self.functionState.SetOnlyResponse()
337
+ def SetAsResponseRunning(self) -> None: self.functionState.SetAsResponseRunning()
338
+ def SetAsResponseStatus(self) -> None: self.functionState.SetAsResponseStatus()
339
+ def SetAsResponseStatusOnly(self) -> None: self.functionState.SetAsResponseStatusOnly()
340
+ def SetAsResponseError(self) -> None: self.functionState.SetAsResponseError()
341
+ def SetAsResponseErrorComplete(self) -> None: self.functionState.SetAsResponseErrorComplete()
342
+ def SetFunctionState(self, a: bool, b: bool, c: bool, d: bool, e: bool) -> None:
343
+ self.functionState.SetFunctionState(a, b, c, d, e)
344
+
345
+ # C# exposes the flags as properties that forward to functionState; we mirror that with @property
346
+ @property
347
+ def IsFunctionCall(self) -> bool:
348
+ return self.functionState.IsFunctionCall
349
+ @IsFunctionCall.setter
350
+ def IsFunctionCall(self, v: bool) -> None:
351
+ self.functionState.IsFunctionCall = v
352
+
353
+ @property
354
+ def IsFunctionCallResponse(self) -> bool:
355
+ return self.functionState.IsFunctionCallResponse
356
+ @IsFunctionCallResponse.setter
357
+ def IsFunctionCallResponse(self, v: bool) -> None:
358
+ self.functionState.IsFunctionCallResponse = v
359
+
360
+ @property
361
+ def IsFunctionCallError(self) -> bool:
362
+ return self.functionState.IsFunctionCallError
363
+ @IsFunctionCallError.setter
364
+ def IsFunctionCallError(self, v: bool) -> None:
365
+ self.functionState.IsFunctionCallError = v
366
+
367
+ @property
368
+ def IsFunctionCallStatus(self) -> bool:
369
+ return self.functionState.IsFunctionCallStatus
370
+ @IsFunctionCallStatus.setter
371
+ def IsFunctionCallStatus(self, v: bool) -> None:
372
+ self.functionState.IsFunctionCallStatus = v
373
+
374
+ @property
375
+ def IsFunctionStillRunning(self) -> bool:
376
+ return self.functionState.IsFunctionStillRunning
377
+ @IsFunctionStillRunning.setter
378
+ def IsFunctionStillRunning(self, v: bool) -> None:
379
+ self.functionState.IsFunctionStillRunning = v
380
+
381
+ # ---------------- Stack helpers & routing props ----------------
382
+ def PopLlm(self) -> None:
383
+ if self.LlmStack:
384
+ self.SourceLlm = self.LlmStack.pop()
385
+ self.DestinationLlm = self.SourceLlm
386
+ self.PopMessageID()
387
+ self.PopFunctionCallId()
388
+ self.PopFunctionName()
389
+ self.PopIsProcessed()
390
+
391
+ def PushLmm(self, llmName: str, newFunctionCallId: str, newFunctionName: str, newMessageID: str, newIsProcessed: bool) -> None:
392
+ if self.SourceLlm:
393
+ self.LlmStack.append(self.SourceLlm)
394
+ self.SourceLlm = self.DestinationLlm
395
+ self.DestinationLlm = llmName
396
+ self.PushMessageID(newMessageID)
397
+ self.PushFunctionCallId(newFunctionCallId)
398
+ self.PushFunctionName(newFunctionName)
399
+ self.PushIsProcessed(newIsProcessed)
400
+
401
+ @property
402
+ def LlmChainStartName(self) -> str:
403
+ if not self.LlmStack:
404
+ return self.SourceLlm
405
+ return self.LlmStack[0]
406
+
407
+ @property
408
+ def RootMessageID(self) -> str:
409
+ if not self.MessageIDStack:
410
+ return self.MessageID
411
+ return self.MessageIDStack[0]
412
+
413
+ @property
414
+ def FirstFunctionName(self) -> str:
415
+ if not self.FunctionNameStack:
416
+ return self.FunctionName
417
+ return self.FunctionNameStack[0]
418
+
419
+ @property
420
+ def IsPrimaryLlm(self) -> bool:
421
+ if self.IsSystemLlm:
422
+ return False
423
+ return self.SourceLlm == self.DestinationLlm
424
+
425
+ def PopMessageID(self) -> None:
426
+ if self.MessageIDStack:
427
+ self.MessageID = self.MessageIDStack.pop()
428
+
429
+ def PushMessageID(self, newMessageID: str) -> None:
430
+ if self.MessageID:
431
+ self.MessageIDStack.append(self.MessageID)
432
+ self.MessageID = newMessageID
433
+
434
+ def PopFunctionCallId(self) -> None:
435
+ if self.FunctionCallIdStack:
436
+ self.FunctionCallId = self.FunctionCallIdStack.pop()
437
+
438
+ def PushFunctionCallId(self, newFunctionCallId: str) -> None:
439
+ if self.FunctionCallId:
440
+ self.FunctionCallIdStack.append(self.FunctionCallId)
441
+ self.FunctionCallId = newFunctionCallId
442
+
443
+ def PopFunctionName(self) -> None:
444
+ if self.FunctionNameStack:
445
+ self.FunctionName = self.FunctionNameStack.pop()
446
+
447
+ def PushFunctionName(self, newFunctionName: str) -> None:
448
+ if self.FunctionName:
449
+ self.FunctionNameStack.append(self.FunctionName)
450
+ self.FunctionName = newFunctionName
451
+
452
+ def PushIsProcessed(self, newIsProcessed: bool) -> None:
453
+ self.IsProcessedStack.append(self.IsProcessed)
454
+ self.IsProcessed = newIsProcessed
455
+
456
+ def PopIsProcessed(self) -> None:
457
+ if self.IsProcessedStack:
458
+ self.IsProcessed = self.IsProcessedStack.pop()
459
+
460
+ # ---------------- Client time helpers ----------------
461
+ def _tz(self) -> ZoneInfo:
462
+ try:
463
+ return ZoneInfo(self.TimeZone) if self.TimeZone else ZoneInfo("UTC")
464
+ except Exception:
465
+ return ZoneInfo("UTC")
466
+
467
+ def GetClientCurrentTime(self) -> datetime:
468
+ try:
469
+ utc_now = datetime.now(timezone.utc)
470
+ return utc_now.astimezone(self._tz())
471
+ except Exception:
472
+ return datetime.now(timezone.utc)
473
+
474
+ def GetClientStartTime(self) -> datetime:
475
+ try:
476
+ return self.StartTimeUTC.astimezone(self._tz())
477
+ except Exception:
478
+ return datetime.now(timezone.utc)
479
+
480
+ def GetClientCurrentUnixTime(self) -> int:
481
+ try:
482
+ return int(self.GetClientCurrentTime().timestamp())
483
+ except Exception:
484
+ return int(datetime.now(timezone.utc).timestamp())
485
 
486
+ def GetClientStartUnixTime(self) -> int:
487
+ try:
488
+ return int(self.GetClientStartTime().timestamp())
489
+ except Exception:
490
+ return int(datetime.now(timezone.utc).timestamp())
491
 
492
 
493
+ # ---------- ResultObj ----------
494
  class ResultObj(BaseModel):
495
  Message: str = ""
496
  Success: bool = False