chcode 0.1.0__py3-none-any.whl
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.
- chcode/__init__.py +0 -0
- chcode/__main__.py +5 -0
- chcode/agent_setup.py +395 -0
- chcode/agents/__init__.py +0 -0
- chcode/agents/definitions.py +158 -0
- chcode/agents/loader.py +104 -0
- chcode/agents/runner.py +159 -0
- chcode/chat.py +1630 -0
- chcode/cli.py +142 -0
- chcode/config.py +571 -0
- chcode/display.py +325 -0
- chcode/prompts.py +640 -0
- chcode/session.py +149 -0
- chcode/skill_manager.py +165 -0
- chcode/utils/__init__.py +3 -0
- chcode/utils/enhanced_chat_openai.py +368 -0
- chcode/utils/git_checker.py +38 -0
- chcode/utils/git_manager.py +261 -0
- chcode/utils/modelscope_ratelimit.py +65 -0
- chcode/utils/multimodal.py +268 -0
- chcode/utils/shell/__init__.py +17 -0
- chcode/utils/shell/output.py +63 -0
- chcode/utils/shell/provider.py +128 -0
- chcode/utils/shell/result.py +14 -0
- chcode/utils/shell/semantics.py +55 -0
- chcode/utils/shell/session.py +159 -0
- chcode/utils/skill_loader.py +565 -0
- chcode/utils/text_utils.py +14 -0
- chcode/utils/tool_result_pipeline.py +244 -0
- chcode/utils/tools.py +1724 -0
- chcode/vision_config.py +371 -0
- chcode-0.1.0.dist-info/METADATA +275 -0
- chcode-0.1.0.dist-info/RECORD +36 -0
- chcode-0.1.0.dist-info/WHEEL +4 -0
- chcode-0.1.0.dist-info/entry_points.txt +2 -0
- chcode-0.1.0.dist-info/licenses/LICENSE +21 -0
chcode/agents/runner.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
from langchain.agents import create_agent
|
|
8
|
+
from langchain.agents.middleware import (
|
|
9
|
+
dynamic_prompt,
|
|
10
|
+
wrap_tool_call,
|
|
11
|
+
wrap_model_call,
|
|
12
|
+
ModelRequest,
|
|
13
|
+
ModelResponse,
|
|
14
|
+
)
|
|
15
|
+
from langchain_core.messages import HumanMessage, ToolMessage
|
|
16
|
+
from langchain.tools.tool_node import ToolCallRequest
|
|
17
|
+
|
|
18
|
+
from chcode.agents.definitions import AgentDefinition
|
|
19
|
+
from chcode.utils.enhanced_chat_openai import EnhancedChatOpenAI
|
|
20
|
+
from chcode.utils.skill_loader import SkillLoader, SkillAgentContext
|
|
21
|
+
from chcode.utils.tool_result_pipeline import (
|
|
22
|
+
clean_tool_output,
|
|
23
|
+
truncate_large_result,
|
|
24
|
+
enforce_per_turn_budget,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@wrap_tool_call
|
|
29
|
+
async def _handle_tool_errors(
|
|
30
|
+
request: ToolCallRequest, handler: Callable[[ToolCallRequest], object]
|
|
31
|
+
) -> object:
|
|
32
|
+
try:
|
|
33
|
+
return await handler(request)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
return ToolMessage(
|
|
36
|
+
f"Tool error: Please check your input and try again ({e})",
|
|
37
|
+
tool_call_id=request.tool_call["id"],
|
|
38
|
+
status="error",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dynamic_prompt
|
|
43
|
+
async def _subagent_system_prompt(request: ModelRequest) -> str:
|
|
44
|
+
return request.runtime.context.extra.get("system_prompt", "")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@wrap_model_call
|
|
48
|
+
async def _tool_result_budget(
|
|
49
|
+
request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse]
|
|
50
|
+
) -> ModelResponse:
|
|
51
|
+
workplace = request.runtime.context.working_directory
|
|
52
|
+
messages = list(request.messages)
|
|
53
|
+
for i, msg in enumerate(messages):
|
|
54
|
+
if isinstance(msg, ToolMessage) and msg.content:
|
|
55
|
+
cleaned = clean_tool_output(msg.content)
|
|
56
|
+
truncated = truncate_large_result(
|
|
57
|
+
cleaned,
|
|
58
|
+
msg.name or "",
|
|
59
|
+
msg.tool_call_id,
|
|
60
|
+
workplace=workplace,
|
|
61
|
+
)
|
|
62
|
+
messages[i] = msg.model_copy(update={"content": truncated})
|
|
63
|
+
messages = enforce_per_turn_budget(messages, budget=200_000, workplace=workplace)
|
|
64
|
+
return await handler(request.override(messages=messages))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _resolve_tools(
|
|
68
|
+
agent_def: AgentDefinition,
|
|
69
|
+
all_tools: list,
|
|
70
|
+
) -> list:
|
|
71
|
+
result = []
|
|
72
|
+
for t in all_tools:
|
|
73
|
+
name = getattr(t, "name", None) or getattr(t, "func", {}).get("__name__", "")
|
|
74
|
+
if name == "agent":
|
|
75
|
+
continue
|
|
76
|
+
if name in agent_def.disallowed_tools:
|
|
77
|
+
continue
|
|
78
|
+
if agent_def.tools is not None and name not in agent_def.tools:
|
|
79
|
+
continue
|
|
80
|
+
result.append(t)
|
|
81
|
+
return result
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def run_subagent(
|
|
85
|
+
prompt: str,
|
|
86
|
+
agent_def: AgentDefinition,
|
|
87
|
+
model_config: dict,
|
|
88
|
+
working_directory: Path,
|
|
89
|
+
skill_loader: SkillLoader,
|
|
90
|
+
timeout_seconds: int = 300,
|
|
91
|
+
description: str = "",
|
|
92
|
+
) -> str:
|
|
93
|
+
# 守卫:超时最小 300s
|
|
94
|
+
timeout_seconds = max(timeout_seconds, 300)
|
|
95
|
+
from chcode.utils.tools import ALL_TOOLS
|
|
96
|
+
|
|
97
|
+
filtered_tools = _resolve_tools(agent_def, ALL_TOOLS)
|
|
98
|
+
|
|
99
|
+
cfg = dict(model_config)
|
|
100
|
+
if agent_def.model:
|
|
101
|
+
cfg = {**cfg, "model": agent_def.model}
|
|
102
|
+
|
|
103
|
+
model = EnhancedChatOpenAI(**cfg)
|
|
104
|
+
|
|
105
|
+
subagent_context = SkillAgentContext(
|
|
106
|
+
skill_loader=skill_loader,
|
|
107
|
+
working_directory=working_directory,
|
|
108
|
+
model_config=cfg,
|
|
109
|
+
extra={"system_prompt": agent_def.system_prompt},
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
middleware = [
|
|
113
|
+
_handle_tool_errors,
|
|
114
|
+
_tool_result_budget,
|
|
115
|
+
_subagent_system_prompt,
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
from chcode.agent_setup import model_retry_with_backoff, ModelSwitchError
|
|
119
|
+
|
|
120
|
+
middleware.append(model_retry_with_backoff)
|
|
121
|
+
# 非 read-only 子代理继承主 agent 的 HITL 配置
|
|
122
|
+
if not agent_def.read_only:
|
|
123
|
+
from chcode.agent_setup import _hitl_middleware
|
|
124
|
+
|
|
125
|
+
if _hitl_middleware is not None:
|
|
126
|
+
middleware.append(_hitl_middleware)
|
|
127
|
+
|
|
128
|
+
subagent = create_agent(
|
|
129
|
+
model,
|
|
130
|
+
filtered_tools,
|
|
131
|
+
middleware=middleware,
|
|
132
|
+
context_schema=SkillAgentContext,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
result = await asyncio.wait_for(
|
|
137
|
+
subagent.ainvoke(
|
|
138
|
+
{"messages": [HumanMessage(content=prompt)]},
|
|
139
|
+
config={"configurable": {"thread_id": f"subagent_{id(subagent)}"}},
|
|
140
|
+
context=subagent_context,
|
|
141
|
+
),
|
|
142
|
+
timeout=timeout_seconds,
|
|
143
|
+
)
|
|
144
|
+
except asyncio.TimeoutError:
|
|
145
|
+
return f"Agent {agent_def.agent_type} timed out after {timeout_seconds}s."
|
|
146
|
+
except ModelSwitchError:
|
|
147
|
+
return f"Agent {agent_def.agent_type} 主模型失败,已切换备用模型,请重试"
|
|
148
|
+
except Exception as e:
|
|
149
|
+
return f"Agent {agent_def.agent_type} error: {e}"
|
|
150
|
+
|
|
151
|
+
from chcode.utils import get_text_content
|
|
152
|
+
messages = result.get("messages", [])
|
|
153
|
+
for msg in reversed(messages):
|
|
154
|
+
if msg.type == "ai" and msg.content:
|
|
155
|
+
content = get_text_content(msg.content)
|
|
156
|
+
if content.strip():
|
|
157
|
+
return content.strip()
|
|
158
|
+
|
|
159
|
+
return "(Agent completed with no text output)"
|