@brandon_9527/tcode 1.0.7 → 1.0.8
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 +3 -1
- package/dist/python-src/entry.py +14 -0
- package/dist/python-src/src/claw/channels/feishu.py +8 -8
- package/dist/python-src/src/core/deepagents.py +5 -5
- 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/managers/sandbox.py +3 -3
- package/dist/python-src/src/middlewares/subagents.py +4 -4
- package/dist/python-src/src/middlewares/summary.py +37 -37
- package/dist/python-src/src/stream/formatter.py +19 -19
- 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 +6 -6
- package/dist/python-src/src/tui/chatui.py +10 -10
- package/dist/python-src/src/tui/components/tlist.py +7 -7
- package/dist/python-src/src/tui/components/tscroll_panel.py +8 -8
- package/dist/python-src/src/tui/utils/trender.py +21 -21
- package/package.json +1 -1
package/dist/python-src/.env
CHANGED
package/dist/python-src/entry.py
CHANGED
|
@@ -29,6 +29,7 @@ warnings.filterwarnings("ignore", message="Pydantic serializer warnings")
|
|
|
29
29
|
|
|
30
30
|
import typer
|
|
31
31
|
import asyncio
|
|
32
|
+
import os
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
from main import (
|
|
@@ -50,7 +51,13 @@ def root(
|
|
|
50
51
|
prompt: str = typer.Option(None, "-p", "--prompt", help="用户输入请求内容"),
|
|
51
52
|
verbose: bool = typer.Option(False, "-v", "--verbose", help="显示agent执行详细过程"),
|
|
52
53
|
mode: str = typer.Option("team", "-m", "--mode", help="运行模式,single或team"),
|
|
54
|
+
# ====================== 新增:接收 max_tokens 参数 ======================
|
|
55
|
+
max_tokens: int = typer.Option(None, "--max-in-tokens", help="覆盖环境变量 MAX_TOKENS_BEFORE_SUMMARY"),
|
|
53
56
|
):
|
|
57
|
+
if max_tokens is not None:
|
|
58
|
+
os.environ["MAX_TOKENS_BEFORE_SUMMARY"] = str(max_tokens)
|
|
59
|
+
# print(f"✅ 已强制覆盖 MAX_TOKENS_BEFORE_SUMMARY = {max_tokens}")
|
|
60
|
+
|
|
54
61
|
if ctx.invoked_subcommand is None:
|
|
55
62
|
if not prompt:
|
|
56
63
|
if mode == "single":
|
|
@@ -67,7 +74,14 @@ def root(
|
|
|
67
74
|
@interact_app.command("interact")
|
|
68
75
|
def interact_mode(
|
|
69
76
|
verbose: bool = typer.Option(False, "-v", "--verbose", help="显示agent执行详细过程"),
|
|
77
|
+
# ====================== 交互模式也支持覆盖 ======================
|
|
78
|
+
max_tokens: int = typer.Option(None, "--max-in-tokens", help="覆盖环境变量 MAX_TOKENS_BEFORE_SUMMARY"),
|
|
70
79
|
):
|
|
80
|
+
if max_tokens is not None:
|
|
81
|
+
print(f"交互模式:当前 MAX_TOKENS_BEFORE_SUMMARY = {os.getenv('MAX_TOKENS_BEFORE_SUMMARY')}")
|
|
82
|
+
os.environ["MAX_TOKENS_BEFORE_SUMMARY"] = str(max_tokens)
|
|
83
|
+
print(f"✅ 交互模式:已覆盖 MAX_TOKENS_BEFORE_SUMMARY = {max_tokens}")
|
|
84
|
+
|
|
71
85
|
# 执行交互模式
|
|
72
86
|
asyncio.run(teminal_chat(verbose))
|
|
73
87
|
|
|
@@ -21,7 +21,7 @@ class FeishuChannel(BaseChannel):
|
|
|
21
21
|
async def start(A):
|
|
22
22
|
if not FEISHU_AVAILABLE:logger.error('Feishu SDK not installed. Run: pip install lark-oapi');return
|
|
23
23
|
if not A.config.app_id or not A.config.app_secret:logger.error('Feishu app_id and app_secret not configured');return
|
|
24
|
-
A._running=_C;A._loop=asyncio.get_running_loop();A._client=lark.Client.builder().app_id(A.config.app_id).app_secret(A.config.app_secret).log_level(lark.LogLevel.INFO).build();B=lark.EventDispatcherHandler.builder(A.config.encrypt_key or'',A.config.verification_token or'').register_p2_im_message_receive_v1(A.
|
|
24
|
+
A._running=_C;A._loop=asyncio.get_running_loop();A._client=lark.Client.builder().app_id(A.config.app_id).app_secret(A.config.app_secret).log_level(lark.LogLevel.INFO).build();B=lark.EventDispatcherHandler.builder(A.config.encrypt_key or'',A.config.verification_token or'').register_p2_im_message_receive_v1(A.dj).build();A._ws_client=lark.ws.Client(A.config.app_id,A.config.app_secret,event_handler=B,log_level=lark.LogLevel.INFO)
|
|
25
25
|
def C():
|
|
26
26
|
try:A._ws_client.start()
|
|
27
27
|
except Exception as B:logger.error(f"Feishu WebSocket error: {B}")
|
|
@@ -33,7 +33,7 @@ class FeishuChannel(BaseChannel):
|
|
|
33
33
|
try:A._ws_client.stop()
|
|
34
34
|
except Exception as B:logger.warning(f"Error stoppping WebSocket client: {B}")
|
|
35
35
|
logger.info('Feishu bot stopped')
|
|
36
|
-
def
|
|
36
|
+
def dg(D,message_id,emoji_type):
|
|
37
37
|
C=emoji_type;B=message_id
|
|
38
38
|
try:
|
|
39
39
|
E=CreateMessageReactionRequest.builder().message_id(B).request_body(CreateMessageReactionRequestBody.builder().reaction_type(Emoji.builder().emoji_type(C).build()).build()).build();A=D._client.im.v1.message_reaction.create(E)
|
|
@@ -42,19 +42,19 @@ class FeishuChannel(BaseChannel):
|
|
|
42
42
|
except Exception as F:logger.warning(f"Error adding reaction: {F}")
|
|
43
43
|
async def _add_reaction(A,message_id,emoji_type=_E):
|
|
44
44
|
if not A._client or not Emoji:return
|
|
45
|
-
B=asyncio.get_running_loop();await B.run_in_executor(_A,A.
|
|
45
|
+
B=asyncio.get_running_loop();await B.run_in_executor(_A,A.dg,message_id,emoji_type)
|
|
46
46
|
_TABLE_RE=re.compile('((?:^[ \\t]*\\|.+\\|[ \\t]*\\n)(?:^[ \\t]*\\|[-:\\s|]+\\|[ \\t]*\\n)(?:^[ \\t]*\\|.+\\|[ \\t]*\\n?)+)',re.MULTILINE)
|
|
47
47
|
@staticmethod
|
|
48
|
-
def
|
|
48
|
+
def di(table_text):
|
|
49
49
|
A=[A.strip()for A in table_text.strip().split('\n')if A.strip()]
|
|
50
50
|
if len(A)<3:return
|
|
51
51
|
B=lambda l:[A.strip()for A in l.strip('|').split('|')];C=B(A[0]);D=[B(A)for A in A[2:]];E=[{_B:'column','name':f"c{A}",'display_name':B,'width':'auto'}for(A,B)in enumerate(C)];return{_B:'table','page_size':len(D)+1,'columns':E,'rows':[{f"c{A}":B[A]if A<len(B)else''for A in range(len(C))}for B in D]}
|
|
52
|
-
def
|
|
52
|
+
def dh(G,content):
|
|
53
53
|
E='markdown';D='content';A=content;B,F=[],0
|
|
54
54
|
for C in G._TABLE_RE.finditer(A):
|
|
55
55
|
H=A[F:C.start()].strip()
|
|
56
56
|
if H:B.append({_B:E,D:H})
|
|
57
|
-
B.append(G.
|
|
57
|
+
B.append(G.di(C.group(1))or{_B:E,D:C.group(1)});F=C.end()
|
|
58
58
|
I=A[F:].strip()
|
|
59
59
|
if I:B.append({_B:E,D:I})
|
|
60
60
|
return B or[{_B:E,D:A}]
|
|
@@ -64,11 +64,11 @@ class FeishuChannel(BaseChannel):
|
|
|
64
64
|
try:
|
|
65
65
|
if A.chat_id.startswith('oc_'):D='chat_id'
|
|
66
66
|
else:D='open_id'
|
|
67
|
-
E=C.
|
|
67
|
+
E=C.dh(A.content);F={'config':{'wide_screen_mode':_C},'elements':E};G=json.dumps(F,ensure_ascii=_D);H=CreateMessageRequest.builder().receive_id_type(D).request_body(CreateMessageRequestBody.builder().receive_id(A.chat_id).msg_type('interactive').content(G).build()).build();B=C._client.im.v1.message.create(H)
|
|
68
68
|
if not B.success():logger.error(f"Failed to send Feishu message: code={B.code},msg={B.msg}, log_id={B.get_log_id()}")
|
|
69
69
|
else:logger.debug(f"Feishu message sent to {A.chat_id}")
|
|
70
70
|
except Exception as I:logger.error(f"Error sending Feishu message: {I}")
|
|
71
|
-
def
|
|
71
|
+
def dj(A,data):
|
|
72
72
|
if A._loop and A._loop.is_running():asyncio.run_coroutine_threadsafe(A._on_message(data),A._loop)
|
|
73
73
|
async def _on_message(A,data):
|
|
74
74
|
J='text'
|
|
@@ -22,9 +22,9 @@ import os
|
|
|
22
22
|
_=load_dotenv(find_dotenv())
|
|
23
23
|
def get_default_model(streaming=False):D='OPENAI_API_KEY';C='OPENAI_API_BASE';B='DEFAULT_MODEL';A=streaming;return ChatOpenAI(model_name=os.getenv(B),base_url=os.getenv(C),api_key=os.getenv(D),streaming=A,model_kwargs={'stream_options':{'include_usage':True}})if A else ChatOpenAI(model_name=os.getenv(B),base_url=os.getenv(C),api_key=os.getenv(D),streaming=A)
|
|
24
24
|
def create_deep_agent(model=_A,tools=_A,*,system_prompt=_A,middleware=(),subagents=_A,response_format=_A,context_schema=_A,checkpointer=_A,store=_A,interrupt_on=_A,debug=False,name=_A,cache=_A,recursion_limit=1000):
|
|
25
|
-
|
|
25
|
+
J='messages';I='tokens';H=subagents;G=middleware;F=system_prompt;E=tools;B=interrupt_on;A=model
|
|
26
26
|
if A is _A:A=get_default_model(streaming=True)
|
|
27
|
-
C=[TodoListMiddleware(),SubAgentMiddleware(default_model=A,default_tools=
|
|
28
|
-
if
|
|
29
|
-
if B is not _A:
|
|
30
|
-
return create_agent(model=A,system_prompt=
|
|
27
|
+
C=int(os.getenv('MAX_TOKENS_BEFORE_SUMMARY',170000));print(f"当前 MAX_TOKENS_BEFORE_SUMMARY = {C}");D=[TodoListMiddleware(),SubAgentMiddleware(default_model=A,default_tools=E,subagents=H if H is not _A else[],default_middleware=[TodoListMiddleware(),SummarizationMiddleware(model=A,trigger=(I,C),keep=(J,6)),PatchToolCallsMiddleware()],default_interrupt_on=B,general_purpose_agent=True),SummarizationMiddleware(model=A,trigger=(I,C),keep=(J,6)),PatchToolCallsMiddleware()]
|
|
28
|
+
if G:D.extend(G)
|
|
29
|
+
if B is not _A:D.append(HumanInTheLoopMiddleware(interrupt_on=B))
|
|
30
|
+
return create_agent(model=A,system_prompt=F+'\n\n'+BASE_AGENT_PROMPT if F else BASE_AGENT_PROMPT,tools=E,middleware=D,response_format=response_format,context_schema=context_schema,checkpointer=checkpointer,store=store,debug=debug,name=name,cache=cache).with_config({'recursion_limit':recursion_limit})
|
|
@@ -15,8 +15,8 @@ from pathlib import Path
|
|
|
15
15
|
import yaml,json,sys,re,os
|
|
16
16
|
sys.path.append('/Users/brandon/workspace/coder/autodev')
|
|
17
17
|
class AgentInfo:
|
|
18
|
-
def __init__(A,name,content,src_):B=content;A.name=name;A.original_content=B;A.src_=src_;A.meta,A.body=A.
|
|
19
|
-
def
|
|
18
|
+
def __init__(A,name,content,src_):B=content;A.name=name;A.original_content=B;A.src_=src_;A.meta,A.body=A.bv(B)
|
|
19
|
+
def bv(F,content):
|
|
20
20
|
B=content;C='^---\\n(.*?)\\n---\\n(.*)$';A=re.match(C,B,re.S)
|
|
21
21
|
if A:D=yaml.safe_load(A.group(1));E=A.group(2).strip();return D,E
|
|
22
22
|
else:return{},B.strip()
|
|
@@ -30,7 +30,7 @@ class AgentManager:
|
|
|
30
30
|
if A.project_agents_dir and not os.path.exists(A.project_agents_dir):os.makedirs(A.project_agents_dir)
|
|
31
31
|
if not os.path.exists(A.user_agents_dir):os.makedirs(A.user_agents_dir)
|
|
32
32
|
A.reload_agents()
|
|
33
|
-
def
|
|
33
|
+
def bw(O,dir_path,source):
|
|
34
34
|
F=source;C=dir_path;A={}
|
|
35
35
|
if not os.path.exists(C):return A
|
|
36
36
|
for(G,P,I)in os.walk(C):
|
|
@@ -47,8 +47,8 @@ class AgentManager:
|
|
|
47
47
|
except Exception as N:print(f"加载 {F} 来源 agent {B}/{E} 失败: {str(N)}")
|
|
48
48
|
return A
|
|
49
49
|
def reload_agents(A):
|
|
50
|
-
A.agents[_B].clear();A.agents[_C].clear();A.agents[_C]=A.
|
|
51
|
-
if A.project_agents_dir:A.agents[_B]=A.
|
|
50
|
+
A.agents[_B].clear();A.agents[_C].clear();A.agents[_C]=A.bw(A.user_agents_dir,_C)
|
|
51
|
+
if A.project_agents_dir:A.agents[_B]=A.bw(A.project_agents_dir,_B)
|
|
52
52
|
def bu(A,source,domain,name):
|
|
53
53
|
B=source
|
|
54
54
|
if B==_B:
|
|
@@ -39,7 +39,7 @@ class InstructionManager:
|
|
|
39
39
|
if A.project_commands_dir and not os.path.exists(A.project_commands_dir):os.makedirs(A.project_commands_dir)
|
|
40
40
|
if not os.path.exists(A.user_dir):os.makedirs(A.user_dir)
|
|
41
41
|
A.reload_instructions()
|
|
42
|
-
def
|
|
42
|
+
def by(O,dir_path,source):
|
|
43
43
|
F=source;C=dir_path;A={}
|
|
44
44
|
if not os.path.exists(C):return A
|
|
45
45
|
for(G,P,I)in os.walk(C):
|
|
@@ -54,9 +54,9 @@ class InstructionManager:
|
|
|
54
54
|
except Exception as N:print(f"从 {F} 加载命令 {B}/{E} 失败: {str(N)}")
|
|
55
55
|
return A
|
|
56
56
|
def reload_instructions(A):
|
|
57
|
-
A.instructions[_B].clear();A.instructions[_C].clear();A.instructions[_C]=A.
|
|
58
|
-
if A.project_commands_dir:A.instructions[_B]=A.
|
|
59
|
-
def
|
|
57
|
+
A.instructions[_B].clear();A.instructions[_C].clear();A.instructions[_C]=A.by(A.user_dir,_C)
|
|
58
|
+
if A.project_commands_dir:A.instructions[_B]=A.by(A.project_commands_dir,_B)
|
|
59
|
+
def bx(A,source,domain,name):
|
|
60
60
|
B=source
|
|
61
61
|
if B==_B:
|
|
62
62
|
if not A.project_commands_dir:raise ValueError('项目目录未初始化,无法操作project来源的指令')
|
|
@@ -79,7 +79,7 @@ class InstructionManager:
|
|
|
79
79
|
def add_instruction(B,domain,name,settings,content,source=_B):
|
|
80
80
|
E=source;D=name;C=domain
|
|
81
81
|
if B.exists(C,D,E):raise ValueError(f"指令 '{E}/{C}/{D}' 已存在")
|
|
82
|
-
G=B.
|
|
82
|
+
G=B.bx(E,C,D);os.makedirs(os.path.dirname(G),exist_ok=_G)
|
|
83
83
|
with open(G,'w',encoding=_I)as A:
|
|
84
84
|
A.write('---\n')
|
|
85
85
|
for(H,F)in settings.items():
|
|
@@ -98,7 +98,7 @@ class InstructionManager:
|
|
|
98
98
|
D=C.instructions[G][A][B]
|
|
99
99
|
if I is not _A:D.settings=I
|
|
100
100
|
if J is not _A:D.original_content=J
|
|
101
|
-
K=C.
|
|
101
|
+
K=C.bx(G,A,B);os.makedirs(os.path.dirname(K),exist_ok=_G)
|
|
102
102
|
with open(K,'w',encoding=_I)as E:
|
|
103
103
|
E.write('---\n')
|
|
104
104
|
for(L,H)in D.settings.items():
|
|
@@ -114,7 +114,7 @@ class InstructionManager:
|
|
|
114
114
|
else:
|
|
115
115
|
G,E=C.get_instruction_with_source(A,B)
|
|
116
116
|
if not E:raise ValueError(f"指令 '{A}/{B}' 不存在于任何来源")
|
|
117
|
-
F=C.
|
|
117
|
+
F=C.bx(E,A,B)
|
|
118
118
|
if os.path.exists(F):os.remove(F)
|
|
119
119
|
C.reload_instructions()
|
|
120
120
|
def load_all_instructions(A,domain=_D,source=_A):
|
|
@@ -21,14 +21,14 @@ sys.path.append(PROJ_PATH)
|
|
|
21
21
|
logger=logging.getLogger(__file__)
|
|
22
22
|
class Container:
|
|
23
23
|
def __init__(A,user_id=_C,workspace_path=_B,dockerfile_path='.',image_name=_J,container_basename='sandbox'):A.client=docker.from_env();A.container_basename=container_basename;A.workspace_path=workspace_path;A.dockerfile_path=dockerfile_path;A.image_name=image_name;A.sock=_B;A.queue=_B;A.container=_B;A.user_id=user_id;A.images={_G:'ubuntu:22.04',_H:'python:3.12-slim','node':'node:20-bullseye-slim',_C:_J}
|
|
24
|
-
def
|
|
24
|
+
def bt(A):
|
|
25
25
|
try:
|
|
26
26
|
B=A.client.images.get(A.image_name)
|
|
27
27
|
if B:logger.info(f"Image {A.image_name} exists.");return _A
|
|
28
28
|
else:logger.info(f"Image {A.image_name} not exists.");return _E
|
|
29
29
|
except docker.errors.ImageNotFound:logger.error(f"Image {A.image_name} not exists.");return _E
|
|
30
30
|
except Exception as C:logger.error(f"检查镜像时出错: {str(C)}");return _E
|
|
31
|
-
def
|
|
31
|
+
def bs(B):
|
|
32
32
|
F='arm64';print('🔧 Building Docker image...')
|
|
33
33
|
try:
|
|
34
34
|
C=platform.machine()
|
|
@@ -44,7 +44,7 @@ class Container:
|
|
|
44
44
|
except Exception as D:logger.error(f"An unexpected error occurred: {D}");raise
|
|
45
45
|
def get_or_create_container(A,container_name,image_type=_C):
|
|
46
46
|
E='running';C=image_type;B=container_name;D=_F;F=A.workspace_path;B=f"{A.container_basename}_{A.user_id}"
|
|
47
|
-
if C==_C and not A.
|
|
47
|
+
if C==_C and not A.bt():A.bs()
|
|
48
48
|
try:
|
|
49
49
|
A.container=A.client.containers.get(B)
|
|
50
50
|
if A.container.status==E:logger.info(f"容器 {B} 已存在且运行中,直接复用")
|
|
@@ -34,7 +34,7 @@ _EXCLUDED_STATE_KEYS=_B,'todos'
|
|
|
34
34
|
TASK_TOOL_DESCRIPTION='Launch an ephemeral subagent to handle complex, multi-step independent tasks with isolated context windows.\n\nAvailable agent types and the tools they have access to:\n{available_agents}\n\nWhen using the Task tool, you must specify a subagent_type parameter to select which agent type to use.\n\n## Usage notes:\n1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses\n2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.\n3. Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.\n4. The agent\'s outputs should generally be trusted\n5. Clearly tell the agent whether you expect it to create content, perform analysis, or just do research (search, file reads, web fetches, etc.), since it is not aware of the user\'s intent\n6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.\n7. When only the general-purpose agent is provided, you should use it for all tasks. It is great for isolating context and token usage, and completing specific, complex tasks, as it has all the same capabilities as the main agent.\n\n### Example usage of the general-purpose agent:\n\n<example_agent_descriptions>\n"general-purpose": use this agent for general purpose tasks, it has access to all tools as the main agent.\n</example_agent_descriptions>\n\n<example>\nUser: "I want to conduct research on the accomplishments of Lebron James, Michael Jordan, and Kobe Bryant, and then compare them."\nAssistant: *Uses the task tool in parallel to conduct isolated research on each of the three players*\nAssistant: *Synthesizes the results of the three isolated research tasks and responds to the User*\n<commentary>\nResearch is a complex, multi-step task in it of itself.\nThe research of each individual player is not dependent on the research of the other players.\nThe assistant uses the task tool to break down the complex objective into three isolated tasks.\nEach research task only needs to worry about context and tokens about one player, then returns synthesized information about each player as the Tool Result.\nThis means each research task can dive deep and spend tokens and context deeply researching each player, but the final result is synthesized information, and saves us tokens in the long run when comparing the players to each other.\n</commentary>\n</example>\n\n<example>\nUser: "Analyze a single large code repository for security vulnerabilities and generate a report."\nAssistant: *Launches a single `task` subagent for the repository analysis*\nAssistant: *Receives report and integrates results into final summary*\n<commentary>\nSubagent is used to isolate a large, context-heavy task, even though there is only one. This prevents the main thread from being overloaded with details.\nIf the user then asks followup questions, we have a concise report to reference instead of the entire history of analysis and tool calls, which is good and saves us time and money.\n</commentary>\n</example>\n\n<example>\nUser: "Schedule two meetings for me and prepare agendas for each."\nAssistant: *Calls the task tool in parallel to launch two `task` subagents (one per meeting) to prepare agendas*\nAssistant: *Returns final schedules and agendas*\n<commentary>\nTasks are simple individually, but subagents help silo agenda preparation.\nEach subagent only needs to worry about the agenda for one meeting.\n</commentary>\n</example>\n\n<example>\nUser: "I want to order a pizza from Dominos, order a burger from McDonald\'s, and order a salad from Subway."\nAssistant: *Calls tools directly in parallel to order a pizza from Dominos, a burger from McDonald\'s, and a salad from Subway*\n<commentary>\nThe assistant did not use the task tool because the objective is super simple and clear and only requires a few trivial tool calls.\nIt is better to just complete the task directly and NOT use the `task`tool.\n</commentary>\n</example>\n\n### Example usage with custom agents:\n\n<example_agent_descriptions>\n"content-reviewer": use this agent after you are done creating significant content or documents\n"greeting-responder": use this agent when to respond to user greetings with a friendly joke\n"research-analyst": use this agent to conduct thorough research on complex topics\n</example_agent_description>\n\n<example>\nuser: "Please write a function that checks if a number is prime"\nassistant: Sure let me write a function that checks if a number is prime\nassistant: First let me use the Write tool to write a function that checks if a number is prime\nassistant: I\'m going to use the Write tool to write the following code:\n<code>\nfunction isPrime(n) {{\n if (n <= 1) return false\n for (let i = 2; i * i <= n; i++) {{\n if (n % i === 0) return false\n }}\n return true\n}}\n</code>\n<commentary>\nSince significant content was created and the task was completed, now use the content-reviewer agent to review the work\n</commentary>\nassistant: Now let me use the content-reviewer agent to review the code\nassistant: Uses the Task tool to launch with the content-reviewer agent\n</example>\n\n<example>\nuser: "Can you help me research the environmental impact of different renewable energy sources and create a comprehensive report?"\n<commentary>\nThis is a complex research task that would benefit from using the research-analyst agent to conduct thorough analysis\n</commentary>\nassistant: I\'ll help you research the environmental impact of renewable energy sources. Let me use the research-analyst agent to conduct comprehensive research on this topic.\nassistant: Uses the Task tool to launch with the research-analyst agent, providing detailed instructions about what research to conduct and what format the report should take\n</example>\n\n<example>\nuser: "Hello"\n<commentary>\nSince the user is greeting, use the greeting-responder agent to respond with a friendly joke\n</commentary>\nassistant: "I\'m going to use the Task tool to launch with the greeting-responder agent"\n</example>'
|
|
35
35
|
TASK_SYSTEM_PROMPT='## `task` (subagent spawner)\n\nYou have access to a `task` tool to launch short-lived subagents that handle isolated tasks. These agents are ephemeral — they live only for the duration of the task and return a single result.\n\nWhen to use the task tool:\n- When a task is complex and multi-step, and can be fully delegated in isolation\n- When a task is independent of other tasks and can run in parallel\n- When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread\n- When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)\n- When you only care about the output of the subagent, and not the intermediate steps (ex. performing a lot of research and then returned a synthesized report, performing a series of computations or lookups to achieve a concise, relevant answer.)\n\nSubagent lifecycle:\n1. **Spawn** → Provide clear role, instructions, and expected output\n2. **Run** → The subagent completes the task autonomously\n3. **Return** → The subagent provides a single structured result\n4. **Reconcile** → Incorporate or synthesize the result into the main thread\n\nWhen NOT to use the task tool:\n- If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)\n- If the task is trivial (a few tool calls or simple lookup)\n- If delegating does not reduce token usage, complexity, or context switching\n- If splitting would add latency without benefit\n- 用户仅仅时普通寒暄时,不应该使用任务工具。\n\n## Important Task Tool Usage Notes to Remember\n- Whenever possible, parallelize the work that you do. This is true for both tool_calls, and for tasks. Whenever you have independent steps to complete - make tool_calls, or kick off tasks (subagents) in parallel to accomplish them faster. This saves time for the user, which is incredibly important.\n- Remember to use the `task` tool to silo independent tasks within a multi-part objective.\n- You should use the `task` tool whenever you have a complex task that will take multiple steps, and is independent from other tasks that the agent needs to complete. These agents are highly competent and efficient.'
|
|
36
36
|
DEFAULT_GENERAL_PURPOSE_DESCRIPTION='General-purpose agent for researching complex questions, searching for files and content, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. This agent has access to all tools as the main agent.'
|
|
37
|
-
def
|
|
37
|
+
def bz(*,default_model,default_tools,default_middleware,default_interrupt_on,subagents,general_purpose_agent):
|
|
38
38
|
M='middleware';L='runnable';F=default_tools;E=default_model;C=default_interrupt_on;D=default_middleware or[];B={};G=[]
|
|
39
39
|
if general_purpose_agent:
|
|
40
40
|
H=[*D]
|
|
@@ -79,8 +79,8 @@ async def astream_handler(stream,runtime):
|
|
|
79
79
|
if isinstance(A,AIMessage)and len(A.tool_calls)==0:C=A
|
|
80
80
|
if isinstance(A,ToolMessage):G=A.name;N=A.content;H=A.tool_call_id;I={_E:'tool_result',_G:D.tool_call_id,_H:H,_I:'',_C:G,_D:{},_J:N,_K:_O};J(json.dumps(I,indent=2,ensure_ascii=_L))
|
|
81
81
|
O={_B:[C]if C and isinstance(C,AIMessage)else[]};return O
|
|
82
|
-
def
|
|
83
|
-
G='Tool call ID is required for subagent invocation';F='custom';A=task_description;B,H=
|
|
82
|
+
def cb(*,default_model,default_tools,default_middleware,default_interrupt_on,subagents,general_purpose_agent,task_description=_A):
|
|
83
|
+
G='Tool call ID is required for subagent invocation';F='custom';A=task_description;B,H=bz(default_model=default_model,default_tools=default_tools,default_middleware=default_middleware,default_interrupt_on=default_interrupt_on,subagents=subagents,general_purpose_agent=general_purpose_agent);C='\n'.join(H)
|
|
84
84
|
def D(result,tool_call_id):A=result;B={A:B for(A,B)in A.items()if A not in _EXCLUDED_STATE_KEYS};C=A[_B][-1];D=I(C);return Command(update={**B,_B:[ToolMessage(D,tool_call_id=tool_call_id)]})
|
|
85
85
|
def I(msg):
|
|
86
86
|
D='text';B=msg.content
|
|
@@ -110,7 +110,7 @@ def cd(*,default_model,default_tools,default_middleware,default_interrupt_on,sub
|
|
|
110
110
|
return D(L,A.tool_call_id)
|
|
111
111
|
return StructuredTool.from_function(name='task',func=J,coroutine=K,description=A)
|
|
112
112
|
class SubAgentMiddleware(AgentMiddleware):
|
|
113
|
-
def __init__(A,*,default_model,default_tools=_A,default_middleware=_A,default_interrupt_on=_A,subagents=_A,system_prompt=TASK_SYSTEM_PROMPT,general_purpose_agent=True,task_description=_A):super().__init__();A.system_prompt=system_prompt;B=
|
|
113
|
+
def __init__(A,*,default_model,default_tools=_A,default_middleware=_A,default_interrupt_on=_A,subagents=_A,system_prompt=TASK_SYSTEM_PROMPT,general_purpose_agent=True,task_description=_A):super().__init__();A.system_prompt=system_prompt;B=cb(default_model=default_model,default_tools=default_tools or[],default_middleware=default_middleware,default_interrupt_on=default_interrupt_on,subagents=subagents or[],general_purpose_agent=general_purpose_agent,task_description=task_description);A.tools=[B]
|
|
114
114
|
def wrap_model_call(B,request,handler):
|
|
115
115
|
C=handler;A=request
|
|
116
116
|
if B.system_prompt is not _A:D=A.system_prompt+'\n\n'+B.system_prompt if A.system_prompt else B.system_prompt;return C(A.override(system_prompt=D))
|
|
@@ -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 cq(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.cn(B,N)for B in B];A.trigger=I;G=I
|
|
48
|
+
else:J=A.cn(B,N);A.trigger=J;G=[J]
|
|
49
|
+
A._trigger_conditions=G;A.keep=A.cn(F,'keep')
|
|
50
|
+
if H is count_tokens_approximately:A.token_counter=cq(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.cr()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 cj(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.cg(B);E=A.token_counter(B);F=A.cj(C);G=A.cu(B,E)
|
|
59
59
|
if not G and not F:return
|
|
60
|
-
D=A.
|
|
60
|
+
D=A.ck(B)
|
|
61
61
|
if D<=0:return
|
|
62
|
-
H,I=A.
|
|
62
|
+
H,I=A.cs(B,D);J=A.co(H);K=A.cp(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.cg(B);E=A.token_counter(B);F=A.cj(C);G=A.cu(B,E)
|
|
66
66
|
if not G and not F:return
|
|
67
|
-
D=A.
|
|
67
|
+
D=A.ck(B)
|
|
68
68
|
if D<=0:return
|
|
69
|
-
H,I=A.
|
|
70
|
-
def
|
|
69
|
+
H,I=A.cs(B,D);J=await A._acreate_summary(H);K=A.cp(J);return{_B:[RemoveMessage(id=REMOVE_ALL_MESSAGES),*K,*I],_G:_F}
|
|
70
|
+
def ch(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 cu(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.ch(E,C):return _C
|
|
81
81
|
if B==_D:
|
|
82
|
-
G=A.
|
|
82
|
+
G=A.cr()
|
|
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.ch(E,D):return _C
|
|
88
88
|
return _F
|
|
89
|
-
def
|
|
89
|
+
def ck(A,messages):
|
|
90
90
|
B=messages;D,E=A.keep
|
|
91
91
|
if D in{_E,_D}:
|
|
92
|
-
C=A.
|
|
92
|
+
C=A.cm(B)
|
|
93
93
|
if C is not _A:return C
|
|
94
|
-
return A.
|
|
95
|
-
return A.
|
|
96
|
-
def
|
|
94
|
+
return A.ci(B,_DEFAULT_MESSAGES_TO_KEEP)
|
|
95
|
+
return A.ci(B,cast('int',E))
|
|
96
|
+
def cm(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.cr()
|
|
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.cl(A,B)
|
|
118
|
+
def cr(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 cn(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 cp(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 cg(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 cs(conversation_messages,cutoff_index):B=cutoff_index;A=conversation_messages;C=A[:B];D=A[B:];return C,D
|
|
142
|
+
def ci(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.cl(A,D)
|
|
146
146
|
@staticmethod
|
|
147
|
-
def
|
|
147
|
+
def cl(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,10 +158,10 @@ 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 co(A,messages_to_sumarize):
|
|
162
162
|
B=messages_to_sumarize
|
|
163
163
|
if not B:return _H
|
|
164
|
-
C=A.
|
|
164
|
+
C=A.ct(B)
|
|
165
165
|
if not C:return'Previous conversation was too long to summarize'
|
|
166
166
|
D=get_buffer_string(C)
|
|
167
167
|
try:E=A.model.invoke(A.summary_prompt.format(messages=D));return E.text.strip()
|
|
@@ -169,12 +169,12 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
169
169
|
async def _acreate_summary(A,messages_to_summarize):
|
|
170
170
|
B=messages_to_summarize
|
|
171
171
|
if not B:return _H
|
|
172
|
-
C=A.
|
|
172
|
+
C=A.ct(B)
|
|
173
173
|
if not C:return'Previous conversation was too long to summarize.'
|
|
174
174
|
D=get_buffer_string(C)
|
|
175
175
|
try:E=await A.model.ainvoke(A.summary_prompt.format(messages=D));return E.text.strip()
|
|
176
176
|
except Exception as F:return f"Error generating sumamry: {F!s}"
|
|
177
|
-
def
|
|
177
|
+
def ct(A,messages):
|
|
178
178
|
B=messages
|
|
179
179
|
try:
|
|
180
180
|
if A.trim_tokens_to_summarize is _A:return B
|
|
@@ -16,33 +16,33 @@ class ToolResultFormatter:
|
|
|
16
16
|
def detect_type(B,content):
|
|
17
17
|
A=content;A=A.strip()
|
|
18
18
|
if A.statswith(SUCCESS_PREFIX):
|
|
19
|
-
C=B.
|
|
20
|
-
if B.
|
|
19
|
+
C=B.bf(A)
|
|
20
|
+
if B.ba(C):return ContentType.JSON
|
|
21
21
|
return ContentType.SUCCESS
|
|
22
22
|
if A.startswith(FAILURE_PREFIX):return ContentType.ERROR
|
|
23
|
-
if B.
|
|
24
|
-
if B.
|
|
25
|
-
if B.
|
|
23
|
+
if B.ba(A):return ContentType.JSON
|
|
24
|
+
if B.bc(A):return ContentType.ERROR
|
|
25
|
+
if B.bb(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.be,ContentType.ERROR:A.
|
|
29
|
-
def
|
|
30
|
-
def
|
|
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.be,ContentType.ERROR:A.bj,ContentType.JSON:A.bh,ContentType.MARKDOWN:A.bg,ContentType.TEXT:A.bd};F=E.get(C,A.bd);G=F(name,B,max_length);return FormattedResult(content_type=C,elements=G,success=D)
|
|
29
|
+
def bf(B,content):A=content.split('\n',2);return A[2].strip()if len(A)>2 else''
|
|
30
|
+
def ba(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(']'):
|
|
34
34
|
try:json.loads(A);return True
|
|
35
35
|
except(json.JSONDecodeError,ValueError):pass
|
|
36
36
|
return _A
|
|
37
|
-
def
|
|
38
|
-
def
|
|
39
|
-
def be(B,name,content,max_length):A='green';C=B.
|
|
40
|
-
def
|
|
41
|
-
def
|
|
37
|
+
def bc(B,content):A=['Traceback (most recent call last)','Exception:','Error:'];return any(A in content for A in A)
|
|
38
|
+
def bb(C,content):A=content;B=['```','**','##','- **'];return A.startswith('#')or any(B in A for B in B)
|
|
39
|
+
def be(B,name,content,max_length):A='green';C=B.bi(content,max_length);return[Panel(Text(C,style=A),title=f"📤 {name} ✓",border_style=A)]
|
|
40
|
+
def bj(B,name,content,max_length):A='red';C=B.bi(content,max_length);return[Panel(Text(C,style=A),title=f"📤 {name} ✗",border_style=A)]
|
|
41
|
+
def bh(B,name,content,max_length):
|
|
42
42
|
D=max_length;A=content;E=A
|
|
43
|
-
if A.startswith(SUCCESS_PREFIX):E=B.
|
|
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
|
|
43
|
+
if A.startswith(SUCCESS_PREFIX):E=B.bf(A)
|
|
44
|
+
try:F=json.loads(E);C=json.dumps(F,indent=2,ensure_ascii=_A);C=B.bi(C,D);return[Text(f"📤 {name} ✓",style=_B),Syntax(C,'json',theme='monokai',line_numbers=_A)]
|
|
45
|
+
except(json.JSONDecodeError,ValueError):return B.bd(name,A,D)
|
|
46
|
+
def bg(A,name,content,max_length):B=A.bi(content,max_length);return[Panel(Markdown(B),title=f"📤 {name}",border_style='cyan dim')]
|
|
47
|
+
def bd(A,name,content,max_length):B=A.bi(content,max_length);return[Text(f"📤 {name}:",style=_B),Text(f" {B}",style='dim')]
|
|
48
|
+
def bi(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 bl(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 bl(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 bl(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 bk(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 bk(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 bo(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 bn(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 bq(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 bo(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 bn(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 bq(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 bo(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 bp(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 bn(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 bq(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 dd(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 dc(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 dc(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 dd(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=dd(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=dc(I);A.execute(f"""
|
|
91
91
|
SELECT
|
|
92
92
|
{S} as bucket,
|
|
93
93
|
COUNT(*),
|
|
@@ -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.cz(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.cz(A);A.clear()
|
|
46
|
+
def cz(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 da(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.da(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.da(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
|
+
d=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(d);A.kb=KeyBindings();A.b();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 e(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 c(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 b(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.a('● 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.a('● 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.e(spinner='',status='等待输入',tokens=A.token_count);A.app.layout.focus(A.input_box);return L
|
|
93
|
+
def a(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.e(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
|
|
@@ -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.q();A.list_container=HSplit(A.rows,padding=0);A.markdown=A.t(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.s()
|
|
23
|
+
def t(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 q(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 u(A):A.rows=A.q();A.list_container.children=A.rows;get_app().invalidate()
|
|
29
|
+
def s(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.u()
|
|
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.u()
|
|
36
36
|
@A.kb.add('enter')
|
|
37
37
|
def D(event):A.callback(A.selected_index)
|
|
38
38
|
async def demo():
|
|
@@ -6,22 +6,22 @@ from prompt_toolkit.layout.controls import UIControl,UIContent
|
|
|
6
6
|
from prompt_toolkit.data_structures import Point
|
|
7
7
|
from prompt_toolkit.formatted_text import ANSI
|
|
8
8
|
class ScrollableFormattedLogControl(FormattedTextControl):
|
|
9
|
-
def __init__(A):A.lines=[];A.scroll_offset=0;A._height=0;A.last_count=0;super().__init__(A.
|
|
9
|
+
def __init__(A):A.lines=[];A.scroll_offset=0;A._height=0;A.last_count=0;super().__init__(A.x,focusable=True,show_cursor=False)
|
|
10
10
|
def clear(A):A.lines=[];A.scroll_offset=0;A.last_count=0;A._height=0
|
|
11
|
-
def append_text(A,ansi_text):B=ansi_text.splitlines();A.lines.extend(B);A.last_count=len(B);A.
|
|
11
|
+
def append_text(A,ansi_text):B=ansi_text.splitlines();A.lines.extend(B);A.last_count=len(B);A.z()
|
|
12
12
|
def update_last(A,ansi_text):
|
|
13
13
|
B=ansi_text.splitlines()
|
|
14
14
|
if A.lines:A.lines=A.lines[:-A.last_count]+B
|
|
15
15
|
else:A.lines=B
|
|
16
|
-
A.last_count=len(B);A.
|
|
16
|
+
A.last_count=len(B);A.z()
|
|
17
17
|
def refresh_scroll(A):
|
|
18
18
|
if A._height:A.scroll_offset=max(0,len(A.lines)-A._height)
|
|
19
|
-
def
|
|
19
|
+
def z(A):
|
|
20
20
|
if A._height:A.scroll_offset=max(0,len(A.lines)-A._height)
|
|
21
21
|
def y(A,amount):
|
|
22
22
|
if A._height:B=max(0,len(A.lines)-A._height);A.scroll_offset=max(0,min(A.scroll_offset+amount,B))
|
|
23
23
|
def is_focusable(A):return True
|
|
24
|
-
def
|
|
24
|
+
def x(A):
|
|
25
25
|
C=A._height or 100;D=A.lines[A.scroll_offset:A.scroll_offset+C];B=[]
|
|
26
26
|
for E in D:F=sanitize_ansi_text(E);B.extend(ANSI(F).__pt_formatted_text__());B.append(('','\n'))
|
|
27
27
|
return B
|
|
@@ -34,15 +34,15 @@ class ScrollableFormattedLogControl(FormattedTextControl):
|
|
|
34
34
|
class ScrollableLogControl(UIControl):
|
|
35
35
|
def __init__(A):A.lines=[];A.scroll_offset=0;A.visible_lines=[];A._height=0;A.last_count=0
|
|
36
36
|
def clear(A):A.visible_lines=[];A.lines=[];A.scroll_offset=0;A.last_count=0;A._height=0
|
|
37
|
-
def append_text(A,ansi_text):B=ansi_text.splitlines();A.lines.extend(B);A.last_count=len(B);A.
|
|
37
|
+
def append_text(A,ansi_text):B=ansi_text.splitlines();A.lines.extend(B);A.last_count=len(B);A.z()
|
|
38
38
|
def update_last(A,ansi_text):
|
|
39
39
|
B=ansi_text.splitlines()
|
|
40
40
|
if A.lines:A.lines=A.lines[:-A.last_count]+B
|
|
41
41
|
else:A.lines=B
|
|
42
|
-
A.last_count=len(B);A.
|
|
42
|
+
A.last_count=len(B);A.z()
|
|
43
43
|
def refresh_scroll(A):
|
|
44
44
|
if A._height:A.scroll_offset=max(0,len(A.lines)-A._height)
|
|
45
|
-
def
|
|
45
|
+
def z(A):A.scroll_offset=max(0,len(A.lines)-A._height)
|
|
46
46
|
def y(A,amount):A.scroll_offset=max(0,min(A.scroll_offset+amount,max(0,len(A.lines)-A._height)))
|
|
47
47
|
def is_focusable(A):return True
|
|
48
48
|
def create_content(A,width,height):B=height;A._height=B;A.visible_lines=A.lines[A.scroll_offset:A.scroll_offset+B];return UIContent(get_line=A.get_line,line_count=len(A.visible_lines),cursor_position=Point(0,len(A.visible_lines)-1))
|
|
@@ -28,7 +28,7 @@ from src.tui.components.tdiff import render_diff_as_markdown
|
|
|
28
28
|
from src.tui.components.tdisplay import render_content_with_line_limit
|
|
29
29
|
from src.tui.utils.render import markdown_to_wrapped_text
|
|
30
30
|
def display_tool_call(tool_name,args,events=[]):
|
|
31
|
-
C=tool_name;A=events;E=format_tool_compact(C,args);D=
|
|
31
|
+
C=tool_name;A=events;E=format_tool_compact(C,args);D=o(C,args);B=Tree(Text(_I,style=_J)+Text(f"{E}",style=_C),guide_style=_D)
|
|
32
32
|
if A and len(A)>0:F=display_sub_panel(D,sub_events=A);B.add(F)
|
|
33
33
|
else:B.add(D)
|
|
34
34
|
return B
|
|
@@ -37,14 +37,14 @@ def display_tool_result(tool_name,args=_E,result=_E,events=[]):
|
|
|
37
37
|
if A is _E:A={}
|
|
38
38
|
H=format_tool_compact(G,A);C=G.lower()
|
|
39
39
|
try:
|
|
40
|
-
if C==_K and isinstance(A,dict):B=
|
|
41
|
-
elif C==_L and isinstance(A,dict):B=
|
|
42
|
-
elif C in[_M]and isinstance(A,dict):B=
|
|
43
|
-
elif C==_H and isinstance(A,dict):B=
|
|
44
|
-
elif C==_N and isinstance(A,dict):B=
|
|
45
|
-
elif C==_O and isinstance(A,dict):B=
|
|
46
|
-
elif C==_P and isinstance(A,dict):B=
|
|
47
|
-
else:B=
|
|
40
|
+
if C==_K and isinstance(A,dict):B=m(A)
|
|
41
|
+
elif C==_L and isinstance(A,dict):B=m(A)
|
|
42
|
+
elif C in[_M]and isinstance(A,dict):B=m(A)
|
|
43
|
+
elif C==_H and isinstance(A,dict):B=k(A,D)
|
|
44
|
+
elif C==_N and isinstance(A,dict):B=l(A,D)
|
|
45
|
+
elif C==_O and isinstance(A,dict):B=f(A,D)
|
|
46
|
+
elif C==_P and isinstance(A,dict):B=h(A,D)
|
|
47
|
+
else:B=n(A,D)
|
|
48
48
|
except Exception as I:import traceback as J;B=Text(f"Error: {str(I)}\n{J.format_exc()}",style='red')
|
|
49
49
|
F=Tree(Text(_I,style=_J)+Text(f"{H}",style=_C),guide_style=_D)
|
|
50
50
|
if E and len(E)>0:K=display_sub_panel(B,sub_events=E);F.add(K)
|
|
@@ -102,12 +102,12 @@ def format_tool_compact(name,args):
|
|
|
102
102
|
I=', '.join(M)
|
|
103
103
|
if len(I)>50:I=I[:47]+_B
|
|
104
104
|
return f"{J}({I})"
|
|
105
|
-
def
|
|
105
|
+
def o(tool_name,arguments):
|
|
106
106
|
D=' Will search for pattern';C=arguments;B='bold grey';A=tool_name.lower()
|
|
107
107
|
if A==_H:return Text(f"running ...",style=B)
|
|
108
108
|
if A==_M:return Text(f" Executing ...",style=B)
|
|
109
|
-
elif A==_L:return
|
|
110
|
-
elif A==_K:return
|
|
109
|
+
elif A==_L:return j(C)
|
|
110
|
+
elif A==_K:return g(C)
|
|
111
111
|
elif A in[_N,_O]:return Text(D,style=B)
|
|
112
112
|
elif A==_P:return Text(D,stype=B)
|
|
113
113
|
else:return Text(' Executing ...',style=B)
|
|
@@ -123,12 +123,12 @@ def display_sub_panel(result=_E,sub_events=[]):
|
|
|
123
123
|
else:A.append(C)
|
|
124
124
|
else:A.append(markdown_to_wrapped_text(f"calling ...",prefix=_E))
|
|
125
125
|
J=Group(*[A for A in A]);K=Box(' \n \n \n \n \n \n \n ');L=Panel(J,title_align='left',box=K);return L
|
|
126
|
-
def
|
|
126
|
+
def g(arguments):
|
|
127
127
|
A=arguments;B=A.get(_F,'');C=A.get('old_string','');D=A.get('new_string','');E=''
|
|
128
128
|
if os.path.exists(B):
|
|
129
129
|
with open(B,'r',encoding='utf-8')as F:G=F.read()
|
|
130
130
|
H=D if C==''else G.replace(C,D,1);I=render_diff_as_markdown(E,H);return I
|
|
131
|
-
def
|
|
131
|
+
def j(arguments):
|
|
132
132
|
D=arguments;A=D.get(_F,'');B=D.get(_R,'')
|
|
133
133
|
if os.path.exists(A):C=open(A,'r').read()
|
|
134
134
|
else:C=''
|
|
@@ -137,7 +137,7 @@ def m(arguments):
|
|
|
137
137
|
E=recognize_language(A)
|
|
138
138
|
if E:G=f"```\n{E}\n{B}\n```";return render_content_with_line_limit(G,'',100)
|
|
139
139
|
else:return render_content_with_line_limit(B,'',100)
|
|
140
|
-
def
|
|
140
|
+
def m(arguments):
|
|
141
141
|
B=arguments.get(_F,'');C=''
|
|
142
142
|
if os.path.exists(B):C=open(B,'r').read()
|
|
143
143
|
E=C.split(_A);F=len(E);A=50
|
|
@@ -146,7 +146,7 @@ def o(arguments):
|
|
|
146
146
|
H=recognize_language(B)
|
|
147
147
|
if H:J=f"```{H}\n{D}\n```";return render_content_with_line_limit(J,'',-1)
|
|
148
148
|
else:return render_content_with_line_limit(D,'',-1)
|
|
149
|
-
def
|
|
149
|
+
def i(arguments,result):
|
|
150
150
|
C=result.strip().split(_A);F=len(C);B=[];D=10;G=C[:D]
|
|
151
151
|
for(H,A)in enumerate(G):
|
|
152
152
|
I=''if H==0 else''
|
|
@@ -155,7 +155,7 @@ def n(arguments,result):
|
|
|
155
155
|
E=F-D
|
|
156
156
|
if E>0:B.append(f" ... and {E} more lines")
|
|
157
157
|
return Text(_A.join(B),style=_G)
|
|
158
|
-
def
|
|
158
|
+
def k(arguments,result):
|
|
159
159
|
C=result.strip().split(_A);F=len(C);B=[];D=10;G=C[:D]
|
|
160
160
|
for(H,A)in enumerate(G):
|
|
161
161
|
I=''if H==0 else''
|
|
@@ -164,7 +164,7 @@ def f(arguments,result):
|
|
|
164
164
|
E=F-D
|
|
165
165
|
if E>0:B.append(f" ... and {E} more lines")
|
|
166
166
|
return Text(_A.join(B),style=_G)
|
|
167
|
-
def
|
|
167
|
+
def l(arguments,result):
|
|
168
168
|
C=result.strip().split(_A);F=len(C);B=[];D=10;G=C[:D]
|
|
169
169
|
for(H,A)in enumerate(G):
|
|
170
170
|
I=''if H==0 else''
|
|
@@ -173,7 +173,7 @@ def g(arguments,result):
|
|
|
173
173
|
E=F-D
|
|
174
174
|
if E>0:B.append(f" ... and {E} more lines")
|
|
175
175
|
return Text(_A.join(B),style=_G)
|
|
176
|
-
def
|
|
176
|
+
def f(arguments,result):
|
|
177
177
|
C=result.strip().split(_A);F=len(C);B=[];D=10;G=C[:D]
|
|
178
178
|
for(H,A)in enumerate(G):
|
|
179
179
|
I=''if H==0 else''
|
|
@@ -182,7 +182,7 @@ def h(arguments,result):
|
|
|
182
182
|
E=F-D
|
|
183
183
|
if E>0:B.append(f" ... and {E} more lines")
|
|
184
184
|
return Text(_A.join(B),style=_G)
|
|
185
|
-
def
|
|
185
|
+
def h(arguments,result):
|
|
186
186
|
C=result.strip().split(_A);F=len(C);B=[];D=10;G=C[:D]
|
|
187
187
|
for(H,A)in enumerate(G):
|
|
188
188
|
I=''if H==0 else''
|
|
@@ -191,7 +191,7 @@ def i(arguments,result):
|
|
|
191
191
|
E=F-D
|
|
192
192
|
if E>0:B.append(f" ... and {E} more lines")
|
|
193
193
|
return Text(_A.join(B),style=_G)
|
|
194
|
-
def
|
|
194
|
+
def n(arguments,result):
|
|
195
195
|
B=result;A=str(B)if B else'tool completed successfully'
|
|
196
196
|
if len(A)>500:C=A[:500]+'\n ... (truncated)'
|
|
197
197
|
else:C=A
|