@brandon_9527/tcode 1.0.9 → 1.0.10
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.
- package/dist/python-src/.env +4 -4
- package/dist/python-src/README.md +8 -0
- package/dist/python-src/main.py +12 -4
- package/dist/python-src/src/agents/token_tracker.py +4 -4
- package/dist/python-src/src/claw/channels/feishu.py +12 -12
- package/dist/python-src/src/claw/cron/service.py +30 -30
- package/dist/python-src/src/claw/heartbeat/service.py +4 -4
- package/dist/python-src/src/core/context.py +8 -8
- package/dist/python-src/src/managers/manager_agent.py +5 -5
- package/dist/python-src/src/managers/manager_instruction.py +7 -7
- package/dist/python-src/src/middlewares/subagents.py +4 -4
- package/dist/python-src/src/middlewares/summary.py +34 -34
- package/dist/python-src/src/stream/formatter.py +14 -14
- package/dist/python-src/src/stream/handler.py +4 -4
- package/dist/python-src/src/stream/handler_with_tracker.py +9 -9
- package/dist/python-src/src/trackers/token/report.py +4 -4
- package/dist/python-src/src/trackers/token/tracker.py +8 -8
- package/dist/python-src/src/tui/chatui.py +10 -10
- package/dist/python-src/src/tui/clawtui.py +26 -20
- package/dist/python-src/src/tui/components/tlist.py +7 -7
- package/dist/python-src/src/tui/components/tscroll_panel.py +10 -10
- package/dist/python-src/src/tui/components/tscroll_panel_old.py +8 -8
- package/dist/python-src/src/tui/utils/trender.py +19 -19
- package/package.json +1 -1
|
@@ -27,7 +27,7 @@ ContextFraction=tuple[Literal[_D],float]
|
|
|
27
27
|
ContextTokens=tuple[Literal[_E],int]
|
|
28
28
|
ContextMessages=tuple[Literal[_B],int]
|
|
29
29
|
ContextSize=ContextFraction|ContextTokens|ContextMessages
|
|
30
|
-
def
|
|
30
|
+
def dj(model):
|
|
31
31
|
if model._llm_type=='anthropic-chat':return partial(count_tokens_approximately,chars_per_token=3.3)
|
|
32
32
|
return count_tokens_approximately
|
|
33
33
|
class SummaryState(AgentState):compact:NotRequired[bool]=_F
|
|
@@ -44,61 +44,61 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
44
44
|
if isinstance(C,str):C=init_chat_model(C)
|
|
45
45
|
A.model=C
|
|
46
46
|
if B is _A:A.trigger=_A;G=[]
|
|
47
|
-
elif isinstance(B,list):I=[A.
|
|
48
|
-
else:J=A.
|
|
49
|
-
A._trigger_conditions=G;A.keep=A.
|
|
50
|
-
if H is count_tokens_approximately:A.token_counter=
|
|
47
|
+
elif isinstance(B,list):I=[A.dk(B,N)for B in B];A.trigger=I;G=I
|
|
48
|
+
else:J=A.dk(B,N);A.trigger=J;G=[J]
|
|
49
|
+
A._trigger_conditions=G;A.keep=A.dk(F,'keep')
|
|
50
|
+
if H is count_tokens_approximately:A.token_counter=dj(A.model)
|
|
51
51
|
else:A.token_counter=H
|
|
52
52
|
A.summary_prompt=summary_prompt;A.trim_tokens_to_summarize=trim_tokens_to_summarize;K=any(A[0]==_D for A in A._trigger_conditions)
|
|
53
53
|
if A.keep[0]==_D:K=_C
|
|
54
|
-
if K and A.
|
|
55
|
-
def
|
|
54
|
+
if K and A.dl()is _A:O='Model profile information is required to use fractional token limits, and is unavailable for the specified model. Please use absolute token counts instead, or pass `\n\nChatModel(..., profile={"max_input_tokens": ...})`.\n\nwith a desired integer value of the model\'s maximum input tokens.';raise ValueError(O)
|
|
55
|
+
def da(B,state):A=state.get(_G,_F);return bool(A)
|
|
56
56
|
@override
|
|
57
57
|
def before_model(self,state,runtime):
|
|
58
|
-
C=state;A=self;B=C[_B];A.
|
|
58
|
+
C=state;A=self;B=C[_B];A.dc(B);E=A.token_counter(B);F=A.da(C);G=A.do(B,E)
|
|
59
59
|
if not G and not F:return
|
|
60
|
-
D=A.
|
|
60
|
+
D=A.db(B)
|
|
61
61
|
if D<=0:return
|
|
62
|
-
H,I=A.
|
|
62
|
+
H,I=A.de(B,D);J=A.dd(H);K=A.dn(J);return{_B:[RemoveMessage(id=REMOVE_ALL_MESSAGES),*K,*I],_G:_F}
|
|
63
63
|
@override
|
|
64
64
|
async def abefore_model(self,state,runtime):
|
|
65
|
-
C=state;A=self;B=C[_B];A.
|
|
65
|
+
C=state;A=self;B=C[_B];A.dc(B);E=A.token_counter(B);F=A.da(C);G=A.do(B,E)
|
|
66
66
|
if not G and not F:return
|
|
67
|
-
D=A.
|
|
67
|
+
D=A.db(B)
|
|
68
68
|
if D<=0:return
|
|
69
|
-
H,I=A.
|
|
70
|
-
def
|
|
69
|
+
H,I=A.de(B,D);J=await A._acreate_summary(H);K=A.dn(J);return{_B:[RemoveMessage(id=REMOVE_ALL_MESSAGES),*K,*I],_G:_F}
|
|
70
|
+
def df(B,messages,threshold):
|
|
71
71
|
A=next((A for A in reversed(messages)if isinstance(A,AIMessage)),_A)
|
|
72
72
|
if isinstance(A,AIMessage)and A.usage_metadata is not _A and(C:=A.usage_metadata.get('total_tokens',-1))and C>=threshold and(D:=A.response_metadata.get('model_provider'))and D==B.model._get_ls_params().get('ls_provider'):return _C
|
|
73
73
|
return _F
|
|
74
|
-
def
|
|
74
|
+
def do(A,messages,total_tokens):
|
|
75
75
|
F=total_tokens;E=messages
|
|
76
76
|
if not A._trigger_conditions:return _F
|
|
77
77
|
for(B,C)in A._trigger_conditions:
|
|
78
78
|
if B==_B and len(E)>=C:return _C
|
|
79
79
|
if B==_E and F>=C:return _C
|
|
80
|
-
if B==_E and A.
|
|
80
|
+
if B==_E and A.df(E,C):return _C
|
|
81
81
|
if B==_D:
|
|
82
|
-
G=A.
|
|
82
|
+
G=A.dl()
|
|
83
83
|
if G is _A:continue
|
|
84
84
|
D=int(G*C)
|
|
85
85
|
if D<=0:D=1
|
|
86
86
|
if F>=D:return _C
|
|
87
|
-
if A.
|
|
87
|
+
if A.df(E,D):return _C
|
|
88
88
|
return _F
|
|
89
|
-
def
|
|
89
|
+
def db(A,messages):
|
|
90
90
|
B=messages;D,E=A.keep
|
|
91
91
|
if D in{_E,_D}:
|
|
92
|
-
C=A.
|
|
92
|
+
C=A.dg(B)
|
|
93
93
|
if C is not _A:return C
|
|
94
|
-
return A.
|
|
95
|
-
return A.
|
|
96
|
-
def
|
|
94
|
+
return A.dh(B,_DEFAULT_MESSAGES_TO_KEEP)
|
|
95
|
+
return A.dh(B,cast('int',E))
|
|
96
|
+
def dg(C,messages):
|
|
97
97
|
A=messages
|
|
98
98
|
if not A:return 0
|
|
99
99
|
H,I=C.keep
|
|
100
100
|
if H==_D:
|
|
101
|
-
J=C.
|
|
101
|
+
J=C.dl()
|
|
102
102
|
if J is _A:return
|
|
103
103
|
F=int(J*I)
|
|
104
104
|
elif H==_E:F=int(I)
|
|
@@ -114,8 +114,8 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
114
114
|
if B>=len(A):
|
|
115
115
|
if len(A)==1:return 0
|
|
116
116
|
B=len(A)-1
|
|
117
|
-
return C.
|
|
118
|
-
def
|
|
117
|
+
return C.di(A,B)
|
|
118
|
+
def dl(C):
|
|
119
119
|
try:A=C.model.profile
|
|
120
120
|
except AttributeError:return
|
|
121
121
|
if not isinstance(A,Mapping):return
|
|
@@ -123,7 +123,7 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
123
123
|
if not isinstance(B,int):return
|
|
124
124
|
return B
|
|
125
125
|
@staticmethod
|
|
126
|
-
def
|
|
126
|
+
def dk(context,parameter_name):
|
|
127
127
|
E=context;C=parameter_name;D,B=E
|
|
128
128
|
if D==_D:
|
|
129
129
|
if not 0<B<=1:A=f"Fractional {C} values must be between 0 and 1, got {B}.";raise ValueError(A)
|
|
@@ -132,19 +132,19 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
132
132
|
else:A=f"Unsupported context size type {D} for {C}.";raise ValueError(A)
|
|
133
133
|
return E
|
|
134
134
|
@staticmethod
|
|
135
|
-
def
|
|
135
|
+
def dn(summary):return[HumanMessage(content=f"Here is a summary of the conversation to date:\n\n{summary}",additional_kwargs={'lc_source':'summarization'})]
|
|
136
136
|
@staticmethod
|
|
137
|
-
def
|
|
137
|
+
def dc(messages):
|
|
138
138
|
for A in messages:
|
|
139
139
|
if A.id is _A:A.id=str(uuid.uuid4())
|
|
140
140
|
@staticmethod
|
|
141
|
-
def
|
|
142
|
-
def
|
|
141
|
+
def de(conversation_messages,cutoff_index):B=cutoff_index;A=conversation_messages;C=A[:B];D=A[B:];return C,D
|
|
142
|
+
def dh(C,messages,messages_to_keep):
|
|
143
143
|
B=messages_to_keep;A=messages
|
|
144
144
|
if len(A)<=B:return 0
|
|
145
|
-
D=len(A)-B;return C.
|
|
145
|
+
D=len(A)-B;return C.di(A,D)
|
|
146
146
|
@staticmethod
|
|
147
|
-
def
|
|
147
|
+
def di(messages,cutoff_index):
|
|
148
148
|
B=cutoff_index;A=messages
|
|
149
149
|
if B>=len(A)or not isinstance(A[B],ToolMessage):return B
|
|
150
150
|
E=set();C=B
|
|
@@ -158,7 +158,7 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
158
158
|
H={A.get('id')for A in D.tool_calls if A.get('id')}
|
|
159
159
|
if E&H:return G
|
|
160
160
|
return C
|
|
161
|
-
def
|
|
161
|
+
def dd(A,messages_to_sumarize):
|
|
162
162
|
B=messages_to_sumarize
|
|
163
163
|
if not B:return _H
|
|
164
164
|
C=A.dm(B)
|
|
@@ -17,17 +17,17 @@ class ToolResultFormatter:
|
|
|
17
17
|
A=content;A=A.strip()
|
|
18
18
|
if A.statswith(SUCCESS_PREFIX):
|
|
19
19
|
C=B.by(A)
|
|
20
|
-
if B.
|
|
20
|
+
if B.bz(C):return ContentType.JSON
|
|
21
21
|
return ContentType.SUCCESS
|
|
22
22
|
if A.startswith(FAILURE_PREFIX):return ContentType.ERROR
|
|
23
|
-
if B.
|
|
23
|
+
if B.bz(A):return ContentType.JSON
|
|
24
24
|
if B.bx(A):return ContentType.ERROR
|
|
25
|
-
if B.
|
|
25
|
+
if B.ca(A):return ContentType.MARKDOWN
|
|
26
26
|
return ContentType.TEXT
|
|
27
27
|
def is_success(A,content):return _is_success(content)
|
|
28
|
-
def format(A,name,content,max_length=800):B=content;C=A.detect_type(B);D=A.is_success(B);E={ContentType.SUCCESS:A.
|
|
28
|
+
def format(A,name,content,max_length=800):B=content;C=A.detect_type(B);D=A.is_success(B);E={ContentType.SUCCESS:A.cb,ContentType.ERROR:A.bw,ContentType.JSON:A.cc,ContentType.MARKDOWN:A.bv,ContentType.TEXT:A.cd};F=E.get(C,A.cd);G=F(name,B,max_length);return FormattedResult(content_type=C,elements=G,success=D)
|
|
29
29
|
def by(B,content):A=content.split('\n',2);return A[2].strip()if len(A)>2 else''
|
|
30
|
-
def
|
|
30
|
+
def bz(B,content):
|
|
31
31
|
A=content;A=A.strip()
|
|
32
32
|
if not A:return _A
|
|
33
33
|
if A.startswith('{')and A.endswith('}')or A.startswith('[')and A.endswith(']'):
|
|
@@ -35,14 +35,14 @@ class ToolResultFormatter:
|
|
|
35
35
|
except(json.JSONDecodeError,ValueError):pass
|
|
36
36
|
return _A
|
|
37
37
|
def bx(B,content):A=['Traceback (most recent call last)','Exception:','Error:'];return any(A in content for A in A)
|
|
38
|
-
def
|
|
39
|
-
def
|
|
40
|
-
def
|
|
41
|
-
def
|
|
38
|
+
def ca(C,content):A=content;B=['```','**','##','- **'];return A.startswith('#')or any(B in A for B in B)
|
|
39
|
+
def cb(B,name,content,max_length):A='green';C=B.bu(content,max_length);return[Panel(Text(C,style=A),title=f"📤 {name} ✓",border_style=A)]
|
|
40
|
+
def bw(B,name,content,max_length):A='red';C=B.bu(content,max_length);return[Panel(Text(C,style=A),title=f"📤 {name} ✗",border_style=A)]
|
|
41
|
+
def cc(B,name,content,max_length):
|
|
42
42
|
D=max_length;A=content;E=A
|
|
43
43
|
if A.startswith(SUCCESS_PREFIX):E=B.by(A)
|
|
44
|
-
try:F=json.loads(E);C=json.dumps(F,indent=2,ensure_ascii=_A);C=B.
|
|
45
|
-
except(json.JSONDecodeError,ValueError):return B.
|
|
46
|
-
def
|
|
47
|
-
def
|
|
48
|
-
def
|
|
44
|
+
try:F=json.loads(E);C=json.dumps(F,indent=2,ensure_ascii=_A);C=B.bu(C,D);return[Text(f"📤 {name} ✓",style=_B),Syntax(C,'json',theme='monokai',line_numbers=_A)]
|
|
45
|
+
except(json.JSONDecodeError,ValueError):return B.cd(name,A,D)
|
|
46
|
+
def bv(A,name,content,max_length):B=A.bu(content,max_length);return[Panel(Markdown(B),title=f"📤 {name}",border_style='cyan dim')]
|
|
47
|
+
def cd(A,name,content,max_length):B=A.bu(content,max_length);return[Text(f"📤 {name}:",style=_B),Text(f" {B}",style='dim')]
|
|
48
|
+
def bu(A,content,max_length):return truncate(content,max_length)
|
|
@@ -87,7 +87,7 @@ async def astream_handler(stream,debug=_M,interrupt_tools=[],tool_mode=_I):
|
|
|
87
87
|
else:F=D
|
|
88
88
|
if J:b=type(F).__name__;print(f"[DEBUG] Event: {b}")
|
|
89
89
|
if isinstance(F,(AIMessageChunk,AIMessage)):
|
|
90
|
-
for A in
|
|
90
|
+
for A in ce(F,T):
|
|
91
91
|
if A.type==_L:S+=A.data.get(_E,'')
|
|
92
92
|
if J:print(f"[DEBUG] Yielding: {A.type}")
|
|
93
93
|
yield A.data
|
|
@@ -124,7 +124,7 @@ def stream_handler(stream,debug=_M,interrupts=[],tool_mode=_I):
|
|
|
124
124
|
else:E=C
|
|
125
125
|
if J:W=type(E).__name__;print(f"[DEBUG] Event: {W}")
|
|
126
126
|
if isinstance(E,(AIMessageChunk,AIMessage)):
|
|
127
|
-
for A in
|
|
127
|
+
for A in ce(E,T):
|
|
128
128
|
if A.type==_L:S+=A.data.get(_E,'')
|
|
129
129
|
if J:print(f"[DEBUG] Yielding: {A.type}")
|
|
130
130
|
yield A.data
|
|
@@ -133,7 +133,7 @@ def stream_handler(stream,debug=_M,interrupts=[],tool_mode=_I):
|
|
|
133
133
|
if P==_T:A=emitter.tool_call(F,L,H,Q);yield A.data
|
|
134
134
|
elif P==_U:A=emitter.tool_result(F,M,X,H,Q);yield A.data
|
|
135
135
|
if J:print(_b)
|
|
136
|
-
def
|
|
136
|
+
def ce(chunk,stream_parser):
|
|
137
137
|
M='reasoning';L='thinking';I='write_file';F=stream_parser;E=chunk;B=E.content
|
|
138
138
|
if isinstance(B,str):
|
|
139
139
|
if B:yield emitter.text(B);return
|
|
@@ -166,7 +166,7 @@ def cf(chunk,stream_parser):
|
|
|
166
166
|
if isinstance(D,str)and D:
|
|
167
167
|
if H==I:F.feed(D)
|
|
168
168
|
if H==I and D=='':F.reset()
|
|
169
|
-
def
|
|
169
|
+
def cf(chunk):
|
|
170
170
|
A=chunk;D=getattr(A,_B,'unknown');B=str(getattr(A,_E,''));E=getattr(A,'tool_call_id','');F=getattr(A,_H,_W);C=B[:DisplayLimits.TOOL_RESULT_MAX]
|
|
171
171
|
if len(B)>DisplayLimits.TOOL_RESULT_MAX:C+='\n... (truncated)'
|
|
172
172
|
yield emitter.tool_result(D,C,F,E)
|
|
@@ -59,17 +59,17 @@ async def astream_handler(stream,emitter,tracker,debug=False):
|
|
|
59
59
|
else:A=B
|
|
60
60
|
if F:Y=type(A).__name__;print(f"[DEBUG] Event: {Y}")
|
|
61
61
|
if isinstance(A,(AIMessageChunk,AIMessage)):
|
|
62
|
-
for C in
|
|
62
|
+
for C in ci(A,E,J,S):
|
|
63
63
|
if C.type==_J:R+=C.data.get(_C,'')
|
|
64
64
|
if F:print(f"[DEBUG] Yielding: {C.type}")
|
|
65
65
|
yield C.data
|
|
66
66
|
if hasattr(A,_Y)and A.tool_calls:
|
|
67
|
-
for C in
|
|
67
|
+
for C in cj(A.tool_calls,E,J):
|
|
68
68
|
if F:print(f"[DEBUG] Yielding from tool_calls: {C.type} took call: {A.tool_calls}")
|
|
69
69
|
yield C.data
|
|
70
70
|
elif hasattr(A,_B)and A.type=='tool':
|
|
71
71
|
if F:I=getattr(A,_A,_L);print(f"[DEBUG] Processing tool result: {I}")
|
|
72
|
-
for C in
|
|
72
|
+
for C in ck(A,E,J):
|
|
73
73
|
if F:print(f"[DEBUG] Yielding: {C.type}")
|
|
74
74
|
yield C.data
|
|
75
75
|
elif K==_Z:
|
|
@@ -99,17 +99,17 @@ def stream_handler(stream,emitter,tracker,debug=False):
|
|
|
99
99
|
else:A=B
|
|
100
100
|
if F:Y=type(A).__name__;print(f"[DEBUG] Event: {Y}")
|
|
101
101
|
if isinstance(A,(AIMessageChunk,AIMessage)):
|
|
102
|
-
for C in
|
|
102
|
+
for C in ci(A,E,J,S):
|
|
103
103
|
if C.type==_J:R+=C.data.get(_C,'')
|
|
104
104
|
if F:print(f"[DEBUG] Yielding: {C.type}")
|
|
105
105
|
yield C.data
|
|
106
106
|
if hasattr(A,_Y)and A.tool_calls:
|
|
107
|
-
for C in
|
|
107
|
+
for C in cj(A.tool_calls,E,J):
|
|
108
108
|
if F:print(f"[DEBUG] Yielding from tool_calls: {C.type}")
|
|
109
109
|
yield C.data
|
|
110
110
|
elif hasattr(A,_B)and A.type=='tool':
|
|
111
111
|
if F:I=getattr(A,_A,_L);print(f"[DEBUG] Processing tool result: {I}")
|
|
112
|
-
for C in
|
|
112
|
+
for C in ck(A,E,J):
|
|
113
113
|
if F:print(f"[DEBUG] Yielding: {C.type}")
|
|
114
114
|
yield C.data
|
|
115
115
|
elif K==_Z:
|
|
@@ -117,7 +117,7 @@ def stream_handler(stream,emitter,tracker,debug=False):
|
|
|
117
117
|
if O==_N:yield E.tool_call(I,Z,Q,P)
|
|
118
118
|
elif O==_e:yield E.tool_result(I,a,b,Q,P)
|
|
119
119
|
if F:print(_f)
|
|
120
|
-
def
|
|
120
|
+
def ci(chunk,emitter,tracker,stream_parser):
|
|
121
121
|
U='index';T='tool_use';S='reasoning';R='thinking';L='write_file';K=stream_parser;J=chunk;I=emitter;E=tracker;C=J.content
|
|
122
122
|
if isinstance(C,str):
|
|
123
123
|
if C:yield I.text(C);return
|
|
@@ -165,14 +165,14 @@ def ch(block,emitter,tracker):
|
|
|
165
165
|
if A:
|
|
166
166
|
D=B.get(_A,'');E=B.get('input',{});F=E if isinstance(E,dict)else{};C.update(A,name=D,args=F)
|
|
167
167
|
if C.is_ready(A):C.mark_emitted(A);yield emitter.tool_call(D,F,A)
|
|
168
|
-
def
|
|
168
|
+
def cj(tool_calls,emitter,tracker):
|
|
169
169
|
B=tracker
|
|
170
170
|
for C in tool_calls:
|
|
171
171
|
A=C.get(_K,'')
|
|
172
172
|
if A:
|
|
173
173
|
D=C.get(_A,'');E=C.get(_F,{});F=E if isinstance(E,dict)else{};B.update(A,name=D,args_complete=True)
|
|
174
174
|
if B.is_ready(A):B.mark_emitted(A);yield emitter.tool_call(D,F,A)
|
|
175
|
-
def
|
|
175
|
+
def ck(chunk,emitter,tracker):
|
|
176
176
|
E=tracker;D=emitter;A=chunk;E.finalize_all()
|
|
177
177
|
for B in E.get_all():yield D.tool_call(B.name,B.args,B.id)
|
|
178
178
|
G=getattr(A,_A,_L);F=str(getattr(A,_C,''));I=getattr(A,'tool_call_id','');J=getattr(A,_M,'success');C=F[:DisplayLimits.TOOL_RESULT_MAX]
|
|
@@ -19,7 +19,7 @@ from collections import defaultdict
|
|
|
19
19
|
from datetime import datetime
|
|
20
20
|
from typing import Optional
|
|
21
21
|
from.tracker import CostTracker
|
|
22
|
-
def
|
|
22
|
+
def dy(since,until,tag=_A,user_id=_A,session_id=_A):
|
|
23
23
|
F=session_id;E=user_id;D=until;C=since;A=[];B=[]
|
|
24
24
|
if C:A.append('timestamp >= ?');B.append(C.isoformat())
|
|
25
25
|
if D:A.append('timestamp <= ?');B.append(D.isoformat())
|
|
@@ -27,7 +27,7 @@ def dx(since,until,tag=_A,user_id=_A,session_id=_A):
|
|
|
27
27
|
if E:A.append('user_id = ?');B.append(E)
|
|
28
28
|
if F:A.append('session_id = ?');B.append(F)
|
|
29
29
|
G=' AND '.join(A)if A else'1=1';return G,B
|
|
30
|
-
def
|
|
30
|
+
def dx(period):
|
|
31
31
|
A=period
|
|
32
32
|
if A=='daily':return"strftime('%Y-%m-%d', timestamp)"
|
|
33
33
|
elif A=='weekly':return"strftime('%Y-%W', timestamp)"
|
|
@@ -35,7 +35,7 @@ def dw(period):
|
|
|
35
35
|
else:raise ValueError(f"Unknown period: {A}")
|
|
36
36
|
class CostReport:
|
|
37
37
|
def __init__(A,tracker):A.tracker=tracker
|
|
38
|
-
def build(L,since=_A,until=_A,period='daily',tag=_A,user_id=_A,session_id=_A):I=period;H=until;G=since;F='output_tokens';E='input_tokens';B,C=
|
|
38
|
+
def build(L,since=_A,until=_A,period='daily',tag=_A,user_id=_A,session_id=_A):I=period;H=until;G=since;F='output_tokens';E='input_tokens';B,C=dy(G,H,tag,user_id,session_id);A=L.tracker._conn.cursor();A.execute(f"""
|
|
39
39
|
SELECT
|
|
40
40
|
COUNT(*),
|
|
41
41
|
SUM(cost),
|
|
@@ -87,7 +87,7 @@ class CostReport:
|
|
|
87
87
|
WHERE {B}
|
|
88
88
|
GROUP BY session_id
|
|
89
89
|
ORDER BY SUM(cost) DESC
|
|
90
|
-
""",C);R={A:{_B:round(C or 0,6),_C:B,E:D or 0,F:G or 0}for(A,B,C,D,G)in A.fetchall()};S=
|
|
90
|
+
""",C);R={A:{_B:round(C or 0,6),_C:B,E:D or 0,F:G or 0}for(A,B,C,D,G)in A.fetchall()};S=dx(I);A.execute(f"""
|
|
91
91
|
SELECT
|
|
92
92
|
{S} as bucket,
|
|
93
93
|
COUNT(*),
|
|
@@ -20,12 +20,12 @@ class BudgetGuard:tag:Optional[str]=_A;user_id:Optional[str]=_A;session_id:Optio
|
|
|
20
20
|
class CostTracker:
|
|
21
21
|
DEFAULT_DB=Path.home()/'.config'/'llm-cost-tracker'/'usage.db'
|
|
22
22
|
def __init__(A,db_path=_A,pricing=_A,budgets=_A,batch_size=50,flush_interval=1.):A.db_path=Path(db_path or os.environ.get('LLM_COST_DB',A.DEFAULT_DB));A.db_path.parent.mkdir(parents=_D,exist_ok=_D);A.pricing=pricing;A.budgets={A.tag or f"{A.user_id or _C}_{A.session_id or _B}":A for A in budgets or[]};A.batch_size=batch_size;A.flush_interval=flush_interval;A._queue=asyncio.Queue();A._task=_A;A._conn=_A
|
|
23
|
-
async def start(A):A._conn=sqlite3.connect(A.db_path,isolation_level=_A,check_same_thread=False);A._conn.execute('PRAGMA journal_mode=WAL;');A._conn.execute('PRAGMA synchronous=NORMAL;');A.
|
|
23
|
+
async def start(A):A._conn=sqlite3.connect(A.db_path,isolation_level=_A,check_same_thread=False);A._conn.execute('PRAGMA journal_mode=WAL;');A._conn.execute('PRAGMA synchronous=NORMAL;');A.dt();A._task=asyncio.create_task(A._writer_loop())
|
|
24
24
|
async def stop(A):
|
|
25
25
|
await A._queue.put(_A)
|
|
26
26
|
if A._task:await A._task
|
|
27
27
|
if A._conn:A._conn.close()
|
|
28
|
-
def
|
|
28
|
+
def dt(A):A._conn.execute(' \n CREATE TABLE IF NOT EXISTS usage (\n id TEXT PRIMARY KEY,\n model TEXT,\n input_tokens INTEGER,\n output_tokens INTEGER,\n cost REAL,\n \n tag TEXT,\n user_id TEXT,\n session_id TEXT,\n \n timestamp TEXT\n )\n ');A._conn.execute('CREATE INDEX IF NOT EXISTS idx_tag ON usage(tag)');A._conn.execute('CREATE INDEX IF NOT EXISTS idx_user_id ON usage(user_id)');A._conn.execute('CREATE INDEX IF NOT EXISTS idx_session_id ON usage(session_id)');A._conn.execute('CREATE INDEX IF NOT EXISTS idx_user_session ON usage(user_id, session_id)')
|
|
29
29
|
async def add_call(A,model,input_tokens,output_tokens,tag=_B,user_id=_C,session_id=_C):
|
|
30
30
|
D=output_tokens;C=input_tokens;B=model;E=.0
|
|
31
31
|
if A.pricing:
|
|
@@ -38,12 +38,12 @@ class CostTracker:
|
|
|
38
38
|
try:C=await asyncio.wait_for(B._queue.get(),timeout=B.flush_interval)
|
|
39
39
|
except asyncio.TimeoutError:C=_A
|
|
40
40
|
if C is _A:
|
|
41
|
-
if A:B.
|
|
41
|
+
if A:B.du(A);A.clear()
|
|
42
42
|
if C is _A and B._queue.empty():break
|
|
43
43
|
continue
|
|
44
44
|
A.append(C)
|
|
45
|
-
if len(A)>=B.batch_size:B.
|
|
46
|
-
def
|
|
45
|
+
if len(A)>=B.batch_size:B.du(A);A.clear()
|
|
46
|
+
def du(D,records):
|
|
47
47
|
F=records;A=D._conn.cursor()
|
|
48
48
|
try:
|
|
49
49
|
A.execute('BEGIN');A.executemany(' \n INSERT INTO usage\n (id, model, input_tokens, output_tokens, cost, tag, user_id, session_id, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n ',[A.to_tuple()for A in F]);H={A.tag for A in F}
|
|
@@ -55,11 +55,11 @@ class CostTracker:
|
|
|
55
55
|
elif E>=C.alert_at:print(f"[WARN] {B} budget {E:.0%}")
|
|
56
56
|
A.execute('COMMIT')
|
|
57
57
|
except Exception:A.execute('ROLLBACK');raise
|
|
58
|
-
def
|
|
58
|
+
def dv(A):
|
|
59
59
|
B=[];C=[]
|
|
60
60
|
if A.tag:B.append('tag=?');C.append(A.tag)
|
|
61
61
|
if A.user_id:B.append('user_id=?');C.append(A.user_id)
|
|
62
62
|
if A.session_id:B.append('session_id=?');C.append(A.session_id)
|
|
63
63
|
D=' AND '.join(B)if B else'1=1';return D,C
|
|
64
|
-
def total_cost(A,tag=_A,user_id=_A,session_id=_A):B=A._conn.cursor();C=BudgetGuard(tag,user_id,session_id);D,E=A.
|
|
65
|
-
def total_tokens(C,tag=_A,user_id=_A,session_id=_A):E=BudgetGuard(tag,user_id,session_id);D=C._conn.cursor();F,G=C.
|
|
64
|
+
def total_cost(A,tag=_A,user_id=_A,session_id=_A):B=A._conn.cursor();C=BudgetGuard(tag,user_id,session_id);D,E=A.dv(C);B.execute(f"SELECT SUM(cost) FROM usage WHERE {D}",E);return B.fetchone()[0]or .0
|
|
65
|
+
def total_tokens(C,tag=_A,user_id=_A,session_id=_A):E=BudgetGuard(tag,user_id,session_id);D=C._conn.cursor();F,G=C.dv(E);D.execute(f"SELECT SUM(input_tokens), SUM(output_tokens) FROM usage WHERE {F}",G);A,B=D.fetchone();A=A or 0;B=B or 0;return{'input':A,'output':B,'total':A+B}
|
|
@@ -42,11 +42,11 @@ from src.tui.utils.trender import display_tool_call,display_tool_result,display_
|
|
|
42
42
|
from langchain_core.messages import HumanMessage
|
|
43
43
|
from langgraph.types import Command
|
|
44
44
|
from dotenv import find_dotenv,load_dotenv
|
|
45
|
-
|
|
45
|
+
q=load_dotenv(find_dotenv())
|
|
46
46
|
class LiveChatUI:
|
|
47
|
-
def __init__(A,agent=_A,saver=_A,workspace=_A,**C):B='#afafff';A.agent=agent;A.saver=saver;A.kwargs=C;A.workspace=re.sub('^(\\/Users\\/[^/]+|\\/home\\/[^/]+)','~',workspace);A.thread_id=1;A.token_count=0;A.mode=_E;A.context=C.get('context',_A);A.instruction_manager=C.get('instruction_manager',_A);A.spinner=Spinner('block');A.max_input_lines=10;A.cancel_event=_A;A.logo_label=Label(LOGO.format(A.workspace),style='class:logo');A.begin_items=[A.logo_label];A.begin_area=HSplit([*A.begin_items],padding=1);A.log_control=ScrollableFormattedLogControl();A.output_area=Window(content=A.log_control,wrap_lines=_B,always_hide_cursor=_C,height=D(weight=1));A.status_label=FormattedTextControl(text=[(_H,' 状态: 等待输入 | Tokens: 0 (⌥ + ⏎ 换行 Esc 中断 ctrl + c 退出)')]);F=FormattedTextControl(text=[('class:spinner',f"{A.spinner.current_frame()}")],show_cursor=_C);G=FormattedTextControl(text=[(_F,f"mode: {A.mode}")]);A.status_bar=VSplit([Window(F,width=D(weight=5),dont_extend_width=_B,dont_extend_height=_B,height=D(weight=1)),Window(A.status_label,width=D(weight=85),height=D(weight=1)),Window(G,width=D(weight=10),dont_extend_width=_B,dont_extend_height=_B,height=D(weight=1))],width=D(weight=100));A.input_box=TextArea(height=1,prompt='> ',multiline=_B,wrap_lines=_B,scrollbar=_B,style='class:input_box');A.input_box.buffer.on_text_changed+=lambda _:A.update_input_area_height(
|
|
48
|
-
def
|
|
49
|
-
def
|
|
47
|
+
def __init__(A,agent=_A,saver=_A,workspace=_A,**C):B='#afafff';A.agent=agent;A.saver=saver;A.kwargs=C;A.workspace=re.sub('^(\\/Users\\/[^/]+|\\/home\\/[^/]+)','~',workspace);A.thread_id=1;A.token_count=0;A.mode=_E;A.context=C.get('context',_A);A.instruction_manager=C.get('instruction_manager',_A);A.spinner=Spinner('block');A.max_input_lines=10;A.cancel_event=_A;A.logo_label=Label(LOGO.format(A.workspace),style='class:logo');A.begin_items=[A.logo_label];A.begin_area=HSplit([*A.begin_items],padding=1);A.log_control=ScrollableFormattedLogControl();A.output_area=Window(content=A.log_control,wrap_lines=_B,always_hide_cursor=_C,height=D(weight=1));A.status_label=FormattedTextControl(text=[(_H,' 状态: 等待输入 | Tokens: 0 (⌥ + ⏎ 换行 Esc 中断 ctrl + c 退出)')]);F=FormattedTextControl(text=[('class:spinner',f"{A.spinner.current_frame()}")],show_cursor=_C);G=FormattedTextControl(text=[(_F,f"mode: {A.mode}")]);A.status_bar=VSplit([Window(F,width=D(weight=5),dont_extend_width=_B,dont_extend_height=_B,height=D(weight=1)),Window(A.status_label,width=D(weight=85),height=D(weight=1)),Window(G,width=D(weight=10),dont_extend_width=_B,dont_extend_height=_B,height=D(weight=1))],width=D(weight=100));A.input_box=TextArea(height=1,prompt='> ',multiline=_B,wrap_lines=_B,scrollbar=_B,style='class:input_box');A.input_box.buffer.on_text_changed+=lambda _:A.update_input_area_height(q);A.kb=KeyBindings();A.t();H=Frame(body=A.input_box,style='class:frame');A.input_items=[A.status_bar,H];A.input_area=HSplit([*A.input_items],padding=0);A.interact_items=[];A.interact_area=HSplit([*A.interact_items],padding=1);E=_F;A.footer=VSplit([Window(FormattedTextControl([(E,f"{A.workspace} (main) ")]),width=D(weight=50)),Window(FormattedTextControl([(E,f"MCP: (0/0) ")]),width=D(weight=20)),Window(FormattedTextControl([(E,'Env: (local) ')]),width=D(weight=20)),Window(FormattedTextControl([(E,f"Model: kimi-k2-0711-preview ")]),wrap_lines=_B,dont_extend_width=_C,always_hide_cursor=_B,width=D(weight=10))],width=D(weight=100),height=1);A.suggest_items=[A.footer];A.suggest_area=HSplit([*A.suggest_items],padding=0);A.logo_area=DynamicContainer(lambda:A.begin_area);A.display_container=DynamicContainer(lambda:A.output_area);A.input_container=DynamicContainer(lambda:A.input_area);A.status_area=DynamicContainer(lambda:A.suggest_area);A.layout=Layout(HSplit([A.logo_area,A.output_area,A.interact_area,A.input_container,A.status_area],padding=0),focused_element=A.input_box);A.style=Style.from_dict({'logo':B,'output':B,'input_box':B,'status':B,'frame.border':B,'suggestions':B,'footer':B,'suggestion.label':B,'suggestion.desc':'#5f5f5f','spinner':B,'suggestion.selected':'bold #00afff'});A.app=Application(layout=A.layout,key_bindings=A.kb,style=A.style,full_screen=_B,mouse_support=_B);A.app.input_area=A.input_box;A.app.kb=A.kb;A.interrupt_tools=C.get('interrupt_tools',[]);A.toolcall_mode='manul'
|
|
48
|
+
def u(A,role='user',spinner='●',status='',tokens=0):A.status_label.text=[(_H,f" 状态: {status} | ({role}) | Tokens: {tokens} (esc + ⏎ 换行 按两次 esc 中断 ctrl + c 退出)")];A.app.invalidate()
|
|
49
|
+
def r(A,workspace=_A,mcp_status=_A,sandbox_status=_A,model_status=_A):
|
|
50
50
|
F=model_status;E=sandbox_status;D=mcp_status;C=workspace;B=_F
|
|
51
51
|
if C is not _A:A._footer_workspace.text=[(B,f"{C}(main) ")]
|
|
52
52
|
if D is not _A:A._footer_context.text=[(B,f"{D} ")]
|
|
@@ -59,7 +59,7 @@ class LiveChatUI:
|
|
|
59
59
|
def clear(A):A.log_control.clear();A.input_box.text='';B=render_info(LOGO.format(A.workspace),style='light_stell_blue',markdown=_C);A.log_control.append_text(B);A.app.invalidate();A.app.layout.focus(A.input_box)
|
|
60
60
|
async def updater(A):await A.spinner.run(A.app)
|
|
61
61
|
async def run_async(A):await asyncio.gather(A.app.run_async(),A.updater())
|
|
62
|
-
def
|
|
62
|
+
def t(A):
|
|
63
63
|
D='enter';C='escape'
|
|
64
64
|
@A.kb.add(D)
|
|
65
65
|
def B(event):
|
|
@@ -78,19 +78,19 @@ class LiveChatUI:
|
|
|
78
78
|
if A.logo_label in A.begin_items:A.begin_items.remove(A.logo_label);A.begin_area.children=list(A.begin_items);J=render_info(LOGO.format(A.workspace),style=D,markdown=_C);A.log_control.append_text(J);A.app.invalidate()
|
|
79
79
|
if B.strip()in['quit','exit','q']:get_app().exit();return
|
|
80
80
|
if B.strip()in['/clear','clear']:A.clear();return
|
|
81
|
-
A.spinner.start();A.
|
|
81
|
+
A.spinner.start();A.s('● user',f"● {B}",style='light_salmon3');await asyncio.sleep(.05)
|
|
82
82
|
if B.strip()in['/commands']and A.instruction_manager:
|
|
83
83
|
E=[]
|
|
84
84
|
for F in A.instruction_manager.list_instructions():E.append(f"/{F.name}: - {F.settings[_D]}")
|
|
85
|
-
K='\n'.join(E);A.
|
|
85
|
+
K='\n'.join(E);A.s('● bot',K,style=D,markdown=_B);return
|
|
86
86
|
if A.instruction_manager:G=A.instruction_manager.parse(B);H,I=G['executed_instruction'],G['message'];B=f"""
|
|
87
87
|
[注意]: 执行用户请求必须严格遵循如下准则:
|
|
88
88
|
{H}
|
|
89
89
|
|
|
90
90
|
用户请求:
|
|
91
91
|
{I}"""if H else I
|
|
92
|
-
C=A.context if C is _A else C;L=await A._handle_stream('○ bot',A._stream_generate(B,C),style=D,markdown=_B,context=C);A.spinner.stop();A.
|
|
93
|
-
def
|
|
92
|
+
C=A.context if C is _A else C;L=await A._handle_stream('○ bot',A._stream_generate(B,C),style=D,markdown=_B,context=C);A.spinner.stop();A.u(spinner='',status='等待输入',tokens=A.token_count);A.app.layout.focus(A.input_box);return L
|
|
93
|
+
def s(A,sender,message,style='green',markdown=_C):D=markdown;C=style;B=message;E=Markdown(B)if D else Text(B,style=C);F=render_panel(sender,E,C,D);A.log_control.append_text(F);A.app.invalidate()
|
|
94
94
|
async def _stream_generate(A,prompt,context=_A):
|
|
95
95
|
B=A.agent.astream({_G:[HumanMessage(content=prompt)]},config={_I:{_J:A.thread_id}},stream_mode=[_G,_K,_L],context=context);A.cancel_event=asyncio.Event()
|
|
96
96
|
async for C in astream_handler(B,interrupt_tools=A.interrupt_tools,tool_mode=A.toolcall_mode):
|
|
@@ -143,7 +143,7 @@ class LiveChatUI:
|
|
|
143
143
|
D.spinner.stop();j=[];p=B['interrupt_id']
|
|
144
144
|
for d in B[m][m]['action_requests']:w=d[P];x=d[Q];y=d[_D];q=await D._handle_human_interrupt(message=f" 允许执行当前函数么? ",options=[{h:'是的,允许当前函数执行',_D:''},{h:'是的,总是允许执行,当前对话过程中不再提示',_D:''},{h:'不, 不允许当前函数执行',_D:''}]);k=['approve',_E,'reject'][q];D.toolcall_mode=_E if k==_E else'manual';j.append({I:k})
|
|
145
145
|
D.spinner.start();await D._handle_stream(a,D._resume_generate(p,j,i),style=b,markdown=c,items=A,context=i);break
|
|
146
|
-
r=Group(*[A[0]for A in A]);D.log_control.update_last(render_panel(a,r,b,c));D.app.invalidate();await asyncio.sleep(.03);s='';D.
|
|
146
|
+
r=Group(*[A[0]for A in A]);D.log_control.update_last(render_panel(a,r,b,c));D.app.invalidate();await asyncio.sleep(.03);s='';D.u(spinner=s,status='正在生成 ...',tokens=D.token_count)
|
|
147
147
|
return R
|
|
148
148
|
async def _handle_human_interrupt(A,message,options):
|
|
149
149
|
E=asyncio.get_event_loop();C=E.create_future();D=A.app.key_bindings
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
_P='custom'
|
|
2
|
+
_O='updates'
|
|
3
|
+
_N='thread_id'
|
|
4
|
+
_M='channels'
|
|
5
|
+
_L='heartbeat'
|
|
6
|
+
_K='class:spinner'
|
|
4
7
|
_J='class:status'
|
|
5
8
|
_I='messages'
|
|
6
9
|
_H='configurable'
|
|
@@ -48,7 +51,7 @@ from src.managers.manager_agent import AgentManager
|
|
|
48
51
|
from langchain_core.messages import HumanMessage
|
|
49
52
|
from langgraph.types import Command
|
|
50
53
|
from dotenv import find_dotenv,load_dotenv
|
|
51
|
-
|
|
54
|
+
k=load_dotenv(find_dotenv())
|
|
52
55
|
class CommandCompleter(Completer):
|
|
53
56
|
def __init__(A,commands,agents):A.path_completer=PathCompleter(expanduser=_B);A.commands=commands;A.agents=agents
|
|
54
57
|
def get_completions(G,document,complete_event):
|
|
@@ -68,9 +71,9 @@ class CommandCompleter(Completer):
|
|
|
68
71
|
elif A.startswith('@'):
|
|
69
72
|
for L in G.agents:yield Completion(f"{L}",start_position=-len(A))
|
|
70
73
|
class LiveChatUI:
|
|
71
|
-
def __init__(A,agent=_A,saver=_A,workspace=_A,**C):G='agents';F='.autodev';B='#afafff';A.agent=agent;A.saver=saver;A.kwargs=C;A.workspace=re.sub('^(\\/Users\\/[^/]+|\\/home\\/[^/]+)','~',workspace);A.thread_id=1;A.token_count=0;A.mode=_F;A.context=C.get('context',_A);A.instruction_manager=C.get('instruction_manager',_A);A.spinner=Spinner('ball');A.max_input_lines=10;A.command_manager=CommandManager(A,workspace=A.workspace);A.command_descriptions=A.command_manager.description_();H=Path.home()/F/G;I=Path(A.workspace).expanduser()/F/G;A.user_agent_manager=AgentManager(H);A.proj_agent_manager=AgentManager(I);A.agent_descriptions={**A.user_agent_manager.descriptions_(user=_B),**A.proj_agent_manager.descriptions_(user=_C)};A.COMMANDS=list(A.command_descriptions.keys());A.AGENTS=list(A.agent_descriptions.keys());A.COMMAND_META={**A.command_descriptions,**A.agent_descriptions};A.suggestions=[];A.selected_index=0;A.suggestions_box=HSplit(children=[],height=5);A.max_input_lines=10;A.cancel_event=_A;A.logo_label=Label(LOGO.format(A.workspace),style='class:logo');A.begin_items=[A.logo_label];A.begin_area=HSplit([*A.begin_items],padding=1);A.log_control=ScrollableFormattedLogControl();A.output_area=Window(content=A.log_control,wrap_lines=_B,always_hide_cursor=_C,height=D(weight=1));A.status_label=FormattedTextControl(text=[(_J,' 状态: 等待输入 | Tokens: 0 (⌥ + ⏎ 换行 Esc 中断 ctrl + c 退出)')]);
|
|
72
|
-
def
|
|
73
|
-
def
|
|
74
|
+
def __init__(A,agent=_A,saver=_A,workspace=_A,**C):G='agents';F='.autodev';B='#afafff';A.agent=agent;A.saver=saver;A.kwargs=C;A.workspace=re.sub('^(\\/Users\\/[^/]+|\\/home\\/[^/]+)','~',workspace);A.thread_id=1;A.token_count=0;A.mode=_F;A.context=C.get('context',_A);A.instruction_manager=C.get('instruction_manager',_A);A.spinner=Spinner('ball');A.max_input_lines=10;A.command_manager=CommandManager(A,workspace=A.workspace);A.command_descriptions=A.command_manager.description_();H=Path.home()/F/G;I=Path(A.workspace).expanduser()/F/G;A.user_agent_manager=AgentManager(H);A.proj_agent_manager=AgentManager(I);A.agent_descriptions={**A.user_agent_manager.descriptions_(user=_B),**A.proj_agent_manager.descriptions_(user=_C)};A.COMMANDS=list(A.command_descriptions.keys());A.AGENTS=list(A.agent_descriptions.keys());A.COMMAND_META={**A.command_descriptions,**A.agent_descriptions};A.suggestions=[];A.selected_index=0;A.suggestions_box=HSplit(children=[],height=5);A.max_input_lines=10;A.cancel_event=_A;A.logo_label=Label(LOGO.format(A.workspace),style='class:logo');A.begin_items=[A.logo_label];A.begin_area=HSplit([*A.begin_items],padding=1);A.log_control=ScrollableFormattedLogControl();A.output_area=Window(content=A.log_control,wrap_lines=_B,always_hide_cursor=_C,height=D(weight=1));A.status_label=FormattedTextControl(text=[(_J,' 状态: 等待输入 | Tokens: 0 (⌥ + ⏎ 换行 Esc 中断 ctrl + c 退出)')]);A.spinner_control=FormattedTextControl(text=[(_K,f"{A.spinner.current_frame()}")],show_cursor=_C);J=FormattedTextControl(text=[(_G,f"mode: {A.mode}")]);A.status_bar=VSplit([Window(A.spinner_control,width=D(weight=5),dont_extend_width=_B,dont_extend_height=_B,height=D(weight=1)),Window(A.status_label,width=D(weight=85),height=D(weight=1)),Window(J,width=D(weight=10),dont_extend_width=_B,dont_extend_height=_B,height=D(weight=1))],width=D(weight=100));A.input_box=TextArea(height=1,prompt='> ',multiline=_B,wrap_lines=_B,scrollbar=_B,completer=CommandCompleter(A.COMMANDS,A.AGENTS),complete_while_typing=_B,style='class:input_box');A.input_box.buffer.on_text_changed+=lambda _:A.update_suggestions();A.input_box.buffer.on_text_changed+=lambda _:A.update_input_area_height(k);A.kb=KeyBindings();A.o();K=Frame(body=A.input_box,style='class:frame');A.input_items=[A.status_bar,K];A.input_area=HSplit([*A.input_items],padding=0);A.interact_items=[];A.interact_area=HSplit([*A.interact_items],padding=1);L=os.getenv('DEFAULT_MODEL','kimi-k2-0711-preview');E=_G;A.footer=VSplit([Window(FormattedTextControl([(E,f"{A.workspace} (main) ")]),width=D(weight=50)),Window(FormattedTextControl([(E,f"MCP: (0/0) ")]),width=D(weight=20)),Window(FormattedTextControl([(E,'Env: (local) ')]),width=D(weight=20)),Window(FormattedTextControl([(E,f"Model: {L} ")]),wrap_lines=_B,dont_extend_width=_C,always_hide_cursor=_B,width=D(weight=10))],width=D(weight=100),height=1);A.suggest_items=[A.footer];A.suggest_area=HSplit([*A.suggest_items],padding=0);A.logo_area=DynamicContainer(lambda:A.begin_area);A.display_container=DynamicContainer(lambda:A.output_area);A.input_container=DynamicContainer(lambda:A.input_area);A.status_area=DynamicContainer(lambda:A.suggest_area);A.layout=Layout(HSplit([A.logo_area,A.output_area,A.interact_area,A.input_container,A.status_area],padding=0),focused_element=A.input_box);A.style=Style.from_dict({'logo':B,'output':B,'input_box':B,'status':B,'frame.border':B,'suggestions':B,'footer':B,'suggestion.label':B,'suggestion.desc':'#5f5f5f','spinner':B,'suggestion.selected':'bold #00afff'});A.app=Application(layout=A.layout,key_bindings=A.kb,style=A.style,full_screen=_B,mouse_support=_B);A.app.input_area=A.input_box;A.app.kb=A.kb;A.interrupt_tools=C.get('interrupt_tools',[]);A.toolcall_mode='manul'
|
|
75
|
+
def p(A,role='user',spinner='●',status='',tokens=0):A.status_label.text=[(_J,f" 状态: {status} | ({role}) | Tokens: {tokens} (esc + ⏎ 换行 按两次 esc 中断 ctrl + c 退出)")];A.spinner_control.text=[(_K,f"{A.spinner.current_frame()}")];A.app.invalidate()
|
|
76
|
+
def m(A,workspace=_A,mcp_status=_A,sandbox_status=_A,model_status=_A):
|
|
74
77
|
F=model_status;E=sandbox_status;D=mcp_status;C=workspace;B=_G
|
|
75
78
|
if C is not _A:A._footer_workspace.text=[(B,f"{C}(main) ")]
|
|
76
79
|
if D is not _A:A._footer_context.text=[(B,f"{D} ")]
|
|
@@ -96,7 +99,7 @@ class LiveChatUI:
|
|
|
96
99
|
for(E,B)in enumerate(D):
|
|
97
100
|
A.suggestions.append({_E:B,_D:A.COMMAND_META.get(B,'')})
|
|
98
101
|
if B==C:A.selected_index=E
|
|
99
|
-
A.suggestions_box.children=A.
|
|
102
|
+
A.suggestions_box.children=A.l()
|
|
100
103
|
if A.suggestions_box not in A.suggest_area.children:A.suggest_area.children.insert(0,A.suggestions_box)
|
|
101
104
|
else:A.clear_suggestions()
|
|
102
105
|
A.app.invalidate()
|
|
@@ -104,7 +107,7 @@ class LiveChatUI:
|
|
|
104
107
|
A.suggestions=[];A.selected_index=0;A.suggestions_box.children=[]
|
|
105
108
|
if A.suggestions_box in A.suggest_area.children:A.suggest_area.children.remove(A.suggestions_box)
|
|
106
109
|
A.app.invalidate()
|
|
107
|
-
def
|
|
110
|
+
def l(A):
|
|
108
111
|
F='reverse';C=[];G=len(A.suggestions)
|
|
109
112
|
if not G:return[]
|
|
110
113
|
H=0;I=len(A.suggestions)
|
|
@@ -113,7 +116,7 @@ class LiveChatUI:
|
|
|
113
116
|
def clear(A):A.log_control.clear();A.input_box.text='';B=render_info(LOGO.format(A.workspace),style='light_stell_blue',markdown=_C);A.log_control.append_text(B);A.app.invalidate();A.app.layout.focus(A.input_box)
|
|
114
117
|
async def updater(A):await A.spinner.run(A.app)
|
|
115
118
|
async def run_async(A):await asyncio.gather(A.app.run_async(),A.updater())
|
|
116
|
-
def
|
|
119
|
+
def o(A):
|
|
117
120
|
D='enter';C='escape'
|
|
118
121
|
@A.kb.add('@')
|
|
119
122
|
def B(event):A=event;A.app.current_buffer.insert_text('@');A.app.current_buffer.start_completion(select_first=_B)
|
|
@@ -135,36 +138,39 @@ class LiveChatUI:
|
|
|
135
138
|
@A.kb.add(C,C)
|
|
136
139
|
def B(event):
|
|
137
140
|
if A.cancel_event:A.cancel_event.set()
|
|
141
|
+
async def shutdown(A):
|
|
142
|
+
print('\nShutting down all services...');B=A.kwargs.get('bus',_A);C=A.kwargs.get('cron',_A);D=A.kwargs.get(_L,_A);E=A.kwargs.get(_M,_A);D.stop();await C.stop();await E.stop_all();B.stop();F=asyncio.get_running_loop()
|
|
143
|
+
for G in asyncio.all_tasks(F):G.cancel()
|
|
138
144
|
async def alisten(A):
|
|
139
|
-
B=A.kwargs.get('bus',_A);C=A.kwargs.get('cron',_A);D=A.kwargs.get(
|
|
145
|
+
B=A.kwargs.get('bus',_A);C=A.kwargs.get('cron',_A);D=A.kwargs.get(_L,_A);E=A.kwargs.get(_M,_A);B.subscribe_inbound('feishu',partial(A._process_message,bus=B,cron=C))
|
|
140
146
|
try:await C.start();await D.start();await asyncio.gather(B.dispatch_inbound(),E.start_all(),A.app.run_async(),A.updater())
|
|
141
|
-
except KeyboardInterrupt:
|
|
147
|
+
except KeyboardInterrupt:await A.shutdown()
|
|
142
148
|
async def _process_message(B,msg,bus,cron):A=msg;D=f"{A.channel}:{A.chat_id}";E={_H:{'session_id':D}};B.context=AgentContext(working_directory=os.getcwd(),sandbox=_A,channel=A.channel,chat_id=A.chat_id,cron_service=cron,workspace=os.getcwd());C=await B._handle_submit(A.content,B.context);C=OutboundMessage(channel=A.channel,chat_id=A.chat_id,content=C,metadata=A.metadata or{});await bus.publish_outbound(C)
|
|
143
149
|
async def _handle_submit(A,text,context=_A):
|
|
144
150
|
D='light_steel_blue';C=context;B=text
|
|
145
151
|
if A.logo_label in A.begin_items:A.begin_items.remove(A.logo_label);A.begin_area.children=list(A.begin_items);J=render_info(LOGO.format(A.workspace),style=D,markdown=_C);A.log_control.append_text(J);A.app.invalidate()
|
|
146
|
-
if B.strip()in['quit','exit','q']:get_app().exit();return
|
|
152
|
+
if B.strip()in['quit','exit','q']:get_app().exit();await A.shutdown();return
|
|
147
153
|
if B.strip()in['/clear','clear']:A.clear();return
|
|
148
|
-
A.spinner.start();A.
|
|
154
|
+
A.spinner.start();A.n('● user',f"● {B}",style='light_salmon3');await asyncio.sleep(.05)
|
|
149
155
|
if B.strip()in['/commands']and A.instruction_manager:
|
|
150
156
|
E=[]
|
|
151
157
|
for F in A.instruction_manager.list_instructions():E.append(f"/{F.name}: - {F.settings[_D]}")
|
|
152
|
-
K='\n'.join(E);A.
|
|
158
|
+
K='\n'.join(E);A.n('● bot',K,style=D,markdown=_B);return
|
|
153
159
|
if A.instruction_manager:G=A.instruction_manager.parse(B);H,I=G['executed_instruction'],G['message'];B=f"""
|
|
154
160
|
[注意]: 执行用户请求必须严格遵循如下准则:
|
|
155
161
|
{H}
|
|
156
162
|
|
|
157
163
|
用户请求:
|
|
158
164
|
{I}"""if H else I
|
|
159
|
-
C=A.context if C is _A else C;L=await A._handle_stream('○ bot',A._stream_generate(B,C),style=D,markdown=_B,context=C);A.spinner.stop();A.
|
|
160
|
-
def
|
|
165
|
+
C=A.context if C is _A else C;L=await A._handle_stream('○ bot',A._stream_generate(B,C),style=D,markdown=_B,context=C);A.spinner.stop();A.p(spinner='',status='等待输入',tokens=A.token_count);A.app.layout.focus(A.input_box);return L
|
|
166
|
+
def n(A,sender,message,style='green',markdown=_C):D=markdown;C=style;B=message;E=Markdown(B)if D else Text(B,style=C);F=render_panel(sender,E,C,D);A.log_control.append_text(F);A.app.invalidate()
|
|
161
167
|
async def _stream_generate(A,prompt,context=_A):
|
|
162
|
-
B=A.agent.astream({_I:[HumanMessage(content=prompt)]},config={_H:{
|
|
168
|
+
B=A.agent.astream({_I:[HumanMessage(content=prompt)]},config={_H:{_N:A.thread_id}},stream_mode=[_I,_O,_P],context=context);A.cancel_event=asyncio.Event()
|
|
163
169
|
async for C in astream_handler(B,interrupt_tools=A.interrupt_tools,tool_mode=A.toolcall_mode):
|
|
164
170
|
if A.cancel_event and A.cancel_event.is_set():A.cancel_event.clear();A.cancel_event=_A;return
|
|
165
171
|
yield C
|
|
166
172
|
async def _resume_generate(A,interrupt_id,decisions,context=_A):
|
|
167
|
-
B=A.agent.astream(Command(resume={interrupt_id:{'decisions':decisions}}),config={_H:{
|
|
173
|
+
B=A.agent.astream(Command(resume={interrupt_id:{'decisions':decisions}}),config={_H:{_N:A.thread_id}},stream_mode=[_I,_O,_P],context=context);A.cancel_event=asyncio.Event()
|
|
168
174
|
async for C in astream_handler(B,interrupt_tools=A.interrupt_tools,tool_mode=A.toolcall_mode):
|
|
169
175
|
if A.cancel_event and A.cancel_event.is_set():A.cancel_event.clear();A.cancel_event=_A
|
|
170
176
|
yield C
|
|
@@ -210,7 +216,7 @@ class LiveChatUI:
|
|
|
210
216
|
D.spinner.stop();i=[];o=B['interrupt_id']
|
|
211
217
|
for d in B[l][l]['action_requests']:v=d[P];w=d[Q];x=d[_D];p=await D._handle_human_interrupt(message=f" 允许执行当前函数么? ",options=[{_E:'是的,允许当前函数执行',_D:''},{_E:'是的,总是允许执行,当前对话过程中不再提示',_D:''},{_E:'不, 不允许当前函数执行',_D:''}]);j=['approve',_F,'reject'][p];D.toolcall_mode=_F if j==_F else'manual';i.append({I:j})
|
|
212
218
|
D.spinner.start();await D._handle_stream(a,D._resume_generate(o,i,h),style=b,markdown=c,items=A,context=h);break
|
|
213
|
-
q=Group(*[A[0]for A in A]);D.log_control.update_last(render_panel(a,q,b,c));D.app.invalidate();await asyncio.sleep(.03);r='';D.
|
|
219
|
+
q=Group(*[A[0]for A in A]);D.log_control.update_last(render_panel(a,q,b,c));D.app.invalidate();await asyncio.sleep(.03);r='';D.p(spinner=r,status='正在生成 ...',tokens=D.token_count)
|
|
214
220
|
return R
|
|
215
221
|
async def _handle_human_interrupt(A,message,options):
|
|
216
222
|
E=asyncio.get_event_loop();C=E.create_future();D=A.app.key_bindings
|
|
@@ -19,20 +19,20 @@ from prompt_toolkit.layout import Layout,HSplit
|
|
|
19
19
|
from prompt_toolkit.styles import Style
|
|
20
20
|
from prompt_toolkit import Application
|
|
21
21
|
class InterruptSelector:
|
|
22
|
-
def __init__(A,description,options,callback):B=description;A.options=options;A.description=B;A.selected_index=0;A.callback=callback;A.rows=A.
|
|
23
|
-
def
|
|
24
|
-
def
|
|
22
|
+
def __init__(A,description,options,callback):B=description;A.options=options;A.description=B;A.selected_index=0;A.callback=callback;A.rows=A.bm();A.list_container=HSplit(A.rows,padding=0);A.markdown=A.bi(B);C=Window(content=A.markdown.content,height=A.markdown.height,dont_extend_height=_B,style='class:desc');D=Frame(body=C);A.container=HSplit([C,A.list_container]);A.kb=KeyBindings();A.bk()
|
|
23
|
+
def bi(C,content):A=StringIO();B=Console(file=A,width=80,force_terminal=_B,color_system='truecolor');B.print(Align.left(Markdown(content)),justify='left');return Window(content=FormattedTextControl(ANSI(A.getvalue())),height=D(min=1))
|
|
24
|
+
def bm(A):
|
|
25
25
|
E='class:suggestion.selected';B=[]
|
|
26
26
|
for(F,C)in enumerate(A.options):D=F==A.selected_index;G='> 'if D else' ';H=E if D else'class:suggestion.label';I='class:suggestion.desc';J=VSplit([Window(FormattedTextControl([(E,G)]),width=2),Window(FormattedTextControl([(H,C[_A])]),width=60),Window(FormattedTextControl([(I,C[_C])]),wrap_lines=_B,dont_extend_width=False,always_hide_cursor=_B)],height=1);B.append(J)
|
|
27
27
|
return B
|
|
28
|
-
def
|
|
29
|
-
def
|
|
28
|
+
def bl(A):A.rows=A.bm();A.list_container.children=A.rows;get_app().invalidate()
|
|
29
|
+
def bk(A):
|
|
30
30
|
@A.kb.add('up')
|
|
31
31
|
def B(event):
|
|
32
|
-
if A.selected_index>0:A.selected_index-=1;A.
|
|
32
|
+
if A.selected_index>0:A.selected_index-=1;A.bl()
|
|
33
33
|
@A.kb.add('down')
|
|
34
34
|
def C(event):
|
|
35
|
-
if A.selected_index<len(A.options)-1:A.selected_index+=1;A.
|
|
35
|
+
if A.selected_index<len(A.options)-1:A.selected_index+=1;A.bl()
|
|
36
36
|
@A.kb.add('enter')
|
|
37
37
|
def D(event):A.callback(A.selected_index)
|
|
38
38
|
async def demo():
|