@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.
@@ -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 dk(model):
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.df(B,N)for B in B];A.trigger=I;G=I
48
- else:J=A.df(B,N);A.trigger=J;G=[J]
49
- A._trigger_conditions=G;A.keep=A.df(F,'keep')
50
- if H is count_tokens_approximately:A.token_counter=dk(A.model)
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.dn()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 do(B,state):A=state.get(_G,_F);return bool(A)
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.da(B);E=A.token_counter(B);F=A.do(C);G=A.db(B,E)
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.dh(B)
60
+ D=A.db(B)
61
61
  if D<=0:return
62
- H,I=A.di(B,D);J=A.de(H);K=A.dg(J);return{_B:[RemoveMessage(id=REMOVE_ALL_MESSAGES),*K,*I],_G:_F}
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.da(B);E=A.token_counter(B);F=A.do(C);G=A.db(B,E)
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.dh(B)
67
+ D=A.db(B)
68
68
  if D<=0:return
69
- H,I=A.di(B,D);J=await A._acreate_summary(H);K=A.dg(J);return{_B:[RemoveMessage(id=REMOVE_ALL_MESSAGES),*K,*I],_G:_F}
70
- def dj(B,messages,threshold):
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 db(A,messages,total_tokens):
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.dj(E,C):return _C
80
+ if B==_E and A.df(E,C):return _C
81
81
  if B==_D:
82
- G=A.dn()
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.dj(E,D):return _C
87
+ if A.df(E,D):return _C
88
88
  return _F
89
- def dh(A,messages):
89
+ def db(A,messages):
90
90
  B=messages;D,E=A.keep
91
91
  if D in{_E,_D}:
92
- C=A.dd(B)
92
+ C=A.dg(B)
93
93
  if C is not _A:return C
94
- return A.dl(B,_DEFAULT_MESSAGES_TO_KEEP)
95
- return A.dl(B,cast('int',E))
96
- def dd(C,messages):
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.dn()
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.dc(A,B)
118
- def dn(C):
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 df(context,parameter_name):
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 dg(summary):return[HumanMessage(content=f"Here is a summary of the conversation to date:\n\n{summary}",additional_kwargs={'lc_source':'summarization'})]
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 da(messages):
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 di(conversation_messages,cutoff_index):B=cutoff_index;A=conversation_messages;C=A[:B];D=A[B:];return C,D
142
- def dl(C,messages,messages_to_keep):
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.dc(A,D)
145
+ D=len(A)-B;return C.di(A,D)
146
146
  @staticmethod
147
- def dc(messages,cutoff_index):
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 de(A,messages_to_sumarize):
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.cd(C):return ContentType.JSON
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.cd(A):return ContentType.JSON
23
+ if B.bz(A):return ContentType.JSON
24
24
  if B.bx(A):return ContentType.ERROR
25
- if B.bv(A):return ContentType.MARKDOWN
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.bz,ContentType.ERROR:A.cb,ContentType.JSON:A.bw,ContentType.MARKDOWN:A.ca,ContentType.TEXT:A.bu};F=E.get(C,A.bu);G=F(name,B,max_length);return FormattedResult(content_type=C,elements=G,success=D)
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 cd(B,content):
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 bv(C,content):A=content;B=['```','**','##','- **'];return A.startswith('#')or any(B in A for B in B)
39
- def bz(B,name,content,max_length):A='green';C=B.cc(content,max_length);return[Panel(Text(C,style=A),title=f"📤 {name} ✓",border_style=A)]
40
- def cb(B,name,content,max_length):A='red';C=B.cc(content,max_length);return[Panel(Text(C,style=A),title=f"📤 {name} ✗",border_style=A)]
41
- def bw(B,name,content,max_length):
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.cc(C,D);return[Text(f"📤 {name} ✓",style=_B),Syntax(C,'json',theme='monokai',line_numbers=_A)]
45
- except(json.JSONDecodeError,ValueError):return B.bu(name,A,D)
46
- def ca(A,name,content,max_length):B=A.cc(content,max_length);return[Panel(Markdown(B),title=f"📤 {name}",border_style='cyan dim')]
47
- def bu(A,name,content,max_length):B=A.cc(content,max_length);return[Text(f"📤 {name}:",style=_B),Text(f" {B}",style='dim')]
48
- def cc(A,content,max_length):return truncate(content,max_length)
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 cf(F,T):
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 cf(E,T):
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 cf(chunk,stream_parser):
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 ce(chunk):
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 cj(A,E,J,S):
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 ck(A.tool_calls,E,J):
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 ci(A,E,J):
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 cj(A,E,J,S):
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 ck(A.tool_calls,E,J):
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 ci(A,E,J):
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 cj(chunk,emitter,tracker,stream_parser):
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 ck(tool_calls,emitter,tracker):
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 ci(chunk,emitter,tracker):
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 dx(since,until,tag=_A,user_id=_A,session_id=_A):
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 dw(period):
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=dx(G,H,tag,user_id,session_id);A=L.tracker._conn.cursor();A.execute(f"""
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=dw(I);A.execute(f"""
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.dv();A._task=asyncio.create_task(A._writer_loop())
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 dv(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)')
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.dt(A);A.clear()
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.dt(A);A.clear()
46
- def dt(D,records):
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 du(A):
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.du(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.du(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}
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
- u=load_dotenv(find_dotenv())
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(u);A.kb=KeyBindings();A.s();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 r(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 q(A,workspace=_A,mcp_status=_A,sandbox_status=_A,model_status=_A):
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 s(A):
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.t('● user',f"● {B}",style='light_salmon3');await asyncio.sleep(.05)
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.t('● bot',K,style=D,markdown=_B);return
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.r(spinner='',status='等待输入',tokens=A.token_count);A.app.layout.focus(A.input_box);return L
93
- def t(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()
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.r(spinner=s,status='正在生成 ...',tokens=D.token_count)
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
- _M='custom'
2
- _L='updates'
3
- _K='thread_id'
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
- p=load_dotenv(find_dotenv())
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 退出)')]);J=FormattedTextControl(text=[('class:spinner',f"{A.spinner.current_frame()}")],show_cursor=_C);K=FormattedTextControl(text=[(_G,f"mode: {A.mode}")]);A.status_bar=VSplit([Window(J,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(K,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(p);A.kb=KeyBindings();A.n();L=Frame(body=A.input_box,style='class:frame');A.input_items=[A.status_bar,L];A.input_area=HSplit([*A.input_items],padding=0);A.interact_items=[];A.interact_area=HSplit([*A.interact_items],padding=1);M=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: {M} ")]),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'
72
- def l(A,role='user',spinner='●',status='',tokens=0):A.status_label.text=[(_J,f" 状态: {status} | ({role}) | Tokens: {tokens} (esc + ⏎ 换行 按两次 esc 中断 ctrl + c 退出)")];A.app.invalidate()
73
- def k(A,workspace=_A,mcp_status=_A,sandbox_status=_A,model_status=_A):
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.m()
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 m(A):
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 n(A):
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('heartbeat',_A);E=A.kwargs.get('channels',_A);B.subscribe_inbound('feishu',partial(A._process_message,bus=B,cron=C))
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:print('\nShutting down ...');D.stop();await C.stop();await E.stop_call();B.stop()
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.o('● user',f"● {B}",style='light_salmon3');await asyncio.sleep(.05)
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.o('● bot',K,style=D,markdown=_B);return
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.l(spinner='',status='等待输入',tokens=A.token_count);A.app.layout.focus(A.input_box);return L
160
- def o(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()
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:{_K:A.thread_id}},stream_mode=[_I,_L,_M],context=context);A.cancel_event=asyncio.Event()
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:{_K:A.thread_id}},stream_mode=[_I,_L,_M],context=context);A.cancel_event=asyncio.Event()
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.l(spinner=r,status='正在生成 ...',tokens=D.token_count)
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.bk();A.list_container=HSplit(A.rows,padding=0);A.markdown=A.bl(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.bn()
23
- def bl(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 bk(A):
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 bo(A):A.rows=A.bk();A.list_container.children=A.rows;get_app().invalidate()
29
- def bn(A):
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.bo()
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.bo()
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():