11from __future__ import annotations
2+ import enum
23import json
34from dataclasses import dataclass
45from typing import Optional , List , Dict , Any , TypeVar , Generic , Callable
1415TSchema = TypeVar ("TSchema" )
1516
1617
17- class AiAgentActionRequest :
18- """Represents an action request from an AI agent."""
18+ class AiAgentActionRequestType (enum .Enum ):
19+ USER_ACTION = "UserAction"
20+ SUB_AGENT = "SubAgent"
21+
22+ def __str__ (self ) -> str :
23+ return self .value
24+
1925
20- def __init__ (self , name : str = None , tool_id : str = None , arguments : str = None ):
26+ class AiAgentActionRequest :
27+ def __init__ (
28+ self ,
29+ name : str = None ,
30+ tool_id : str = None ,
31+ arguments : str = None ,
32+ type : AiAgentActionRequestType = AiAgentActionRequestType .USER_ACTION ,
33+ sub_conversation_id : Optional [str ] = None ,
34+ ):
2135 self .name = name
2236 self .tool_id = tool_id
2337 self .arguments = arguments
38+ self .type = type
39+ self .sub_conversation_id = sub_conversation_id
2440
2541 @classmethod
2642 def from_json (cls , json_dict : Dict [str , Any ]) -> AiAgentActionRequest :
2743 return cls (
2844 name = json_dict .get ("Name" ),
2945 tool_id = json_dict .get ("ToolId" ),
3046 arguments = json_dict .get ("Arguments" ),
47+ type = AiAgentActionRequestType (json_dict .get ("Type" ) or "UserAction" ),
48+ sub_conversation_id = json_dict .get ("SubConversationId" ),
3149 )
3250
3351 def to_json (self ) -> Dict [str , Any ]:
3452 return {
3553 "Name" : self .name ,
3654 "ToolId" : self .tool_id ,
3755 "Arguments" : self .arguments ,
56+ "Type" : self .type .value ,
57+ "SubConversationId" : self .sub_conversation_id ,
3858 }
3959
60+ def __eq__ (self , other : object ) -> bool :
61+ if not isinstance (other , AiAgentActionRequest ):
62+ return False
63+ return (
64+ self .tool_id == other .tool_id
65+ and self .name == other .name
66+ and self .arguments == other .arguments
67+ and self .type == other .type
68+ and self .sub_conversation_id == other .sub_conversation_id
69+ )
70+
71+ def __hash__ (self ) -> int :
72+ return hash ((self .tool_id , self .name , self .arguments , self .type , self .sub_conversation_id ))
73+
74+ def __repr__ (self ) -> str :
75+ return json .dumps (self .to_json ())
76+
4077
4178@dataclass
4279class AiAgentActionResponse :
43- """Represents a response to an AI agent action request."""
44-
4580 tool_id : Optional [str ] = None
4681 content : Optional [str ] = None
4782
@@ -58,16 +93,12 @@ def to_json(self) -> Dict[str, Any]:
5893
5994@dataclass
6095class AiAgentArtificialActionResponse :
61- """
62- Represents an artificial action (tool call) and response to inject into the model's conversation context.
63- This allows programmatically prompting the agent by making it "believe" it executed a tool.
64- """
65-
96+ # Synthetic (tool_id, content) pair injected to make the model "believe"
97+ # it executed a tool. Sent in addition to a real ActionResponses entry.
6698 tool_id : Optional [str ] = None
6799 content : Optional [str ] = None
68100
69101 def validate (self ) -> None :
70- """Validates that tool_id and content are not empty."""
71102 if not self .tool_id or self .tool_id .isspace ():
72103 raise ValueError ("tool_id cannot be None or empty" )
73104 if not self .content or self .content .isspace ():
@@ -86,8 +117,6 @@ def to_json(self) -> Dict[str, Any]:
86117
87118@dataclass
88119class AiUsage :
89- """Represents AI token usage statistics."""
90-
91120 prompt_tokens : int = 0
92121 completion_tokens : int = 0
93122 total_tokens : int = 0
@@ -115,27 +144,15 @@ def to_json(self) -> Dict[str, Any]:
115144
116145 @staticmethod
117146 def get_usage_difference (current : AiUsage , previous : AiUsage ) -> AiUsage :
118- """
119- Calculate the usage difference between current and previous usage.
120-
121- Args:
122- current: The current usage statistics
123- previous: The previous usage statistics
124-
125- Returns:
126- An AiUsage object representing the difference
127- """
147+ # cached/completion/reasoning are last-response-only, so they pass
148+ # through. prompt/total are clamped against bogus negative model output.
128149 previous_total_without_reasoning = (
129150 previous .completion_tokens - previous .reasoning_tokens + previous .prompt_tokens
130151 )
131152 return AiUsage (
132- # in case the model gives us crappy results and current.prompt_tokens - previous_total_without_reasoning < 0
133153 prompt_tokens = max (current .prompt_tokens - previous_total_without_reasoning , 0 ),
134- # in case the model gives us crappy results and current.total_tokens - previous_total_without_reasoning < 0
135154 total_tokens = max (current .total_tokens - previous_total_without_reasoning , 0 ),
136- # we don't want to subtract cached tokens, as they are only for the last response
137155 cached_tokens = current .cached_tokens ,
138- # we don't want to subtract completion tokens, as they are only for the last response
139156 completion_tokens = current .completion_tokens ,
140157 reasoning_tokens = current .reasoning_tokens ,
141158 )
0 commit comments