autocoder-nano 0.1.30__py3-none-any.whl → 0.1.33__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.
- autocoder_nano/agent/agent_base.py +4 -4
- autocoder_nano/agent/agentic_edit.py +1584 -0
- autocoder_nano/agent/agentic_edit_tools/__init__.py +28 -0
- autocoder_nano/agent/agentic_edit_tools/ask_followup_question_tool.py +51 -0
- autocoder_nano/agent/agentic_edit_tools/attempt_completion_tool.py +36 -0
- autocoder_nano/agent/agentic_edit_tools/base_tool_resolver.py +31 -0
- autocoder_nano/agent/agentic_edit_tools/execute_command_tool.py +65 -0
- autocoder_nano/agent/agentic_edit_tools/list_code_definition_names_tool.py +78 -0
- autocoder_nano/agent/agentic_edit_tools/list_files_tool.py +123 -0
- autocoder_nano/agent/agentic_edit_tools/list_package_info_tool.py +42 -0
- autocoder_nano/agent/agentic_edit_tools/plan_mode_respond_tool.py +35 -0
- autocoder_nano/agent/agentic_edit_tools/read_file_tool.py +73 -0
- autocoder_nano/agent/agentic_edit_tools/replace_in_file_tool.py +148 -0
- autocoder_nano/agent/agentic_edit_tools/search_files_tool.py +135 -0
- autocoder_nano/agent/agentic_edit_tools/write_to_file_tool.py +57 -0
- autocoder_nano/agent/agentic_edit_types.py +151 -0
- autocoder_nano/auto_coder_nano.py +145 -91
- autocoder_nano/git_utils.py +63 -1
- autocoder_nano/llm_client.py +170 -3
- autocoder_nano/llm_types.py +53 -14
- autocoder_nano/rules/rules_learn.py +221 -0
- autocoder_nano/templates.py +1 -1
- autocoder_nano/utils/formatted_log_utils.py +128 -0
- autocoder_nano/utils/printer_utils.py +5 -4
- autocoder_nano/utils/shell_utils.py +85 -0
- autocoder_nano/version.py +1 -1
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/METADATA +3 -2
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/RECORD +33 -16
- autocoder_nano/agent/new/auto_new_project.py +0 -278
- /autocoder_nano/{agent/new → rules}/__init__.py +0 -0
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/LICENSE +0 -0
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/WHEEL +0 -0
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/entry_points.txt +0 -0
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/top_level.txt +0 -0
autocoder_nano/llm_types.py
CHANGED
@@ -18,6 +18,7 @@ class AutoCoderArgs(BaseModel):
|
|
18
18
|
index_filter_level: Optional[int] = 0 # 用于查找相关文件的过滤级别
|
19
19
|
index_filter_file_num: Optional[int] = -1 #
|
20
20
|
index_filter_workers: Optional[int] = 1 # 过滤文件的线程数量
|
21
|
+
index_model_max_input_length: Optional[int] = 6000 # 模型最大输入长度
|
21
22
|
filter_batch_size: Optional[int] = 5 #
|
22
23
|
anti_quota_limit: Optional[int] = 1 # 请求模型时的间隔时间(s)
|
23
24
|
skip_build_index: Optional[bool] = False # 是否跳过索引构建(索引可以帮助您通过查询找到相关文件)
|
@@ -46,7 +47,7 @@ class AutoCoderArgs(BaseModel):
|
|
46
47
|
full_text_ratio: Optional[float] = 0.7
|
47
48
|
segment_ratio: Optional[float] = 0.2
|
48
49
|
buff_ratio: Optional[float] = 0.1
|
49
|
-
required_exts: Optional[str] = None
|
50
|
+
required_exts: Optional[str] = None # 指定处理的文件后缀,例如.pdf,.doc
|
50
51
|
monitor_mode: bool = False # 监控模式,会监控doc_dir目录中的文件变化
|
51
52
|
enable_hybrid_index: bool = False # 开启混合索引
|
52
53
|
disable_auto_window: bool = False
|
@@ -57,26 +58,32 @@ class AutoCoderArgs(BaseModel):
|
|
57
58
|
enable_rag_context: Optional[Union[bool, str]] = False
|
58
59
|
disable_segment_reorder: bool = False
|
59
60
|
disable_inference_enhance: bool = False
|
60
|
-
duckdb_vector_dim: Optional[int] = 1024
|
61
|
+
duckdb_vector_dim: Optional[int] = 1024 # DuckDB 向量化存储的维度
|
61
62
|
duckdb_query_similarity: Optional[float] = 0.7 # DuckDB 向量化检索 相似度 阈值
|
62
|
-
duckdb_query_top_k: Optional[int] = 50
|
63
|
+
duckdb_query_top_k: Optional[int] = 50 # DuckDB 向量化检索 返回 TopK个结果(且大于相似度)
|
63
64
|
|
64
65
|
# Git 相关参数
|
65
66
|
skip_commit: Optional[bool] = False
|
66
67
|
|
68
|
+
# Rules 相关参数
|
69
|
+
enable_rules: Optional[bool] = False
|
70
|
+
|
71
|
+
# Agent 相关参数
|
72
|
+
generate_max_rounds: Optional[int] = 5
|
73
|
+
|
67
74
|
# 模型相关参数
|
68
75
|
current_chat_model: Optional[str] = ""
|
69
76
|
current_code_model: Optional[str] = ""
|
70
|
-
model: Optional[str] = ""
|
71
|
-
chat_model: Optional[str] = ""
|
72
|
-
index_model: Optional[str] = ""
|
73
|
-
code_model: Optional[str] = ""
|
74
|
-
commit_model: Optional[str] = ""
|
75
|
-
emb_model: Optional[str] = ""
|
76
|
-
recall_model: Optional[str] = ""
|
77
|
-
chunk_model: Optional[str] = ""
|
78
|
-
qa_model: Optional[str] = ""
|
79
|
-
vl_model: Optional[str] = ""
|
77
|
+
model: Optional[str] = "" # 默认模型
|
78
|
+
chat_model: Optional[str] = "" # AI Chat交互模型
|
79
|
+
index_model: Optional[str] = "" # 代码索引生成模型
|
80
|
+
code_model: Optional[str] = "" # 编码模型
|
81
|
+
commit_model: Optional[str] = "" # Git Commit 模型
|
82
|
+
emb_model: Optional[str] = "" # RAG Emb 模型
|
83
|
+
recall_model: Optional[str] = "" # RAG 召回阶段模型
|
84
|
+
chunk_model: Optional[str] = "" # 段落重排序模型
|
85
|
+
qa_model: Optional[str] = "" # RAG 提问模型
|
86
|
+
vl_model: Optional[str] = "" # 多模态模型
|
80
87
|
|
81
88
|
class Config:
|
82
89
|
protected_namespaces = ()
|
@@ -117,6 +124,14 @@ class SourceCode(BaseModel):
|
|
117
124
|
metadata: Dict[str, Any] = Field(default_factory=dict)
|
118
125
|
|
119
126
|
|
127
|
+
class SourceCodeList:
|
128
|
+
def __init__(self, sources: List[SourceCode]):
|
129
|
+
self.sources = sources
|
130
|
+
|
131
|
+
def to_str(self):
|
132
|
+
return "\n".join([f"##File: {source.module_name}\n{source.source_code}\n" for source in self.sources])
|
133
|
+
|
134
|
+
|
120
135
|
class LLMRequest(BaseModel):
|
121
136
|
model: str # 指定使用的语言模型名称
|
122
137
|
messages: List[Dict[str, str]] # 包含对话消息的列表,每个消息是一个字典,包含 "role"(角色)和 "content"(内容)
|
@@ -138,6 +153,21 @@ class LLMResponse(BaseModel):
|
|
138
153
|
)
|
139
154
|
|
140
155
|
|
156
|
+
class SingleOutputMeta:
|
157
|
+
def __init__(self, input_tokens_count: int = 0,
|
158
|
+
generated_tokens_count: int = 0,
|
159
|
+
reasoning_content: str = "",
|
160
|
+
finish_reason: str = "",
|
161
|
+
first_token_time: float = 0.0,
|
162
|
+
extra_info: Dict[str, Any] = {}):
|
163
|
+
self.input_tokens_count = input_tokens_count
|
164
|
+
self.generated_tokens_count = generated_tokens_count
|
165
|
+
self.reasoning_content = reasoning_content
|
166
|
+
self.finish_reason = finish_reason
|
167
|
+
self.first_token_time = first_token_time
|
168
|
+
self.extra_info = extra_info
|
169
|
+
|
170
|
+
|
141
171
|
class IndexItem(BaseModel):
|
142
172
|
module_name: str
|
143
173
|
symbols: str
|
@@ -269,4 +299,13 @@ class FileInfo(BaseModel):
|
|
269
299
|
file_path: str
|
270
300
|
relative_path: str
|
271
301
|
modify_time: float
|
272
|
-
file_md5: str
|
302
|
+
file_md5: str
|
303
|
+
|
304
|
+
|
305
|
+
class RuleFile(BaseModel):
|
306
|
+
"""规则文件的Pydantic模型"""
|
307
|
+
description: str = Field(default="", description="规则的描述")
|
308
|
+
globs: List[str] = Field(default_factory=list, description="文件匹配模式列表")
|
309
|
+
always_apply: bool = Field(default=False, description="是否总是应用规则")
|
310
|
+
content: str = Field(default="", description="规则文件的正文内容")
|
311
|
+
file_path: str = Field(default="", description="规则文件的路径")
|
@@ -0,0 +1,221 @@
|
|
1
|
+
import os
|
2
|
+
from typing import List, Tuple, Dict, Optional, Generator
|
3
|
+
|
4
|
+
from autocoder_nano.git_utils import get_commit_changes
|
5
|
+
from autocoder_nano.llm_client import AutoLLM
|
6
|
+
from autocoder_nano.llm_prompt import prompt
|
7
|
+
from autocoder_nano.llm_types import AutoCoderArgs, SourceCodeList
|
8
|
+
from autocoder_nano.utils.printer_utils import Printer
|
9
|
+
|
10
|
+
|
11
|
+
printer = Printer()
|
12
|
+
|
13
|
+
|
14
|
+
class AutoRulesLearn:
|
15
|
+
|
16
|
+
def __init__(self, args: AutoCoderArgs, llm: AutoLLM):
|
17
|
+
self.args = args
|
18
|
+
self.llm = llm
|
19
|
+
|
20
|
+
@prompt()
|
21
|
+
def _analyze_commit_changes(
|
22
|
+
self, querie_with_urls_and_changes: List[Tuple[str, List[str], Dict[str, Tuple[str, str]]]]
|
23
|
+
):
|
24
|
+
"""
|
25
|
+
下面是用户一次提交的代码变更:
|
26
|
+
<changes>
|
27
|
+
{% for query,urls,changes in querie_with_urls_and_changes %}
|
28
|
+
## 原始的任务需求
|
29
|
+
{{ query }}
|
30
|
+
|
31
|
+
修改的文件:
|
32
|
+
{% for url in urls %}
|
33
|
+
- {{ url }}
|
34
|
+
{% endfor %}
|
35
|
+
|
36
|
+
代码变更:
|
37
|
+
{% for file_path, (before, after) in changes.items() %}
|
38
|
+
##File: {{ file_path }}
|
39
|
+
##修改前:
|
40
|
+
|
41
|
+
{{ before or "New file" }}
|
42
|
+
|
43
|
+
##File: {{ file_path }}
|
44
|
+
##修改后:
|
45
|
+
|
46
|
+
{{ after or "File deleted" }}
|
47
|
+
|
48
|
+
{% endfor %}
|
49
|
+
{% endfor %}
|
50
|
+
</changes>
|
51
|
+
|
52
|
+
请对根据上面的代码变更进行深入分析,提取具有通用价值的功能模式和设计模式,转化为可在其他项目中复用的代码规则(rules)。
|
53
|
+
|
54
|
+
- 识别代码变更中具有普遍应用价值的功能点和模式
|
55
|
+
- 将这些功能点提炼为结构化规则,便于在其他项目中快速复用
|
56
|
+
- 生成清晰的使用示例,包含完整依赖和调用方式
|
57
|
+
|
58
|
+
最后,新生成的文件格式要是这种形态的:
|
59
|
+
|
60
|
+
<example_rules>
|
61
|
+
---
|
62
|
+
description: [简明描述规则的功能,20字以内]
|
63
|
+
globs: [匹配应用此规则的文件路径,如"src/services/*.py"]
|
64
|
+
alwaysApply: [是否总是应用,通常为false]
|
65
|
+
---
|
66
|
+
|
67
|
+
# [规则主标题]
|
68
|
+
|
69
|
+
## 简要说明
|
70
|
+
[该规则的功能、适用场景和价值,100字以内]
|
71
|
+
|
72
|
+
## 典型用法
|
73
|
+
```python
|
74
|
+
# 完整的代码示例,包含:
|
75
|
+
# 1. 必要的import语句
|
76
|
+
# 2. 类/函数定义
|
77
|
+
# 3. 参数说明
|
78
|
+
# 4. 调用方式
|
79
|
+
# 5. 关键注释
|
80
|
+
```
|
81
|
+
|
82
|
+
## 依赖说明
|
83
|
+
- [必要的依赖库及版本]
|
84
|
+
- [环境要求]
|
85
|
+
- [初始化流程(如有)]
|
86
|
+
|
87
|
+
## 学习来源
|
88
|
+
[从哪个提交变更的哪部分代码中提取的该功能点]
|
89
|
+
</example_rules>
|
90
|
+
"""
|
91
|
+
|
92
|
+
@prompt()
|
93
|
+
def _analyze_modules(self, sources: SourceCodeList):
|
94
|
+
"""
|
95
|
+
下面是用户提供的需要抽取规则的代码:
|
96
|
+
<files>
|
97
|
+
{% for source in sources.sources %}
|
98
|
+
##File: {{ source.module_name }}
|
99
|
+
{{ source.source_code }}
|
100
|
+
{% endfor %}
|
101
|
+
</files>
|
102
|
+
|
103
|
+
请对对上面的代码进行深入分析,提取具有通用价值的功能模式和设计模式,转化为可在其他项目中复用的代码规则(rules)。
|
104
|
+
|
105
|
+
- 识别代码变更中具有普遍应用价值的功能点和模式
|
106
|
+
- 将这些功能点提炼为结构化规则,便于在其他项目中快速复用
|
107
|
+
- 生成清晰的使用示例,包含完整依赖和调用方式
|
108
|
+
|
109
|
+
最后,新生成的文件格式要是这种形态的:
|
110
|
+
|
111
|
+
<example_rules>
|
112
|
+
---
|
113
|
+
description: [简明描述规则的功能,20字以内]
|
114
|
+
globs: [匹配应用此规则的文件路径,如"src/services/*.py"]
|
115
|
+
alwaysApply: [是否总是应用,通常为false]
|
116
|
+
---
|
117
|
+
|
118
|
+
# [规则主标题]
|
119
|
+
|
120
|
+
## 简要说明
|
121
|
+
[该规则的功能、适用场景和价值,100字以内]
|
122
|
+
|
123
|
+
## 典型用法
|
124
|
+
```python
|
125
|
+
# 完整的代码示例,包含:
|
126
|
+
# 1. 必要的import语句
|
127
|
+
# 2. 类/函数定义
|
128
|
+
# 3. 参数说明
|
129
|
+
# 4. 调用方式
|
130
|
+
# 5. 关键注释
|
131
|
+
```
|
132
|
+
|
133
|
+
## 依赖说明
|
134
|
+
- [必要的依赖库及版本]
|
135
|
+
- [环境要求]
|
136
|
+
- [初始化流程(如有)]
|
137
|
+
|
138
|
+
## 学习来源
|
139
|
+
[从哪个提交变更的哪部分代码中提取的该功能点]
|
140
|
+
</example_rules>
|
141
|
+
"""
|
142
|
+
|
143
|
+
def analyze_commit_changes(
|
144
|
+
self, commit_id: str, conversations=None
|
145
|
+
) -> str:
|
146
|
+
""" 分析指定commit的代码变更 """
|
147
|
+
if conversations is None:
|
148
|
+
conversations = []
|
149
|
+
|
150
|
+
changes, _ = get_commit_changes(self.args.source_dir, commit_id)
|
151
|
+
|
152
|
+
if not changes:
|
153
|
+
printer.print_text("未发现代码变更(Commit)", style="yellow")
|
154
|
+
return ""
|
155
|
+
|
156
|
+
try:
|
157
|
+
# 获取prompt内容
|
158
|
+
prompt_content = self._analyze_commit_changes.prompt(
|
159
|
+
querie_with_urls_and_changes=changes
|
160
|
+
)
|
161
|
+
|
162
|
+
# 准备对话历史
|
163
|
+
if conversations:
|
164
|
+
new_conversations = conversations[:-1]
|
165
|
+
else:
|
166
|
+
new_conversations = []
|
167
|
+
new_conversations.append({"role": "user", "content": prompt_content})
|
168
|
+
|
169
|
+
self.llm.setup_default_model_name(self.args.chat_model)
|
170
|
+
v = self.llm.chat_ai(new_conversations, self.args.chat_model)
|
171
|
+
return v.output
|
172
|
+
except Exception as e:
|
173
|
+
printer.print_text(f"代码变更分析失败: {e}", style="red")
|
174
|
+
return ""
|
175
|
+
|
176
|
+
def analyze_modules(
|
177
|
+
self, sources: SourceCodeList, conversations=None
|
178
|
+
) -> str:
|
179
|
+
""" 分析给定的模块文件,根据用户需求生成可复用功能点的总结。 """
|
180
|
+
|
181
|
+
if conversations is None:
|
182
|
+
conversations = []
|
183
|
+
|
184
|
+
if not sources or not sources.sources:
|
185
|
+
printer.print_text("没有提供有效的模块文件进行分析.", style="red")
|
186
|
+
return ""
|
187
|
+
|
188
|
+
try:
|
189
|
+
# 准备 Prompt
|
190
|
+
prompt_content = self._analyze_modules.prompt(
|
191
|
+
sources=sources
|
192
|
+
)
|
193
|
+
|
194
|
+
# 准备对话历史
|
195
|
+
# 如果提供了 conversations,我们假设最后一个是用户的原始查询,替换它
|
196
|
+
if conversations:
|
197
|
+
new_conversations = conversations[:-1]
|
198
|
+
else:
|
199
|
+
new_conversations = []
|
200
|
+
new_conversations.append({"role": "user", "content": prompt_content})
|
201
|
+
|
202
|
+
self.llm.setup_default_model_name(self.args.chat_model)
|
203
|
+
v = self.llm.chat_ai(new_conversations, self.args.chat_model)
|
204
|
+
return v.output
|
205
|
+
except Exception as e:
|
206
|
+
printer.print_text(f"代码模块分析失败: {e}", style="red")
|
207
|
+
return ""
|
208
|
+
|
209
|
+
def _get_index_file_content(self) -> str:
|
210
|
+
"""获取索引文件内容"""
|
211
|
+
index_file_path = os.path.join(os.path.abspath(self.args.source_dir), ".autocoderrules", "index.md")
|
212
|
+
index_file_content = ""
|
213
|
+
|
214
|
+
try:
|
215
|
+
if os.path.exists(index_file_path):
|
216
|
+
with open(index_file_path, 'r', encoding='utf-8') as f:
|
217
|
+
index_file_content = f.read()
|
218
|
+
except Exception as e:
|
219
|
+
printer.print_text(f"读取索引文件时出错: {str(e)}", style="yellow")
|
220
|
+
|
221
|
+
return index_file_content
|
autocoder_nano/templates.py
CHANGED
@@ -0,0 +1,128 @@
|
|
1
|
+
import os
|
2
|
+
import json
|
3
|
+
import uuid
|
4
|
+
from datetime import datetime
|
5
|
+
|
6
|
+
|
7
|
+
# New helper function for cleaning up logs
|
8
|
+
def _cleanup_logs(logs_dir: str, max_files: int = 100):
|
9
|
+
"""
|
10
|
+
Cleans up old log files in the specified directory, keeping only the most recent ones.
|
11
|
+
Log files are expected to follow the naming convention: <YYYYmmdd_HHMMSS>_<uuid>_<suffix>.md
|
12
|
+
"""
|
13
|
+
# logger.debug(f"开始清理日志目录: {logs_dir},最大保留文件数: {max_files}")
|
14
|
+
if not os.path.isdir(logs_dir):
|
15
|
+
# logger.debug(f"日志目录 {logs_dir} 不存在,无需清理。")
|
16
|
+
return
|
17
|
+
|
18
|
+
log_files = []
|
19
|
+
for filename in os.listdir(logs_dir):
|
20
|
+
if filename.endswith(".md"):
|
21
|
+
parts = filename.split('_')
|
22
|
+
# Expected format: <YYYYmmdd_HHMMSS>_<uuid>_<suffix>.md
|
23
|
+
# parts[0] should be the full timestamp string "YYYYmmdd_HHMMSS"
|
24
|
+
if len(parts) >= 2: # At least timestamp and uuid part (suffix might be empty or complex)
|
25
|
+
timestamp_str = parts[0]
|
26
|
+
try:
|
27
|
+
# Validate the timestamp format
|
28
|
+
datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
|
29
|
+
log_files.append((timestamp_str, os.path.join(logs_dir, filename)))
|
30
|
+
except ValueError:
|
31
|
+
# logger.debug(f"文件名 {filename} 的时间戳部分 ({timestamp_str}) 格式不正确,跳过。")
|
32
|
+
continue
|
33
|
+
else:
|
34
|
+
pass
|
35
|
+
# Log the parts for better debugging if needed
|
36
|
+
# logger.debug(f"文件名 {filename} (分割后: {parts}) 不符合预期的下划线分割数量 (至少需要 <timestamp>_<uuid>_...),跳过。")
|
37
|
+
|
38
|
+
# Sort by timestamp (oldest first)
|
39
|
+
log_files.sort(key=lambda x: x[0])
|
40
|
+
|
41
|
+
if len(log_files) > max_files:
|
42
|
+
files_to_delete_count = len(log_files) - max_files
|
43
|
+
# logger.info(f"日志文件数量 ({len(log_files)}) 超过限制 ({max_files}),将删除 {files_to_delete_count} 个最旧的文件。")
|
44
|
+
for i in range(files_to_delete_count):
|
45
|
+
file_to_delete_timestamp, file_to_delete_path = log_files[i]
|
46
|
+
try:
|
47
|
+
os.remove(file_to_delete_path)
|
48
|
+
# logger.info(f"已删除旧日志文件: {file_to_delete_path} (时间戳: {file_to_delete_timestamp})")
|
49
|
+
except OSError as e:
|
50
|
+
pass
|
51
|
+
# logger.warning(f"删除日志文件 {file_to_delete_path} 失败: {str(e)}")
|
52
|
+
# logger.exception(e) # Log stack trace
|
53
|
+
else:
|
54
|
+
pass
|
55
|
+
# logger.debug(f"日志文件数量 ({len(log_files)}) 未超过限制 ({max_files}),无需删除。")
|
56
|
+
|
57
|
+
|
58
|
+
def save_formatted_log(project_root, json_text, suffix):
|
59
|
+
"""
|
60
|
+
Save a JSON log as a formatted markdown file under project_root/.auto-coder/logs/agentic.
|
61
|
+
Filename: <YYYYmmdd_HHMMSS>_<uuid>_<suffix>.md
|
62
|
+
Also cleans up old logs in the directory, keeping the latest 100.
|
63
|
+
Args:
|
64
|
+
project_root (str): The root directory of the project.
|
65
|
+
json_text (str): The JSON string to be formatted and saved.
|
66
|
+
suffix (str): The suffix for the filename.
|
67
|
+
"""
|
68
|
+
# Prepare directory (logs_dir is needed for cleanup first)
|
69
|
+
logs_dir = os.path.join(project_root, ".auto-coder", "logs", "agentic")
|
70
|
+
|
71
|
+
# Cleanup old logs BEFORE saving the new one
|
72
|
+
try:
|
73
|
+
_cleanup_logs(logs_dir) # Default to keep 100 files
|
74
|
+
except Exception as e:
|
75
|
+
pass
|
76
|
+
# logger.warning(f"日志清理过程中发生错误: {str(e)}")
|
77
|
+
# logger.exception(e)
|
78
|
+
# Log cleanup failure should not prevent the main functionality
|
79
|
+
|
80
|
+
# Parse JSON
|
81
|
+
try:
|
82
|
+
data = json.loads(json_text)
|
83
|
+
except Exception as e:
|
84
|
+
# logger.error(f"无效的 JSON 格式: {str(e)}") # Log error before raising
|
85
|
+
# logger.exception(e) # Log stack trace
|
86
|
+
raise ValueError(f"Invalid JSON provided: {e}")
|
87
|
+
|
88
|
+
# Format as markdown with recursive depth
|
89
|
+
def to_markdown(obj, level=1):
|
90
|
+
lines = []
|
91
|
+
if isinstance(obj, dict):
|
92
|
+
for key, value in obj.items():
|
93
|
+
lines.append(f"{'#' * (level + 1)} {key}\n")
|
94
|
+
lines.extend(to_markdown(value, level + 1))
|
95
|
+
elif isinstance(obj, list):
|
96
|
+
for idx, item in enumerate(obj, 1):
|
97
|
+
lines.append(f"{'#' * (level + 1)} Item {idx}\n")
|
98
|
+
lines.extend(to_markdown(item, level + 1))
|
99
|
+
else:
|
100
|
+
lines.append(str(obj) + "\n")
|
101
|
+
return lines
|
102
|
+
|
103
|
+
md_lines = ["# Log Entry\n"]
|
104
|
+
md_lines.extend(to_markdown(data, 1))
|
105
|
+
md_content = "\n".join(md_lines)
|
106
|
+
|
107
|
+
# Ensure directory exists
|
108
|
+
# _cleanup_logs checks if dir exists but does not create it.
|
109
|
+
# os.makedirs needs to be called to ensure the directory for the new log file.
|
110
|
+
os.makedirs(logs_dir, exist_ok=True)
|
111
|
+
|
112
|
+
# Prepare filename
|
113
|
+
now = datetime.now().strftime("%Y%m%d_%H%M%S")
|
114
|
+
unique_id = str(uuid.uuid4())
|
115
|
+
filename = f"{now}_{unique_id}_{suffix}.md"
|
116
|
+
filepath = os.path.join(logs_dir, filename)
|
117
|
+
|
118
|
+
# Save file
|
119
|
+
try:
|
120
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
121
|
+
f.write(md_content)
|
122
|
+
# logger.info(f"日志已保存至: {filepath}")
|
123
|
+
except IOError as e:
|
124
|
+
# logger.error(f"保存日志文件 {filepath} 失败: {str(e)}")
|
125
|
+
# logger.exception(e) # Log stack trace
|
126
|
+
raise # Re-throw the exception so the caller knows saving failed
|
127
|
+
|
128
|
+
return filepath
|
@@ -294,7 +294,7 @@ class Printer:
|
|
294
294
|
|
295
295
|
def print_key_value(
|
296
296
|
self, items: Dict[str, Any], key_style: str = "bold cyan",
|
297
|
-
value_style: str = "green", separator: str = ": ", panel: bool = True
|
297
|
+
value_style: str = "green", separator: str = ": ", panel: bool = True, title: Optional[str] = None
|
298
298
|
) -> None:
|
299
299
|
"""
|
300
300
|
键值对格式化输出
|
@@ -303,6 +303,7 @@ class Printer:
|
|
303
303
|
:param value_style: 值的样式
|
304
304
|
:param separator: 键值分隔符
|
305
305
|
:param panel: 是否用面板包裹
|
306
|
+
:param title: 面板标题
|
306
307
|
"""
|
307
308
|
content = Group(*[
|
308
309
|
Text.assemble(
|
@@ -310,7 +311,7 @@ class Printer:
|
|
310
311
|
(str(v), value_style)
|
311
312
|
) for k, v in items.items()
|
312
313
|
])
|
313
|
-
self._print_with_panel(content, panel)
|
314
|
+
self._print_with_panel(content, panel, title)
|
314
315
|
|
315
316
|
def context_aware_help(
|
316
317
|
self, help_content: Dict[str, str], current_context: str, width: int = 40
|
@@ -336,10 +337,10 @@ class Printer:
|
|
336
337
|
width=width
|
337
338
|
)
|
338
339
|
|
339
|
-
def _print_with_panel(self, content: Any, use_panel: bool) -> None:
|
340
|
+
def _print_with_panel(self, content: Any, use_panel: bool, title: Optional[str] = None) -> None:
|
340
341
|
"""内部方法:根据参数决定是否使用面板包装"""
|
341
342
|
if use_panel:
|
342
|
-
self.print_panel(content)
|
343
|
+
self.print_panel(content, title)
|
343
344
|
else:
|
344
345
|
self.console.print(content)
|
345
346
|
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import os
|
2
|
+
import platform
|
3
|
+
import subprocess
|
4
|
+
import sys
|
5
|
+
|
6
|
+
import psutil
|
7
|
+
|
8
|
+
|
9
|
+
def get_windows_parent_process_name():
|
10
|
+
"""
|
11
|
+
获取当前进程的父进程名(仅在Windows系统下有意义)。
|
12
|
+
|
13
|
+
适用场景:
|
14
|
+
- 判断命令是否由PowerShell或cmd.exe启动,以便调整命令格式。
|
15
|
+
- 在Windows平台上进行父进程分析。
|
16
|
+
|
17
|
+
参数:
|
18
|
+
- 无
|
19
|
+
|
20
|
+
返回:
|
21
|
+
- str|None: 父进程名(小写字符串,如"powershell.exe"或"cmd.exe"),如果无法获取则为None。
|
22
|
+
|
23
|
+
异常:
|
24
|
+
- 捕获所有异常,返回None。
|
25
|
+
"""
|
26
|
+
try:
|
27
|
+
current_process = psutil.Process()
|
28
|
+
while True:
|
29
|
+
parent = current_process.parent()
|
30
|
+
if parent is None:
|
31
|
+
break
|
32
|
+
parent_name = parent.name().lower()
|
33
|
+
if parent_name in ["powershell.exe", "cmd.exe"]:
|
34
|
+
return parent_name
|
35
|
+
current_process = parent
|
36
|
+
return None
|
37
|
+
except Exception:
|
38
|
+
return None
|
39
|
+
|
40
|
+
|
41
|
+
def run_cmd_subprocess(command, verbose=False, cwd=None, encoding=sys.stdout.encoding):
|
42
|
+
if verbose:
|
43
|
+
print("Using run_cmd_subprocess:", command)
|
44
|
+
|
45
|
+
try:
|
46
|
+
shell = os.environ.get("SHELL", "/bin/sh")
|
47
|
+
parent_process = None
|
48
|
+
|
49
|
+
# Determine the appropriate shell
|
50
|
+
if platform.system() == "Windows":
|
51
|
+
parent_process = get_windows_parent_process_name()
|
52
|
+
if parent_process == "powershell.exe":
|
53
|
+
command = f"powershell -Command {command}"
|
54
|
+
|
55
|
+
if verbose:
|
56
|
+
print("Running command:", command)
|
57
|
+
print("SHELL:", shell)
|
58
|
+
if platform.system() == "Windows":
|
59
|
+
print("Parent process:", parent_process)
|
60
|
+
|
61
|
+
process = subprocess.Popen(
|
62
|
+
command,
|
63
|
+
stdout=subprocess.PIPE,
|
64
|
+
stderr=subprocess.STDOUT,
|
65
|
+
text=True,
|
66
|
+
shell=True,
|
67
|
+
encoding=encoding,
|
68
|
+
errors="replace",
|
69
|
+
bufsize=0, # Set bufsize to 0 for unbuffered output
|
70
|
+
universal_newlines=True,
|
71
|
+
cwd=cwd,
|
72
|
+
)
|
73
|
+
|
74
|
+
output = []
|
75
|
+
while True:
|
76
|
+
chunk = process.stdout.read(1)
|
77
|
+
if not chunk:
|
78
|
+
break
|
79
|
+
print(chunk, end="", flush=True) # Print the chunk in real-time
|
80
|
+
output.append(chunk) # Store the chunk for later use
|
81
|
+
|
82
|
+
process.wait()
|
83
|
+
return process.returncode, "".join(output)
|
84
|
+
except Exception as e:
|
85
|
+
return 1, str(e)
|
autocoder_nano/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: autocoder_nano
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.33
|
4
4
|
Summary: AutoCoder Nano
|
5
5
|
Author: moofs
|
6
6
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
@@ -24,10 +24,11 @@ Requires-Dist: uvicorn
|
|
24
24
|
Requires-Dist: pathspec
|
25
25
|
Requires-Dist: fastapi
|
26
26
|
Requires-Dist: tokenizers
|
27
|
-
Requires-Dist: duckdb
|
27
|
+
Requires-Dist: duckdb
|
28
28
|
Requires-Dist: numpy
|
29
29
|
Requires-Dist: requests
|
30
30
|
Requires-Dist: beautifulsoup4
|
31
|
+
Requires-Dist: psutil
|
31
32
|
Dynamic: author
|
32
33
|
Dynamic: classifier
|
33
34
|
Dynamic: description
|