jarvis-ai-assistant 0.3.30__py3-none-any.whl → 0.7.6__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.
Files changed (181) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +458 -152
  3. jarvis/jarvis_agent/agent_manager.py +17 -13
  4. jarvis/jarvis_agent/builtin_input_handler.py +2 -6
  5. jarvis/jarvis_agent/config_editor.py +2 -7
  6. jarvis/jarvis_agent/event_bus.py +82 -12
  7. jarvis/jarvis_agent/file_context_handler.py +329 -0
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +628 -55
  10. jarvis/jarvis_agent/language_extractors/__init__.py +57 -0
  11. jarvis/jarvis_agent/language_extractors/c_extractor.py +21 -0
  12. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +21 -0
  13. jarvis/jarvis_agent/language_extractors/go_extractor.py +21 -0
  14. jarvis/jarvis_agent/language_extractors/java_extractor.py +84 -0
  15. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +79 -0
  16. jarvis/jarvis_agent/language_extractors/python_extractor.py +21 -0
  17. jarvis/jarvis_agent/language_extractors/rust_extractor.py +21 -0
  18. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +84 -0
  19. jarvis/jarvis_agent/language_support_info.py +486 -0
  20. jarvis/jarvis_agent/main.py +34 -10
  21. jarvis/jarvis_agent/memory_manager.py +7 -16
  22. jarvis/jarvis_agent/methodology_share_manager.py +10 -16
  23. jarvis/jarvis_agent/prompt_manager.py +1 -1
  24. jarvis/jarvis_agent/prompts.py +193 -171
  25. jarvis/jarvis_agent/protocols.py +8 -12
  26. jarvis/jarvis_agent/run_loop.py +105 -9
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +20 -22
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  31. jarvis/jarvis_agent/task_analyzer.py +31 -6
  32. jarvis/jarvis_agent/task_manager.py +11 -27
  33. jarvis/jarvis_agent/tool_executor.py +2 -3
  34. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  35. jarvis/jarvis_agent/utils.py +5 -1
  36. jarvis/jarvis_agent/web_bridge.py +189 -0
  37. jarvis/jarvis_agent/web_output_sink.py +53 -0
  38. jarvis/jarvis_agent/web_server.py +786 -0
  39. jarvis/jarvis_c2rust/__init__.py +26 -0
  40. jarvis/jarvis_c2rust/cli.py +575 -0
  41. jarvis/jarvis_c2rust/collector.py +250 -0
  42. jarvis/jarvis_c2rust/constants.py +26 -0
  43. jarvis/jarvis_c2rust/library_replacer.py +1254 -0
  44. jarvis/jarvis_c2rust/llm_module_agent.py +1272 -0
  45. jarvis/jarvis_c2rust/loaders.py +207 -0
  46. jarvis/jarvis_c2rust/models.py +28 -0
  47. jarvis/jarvis_c2rust/optimizer.py +2157 -0
  48. jarvis/jarvis_c2rust/scanner.py +1681 -0
  49. jarvis/jarvis_c2rust/transpiler.py +2983 -0
  50. jarvis/jarvis_c2rust/utils.py +385 -0
  51. jarvis/jarvis_code_agent/build_validation_config.py +132 -0
  52. jarvis/jarvis_code_agent/code_agent.py +1371 -220
  53. jarvis/jarvis_code_agent/code_analyzer/__init__.py +65 -0
  54. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +106 -0
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +74 -0
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +72 -0
  60. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +70 -0
  61. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +53 -0
  62. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +47 -0
  63. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +61 -0
  64. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +110 -0
  65. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +154 -0
  66. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +110 -0
  67. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +153 -0
  68. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  69. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +648 -0
  70. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  71. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  72. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  73. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  74. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  75. jarvis/jarvis_code_agent/code_analyzer/language_support.py +110 -0
  76. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +49 -0
  77. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +299 -0
  78. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +215 -0
  79. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  80. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  81. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +269 -0
  82. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +281 -0
  83. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  84. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +605 -0
  85. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  86. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +252 -0
  87. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +58 -0
  88. jarvis/jarvis_code_agent/lint.py +501 -8
  89. jarvis/jarvis_code_agent/utils.py +141 -0
  90. jarvis/jarvis_code_analysis/code_review.py +493 -584
  91. jarvis/jarvis_data/config_schema.json +128 -12
  92. jarvis/jarvis_git_squash/main.py +4 -5
  93. jarvis/jarvis_git_utils/git_commiter.py +82 -75
  94. jarvis/jarvis_mcp/sse_mcp_client.py +22 -29
  95. jarvis/jarvis_mcp/stdio_mcp_client.py +12 -13
  96. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  97. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  98. jarvis/jarvis_methodology/main.py +32 -48
  99. jarvis/jarvis_multi_agent/__init__.py +287 -55
  100. jarvis/jarvis_multi_agent/main.py +36 -4
  101. jarvis/jarvis_platform/base.py +524 -202
  102. jarvis/jarvis_platform/human.py +7 -8
  103. jarvis/jarvis_platform/kimi.py +30 -36
  104. jarvis/jarvis_platform/openai.py +88 -25
  105. jarvis/jarvis_platform/registry.py +26 -10
  106. jarvis/jarvis_platform/tongyi.py +24 -25
  107. jarvis/jarvis_platform/yuanbao.py +32 -43
  108. jarvis/jarvis_platform_manager/main.py +66 -77
  109. jarvis/jarvis_platform_manager/service.py +8 -13
  110. jarvis/jarvis_rag/cli.py +53 -55
  111. jarvis/jarvis_rag/embedding_manager.py +13 -18
  112. jarvis/jarvis_rag/llm_interface.py +8 -9
  113. jarvis/jarvis_rag/query_rewriter.py +10 -21
  114. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  115. jarvis/jarvis_rag/reranker.py +4 -5
  116. jarvis/jarvis_rag/retriever.py +28 -30
  117. jarvis/jarvis_sec/__init__.py +305 -0
  118. jarvis/jarvis_sec/agents.py +143 -0
  119. jarvis/jarvis_sec/analysis.py +276 -0
  120. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  121. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  122. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  123. jarvis/jarvis_sec/cli.py +139 -0
  124. jarvis/jarvis_sec/clustering.py +1439 -0
  125. jarvis/jarvis_sec/file_manager.py +427 -0
  126. jarvis/jarvis_sec/parsers.py +73 -0
  127. jarvis/jarvis_sec/prompts.py +268 -0
  128. jarvis/jarvis_sec/report.py +336 -0
  129. jarvis/jarvis_sec/review.py +453 -0
  130. jarvis/jarvis_sec/status.py +264 -0
  131. jarvis/jarvis_sec/types.py +20 -0
  132. jarvis/jarvis_sec/utils.py +499 -0
  133. jarvis/jarvis_sec/verification.py +848 -0
  134. jarvis/jarvis_sec/workflow.py +226 -0
  135. jarvis/jarvis_smart_shell/main.py +38 -87
  136. jarvis/jarvis_stats/cli.py +2 -2
  137. jarvis/jarvis_stats/stats.py +8 -8
  138. jarvis/jarvis_stats/storage.py +15 -21
  139. jarvis/jarvis_stats/visualizer.py +1 -1
  140. jarvis/jarvis_tools/clear_memory.py +3 -20
  141. jarvis/jarvis_tools/cli/main.py +21 -23
  142. jarvis/jarvis_tools/edit_file.py +1019 -132
  143. jarvis/jarvis_tools/execute_script.py +83 -25
  144. jarvis/jarvis_tools/file_analyzer.py +6 -9
  145. jarvis/jarvis_tools/generate_new_tool.py +14 -21
  146. jarvis/jarvis_tools/lsp_client.py +1552 -0
  147. jarvis/jarvis_tools/methodology.py +2 -3
  148. jarvis/jarvis_tools/read_code.py +1736 -35
  149. jarvis/jarvis_tools/read_symbols.py +140 -0
  150. jarvis/jarvis_tools/read_webpage.py +12 -13
  151. jarvis/jarvis_tools/registry.py +427 -200
  152. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  153. jarvis/jarvis_tools/rewrite_file.py +72 -158
  154. jarvis/jarvis_tools/save_memory.py +3 -15
  155. jarvis/jarvis_tools/search_web.py +18 -18
  156. jarvis/jarvis_tools/sub_agent.py +36 -43
  157. jarvis/jarvis_tools/sub_code_agent.py +25 -26
  158. jarvis/jarvis_tools/virtual_tty.py +55 -33
  159. jarvis/jarvis_utils/clipboard.py +7 -10
  160. jarvis/jarvis_utils/config.py +232 -45
  161. jarvis/jarvis_utils/embedding.py +8 -5
  162. jarvis/jarvis_utils/fzf.py +8 -8
  163. jarvis/jarvis_utils/git_utils.py +225 -36
  164. jarvis/jarvis_utils/globals.py +3 -3
  165. jarvis/jarvis_utils/http.py +1 -1
  166. jarvis/jarvis_utils/input.py +99 -48
  167. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  168. jarvis/jarvis_utils/methodology.py +52 -48
  169. jarvis/jarvis_utils/utils.py +819 -491
  170. jarvis_ai_assistant-0.7.6.dist-info/METADATA +600 -0
  171. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  172. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +4 -0
  173. jarvis/jarvis_agent/config.py +0 -92
  174. jarvis/jarvis_agent/edit_file_handler.py +0 -296
  175. jarvis/jarvis_platform/ai8.py +0 -332
  176. jarvis/jarvis_tools/ask_user.py +0 -54
  177. jarvis_ai_assistant-0.3.30.dist-info/METADATA +0 -381
  178. jarvis_ai_assistant-0.3.30.dist-info/RECORD +0 -137
  179. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  180. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  181. {jarvis_ai_assistant-0.3.30.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,465 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Jsonnet 兼容层 - 提供类似 json5.loads() 的接口"""
3
+
4
+ import json
5
+ from typing import Any
6
+
7
+ import _jsonnet
8
+
9
+
10
+ def _fix_jsonnet_multiline_strings(s: str) -> tuple[str, dict]:
11
+ """
12
+ 修复 jsonnet ||| 多行字符串的缩进问题。
13
+
14
+ jsonnet 要求 ||| 之后的第一行内容必须有缩进(至少一个空格),
15
+ 否则会报错 "text block's first line must start with whitespace"。
16
+
17
+ 此函数会自动检测并修复这个问题。
18
+
19
+ 参数:
20
+ s: 输入字符串
21
+
22
+ 返回:
23
+ (修复后的字符串, 第一行原始缩进信息字典)
24
+ 缩进信息字典的键是修复后字符串中 ||| 多行字符串的标记,
25
+ 值是第一行的原始缩进级别(如果第一行原本有缩进但后续行没有)
26
+ """
27
+ if not isinstance(s, str):
28
+ return s
29
+
30
+ import re
31
+
32
+ # 匹配 ||| 多行字符串模式
33
+ # 格式:||| 后跟可选空白和换行,然后是内容,最后是 |||
34
+ # 使用非贪婪匹配,确保匹配到最近的 |||
35
+ pattern = r'(\|\|\|)(\s*\n)(.*?)(\n\s*\|\|\|)'
36
+
37
+ def fix_match(match):
38
+ start_marker = match.group(1) # |||
39
+ whitespace_after = match.group(2) # 空白和换行
40
+ content = match.group(3) # 多行内容
41
+ end_marker = match.group(4) # 换行、空白和 |||
42
+
43
+ # jsonnet 要求结束标记 ||| 必须单独一行且没有缩进(从行首开始)
44
+ # 无论原来是什么格式,统一修复为 '\n|||'
45
+ end_marker = '\n|||'
46
+
47
+
48
+ # 如果内容为空,返回修复后的结束标记
49
+ if not content.strip():
50
+ return start_marker + whitespace_after + content + end_marker, {}
51
+
52
+ # 按行分割内容
53
+
54
+ lines = content.split('\n')
55
+
56
+ # 确定缩进级别:
57
+ # 1. 如果第一行有缩进,使用第一行的缩进级别
58
+ # 2. 如果第一行是空行,查找第一个非空行的缩进级别
59
+ # 3. 如果第一行没有缩进,使用默认的一个空格
60
+ # 4. 如果所有行都为空,使用默认的一个空格
61
+ indent_level = 1 # 默认缩进级别
62
+ first_line_has_indent = False
63
+ first_line_indent = 0
64
+
65
+ if lines:
66
+ first_line = lines[0]
67
+ if first_line.strip() and first_line.startswith((' ', '\t')):
68
+ # 第一行已有缩进,记录其缩进级别
69
+ first_line_indent = len(first_line) - len(first_line.lstrip())
70
+ first_line_has_indent = True
71
+ indent_level = first_line_indent
72
+ # 确保至少有一个空格
73
+ if indent_level == 0:
74
+ indent_level = 1
75
+ elif not first_line.strip():
76
+ # 第一行是空行,查找第一个非空行的缩进级别
77
+ for line in lines[1:]:
78
+ if line.strip():
79
+ if line.startswith((' ', '\t')):
80
+ # 找到第一个非空行,使用其缩进级别
81
+ indent_level = len(line) - len(line.lstrip())
82
+ # 确保至少有一个空格
83
+ if indent_level == 0:
84
+ indent_level = 1
85
+ else:
86
+ # 第一个非空行没有缩进,使用默认的一个空格
87
+ indent_level = 1
88
+ break
89
+
90
+ # 对每一行都统一缩进级别
91
+ # jsonnet 的 ||| 要求所有行都有相同的缩进级别,并会去除所有行的最小共同缩进前缀
92
+ # 为了保留第一行的缩进,我们需要:
93
+ # 1. 让所有行都有相同的缩进(满足 jsonnet 的要求)
94
+ # 2. 记录第一行的原始缩进级别,以便在解析后恢复
95
+ # 3. 在解析后,为第一行添加原始缩进
96
+
97
+ # 检查是否有后续行没有缩进(需要修复)
98
+ has_unindented_lines = False
99
+ if first_line_has_indent:
100
+ for line in lines[1:]:
101
+ if line.strip() and not line.startswith((' ', '\t')):
102
+ has_unindented_lines = True
103
+ break
104
+
105
+ # 记录所有行的原始缩进信息(用于恢复)
106
+ # 如果存在不同缩进级别的行,我们需要记录每行的原始缩进
107
+ original_indents = {} # 键:行内容(去除缩进后),值:原始缩进级别
108
+ has_mixed_indents = False
109
+
110
+ # 记录所有行的原始缩进信息(无论是否混合缩进,都需要记录以便恢复)
111
+ if lines:
112
+ seen_indents = set()
113
+ for line in lines:
114
+
115
+ if line.strip():
116
+ line_indent = len(line) - len(line.lstrip())
117
+ seen_indents.add(line_indent)
118
+ line_content = line.lstrip()
119
+ # 记录原始缩进(包括0缩进的情况,用特殊值标记)
120
+ original_indents[line_content] = line_indent
121
+
122
+ # 如果有多个不同的缩进级别,说明是混合缩进
123
+ if len(seen_indents) > 1:
124
+ has_mixed_indents = True
125
+
126
+
127
+ # 如果第一行有缩进,但后续行没有,我们也需要记录
128
+ if first_line_has_indent and has_unindented_lines:
129
+ has_mixed_indents = True
130
+ # 记录第一行的原始缩进
131
+ first_line_content = lines[0].lstrip()
132
+ original_indents[first_line_content] = first_line_indent
133
+
134
+ # jsonnet的text block规则:所有行缩进必须 >= 首行缩进
135
+ # 因此我们统一所有行为相同的基础缩进,通过恢复逻辑还原原始缩进
136
+ base_indent = 1 # 统一使用1空格缩进
137
+
138
+ # 统一所有行的缩进为基础缩进(满足jsonnet要求)
139
+
140
+ for i in range(len(lines)):
141
+ line = lines[i]
142
+ if line.strip(): # 只处理非空行
143
+ line_content = line.lstrip()
144
+ lines[i] = ' ' * base_indent + line_content
145
+
146
+
147
+ # 重新组合内容
148
+ fixed_content = '\n'.join(lines)
149
+
150
+ # 返回修复后的内容和原始缩进信息
151
+ # 只要有混合缩进,就返回缩进信息以便恢复
152
+ indent_info = original_indents if has_mixed_indents else {}
153
+
154
+ return start_marker + whitespace_after + fixed_content + end_marker, indent_info
155
+
156
+
157
+ # 使用 DOTALL 标志,使 . 匹配换行符
158
+ # 收集所有修复后的内容和缩进信息
159
+ all_indent_info = {}
160
+ fixed_parts = []
161
+ last_end = 0
162
+
163
+ for match in re.finditer(pattern, s, flags=re.DOTALL):
164
+ # 添加匹配前的部分
165
+ fixed_parts.append(s[last_end:match.start()])
166
+
167
+ # 修复匹配的部分
168
+ fixed_content, indent_info = fix_match(match)
169
+ fixed_parts.append(fixed_content)
170
+
171
+ # 合并缩进信息
172
+ all_indent_info.update(indent_info)
173
+
174
+ last_end = match.end()
175
+
176
+ # 添加剩余部分
177
+ fixed_parts.append(s[last_end:])
178
+ fixed = ''.join(fixed_parts)
179
+
180
+ return fixed, all_indent_info
181
+
182
+
183
+ def _restore_first_line_indent(obj: Any, indent_info: dict) -> Any:
184
+ """
185
+ 恢复第一行的原始缩进。
186
+
187
+ 参数:
188
+ obj: 解析后的对象
189
+ indent_info: 缩进信息字典
190
+
191
+ 返回:
192
+ 恢复缩进后的对象
193
+ """
194
+ if not indent_info:
195
+ return obj
196
+
197
+ if isinstance(obj, dict):
198
+ return {k: _restore_first_line_indent(v, indent_info) for k, v in obj.items()}
199
+ elif isinstance(obj, list):
200
+ return [_restore_first_line_indent(item, indent_info) for item in obj]
201
+ elif isinstance(obj, str):
202
+ # 检查字符串的每一行是否原本有缩进
203
+ # 如果存在原始缩进信息,恢复每行的原始缩进
204
+ if indent_info:
205
+ lines = obj.split('\n')
206
+ restored_lines = []
207
+ for line in lines:
208
+ if line.strip():
209
+ # 去除当前行的缩进,获取内容
210
+ line_content = line.lstrip()
211
+ # 检查是否有对应的原始缩进信息
212
+ if line_content in indent_info:
213
+ # 恢复原始缩进
214
+ original_indent = indent_info[line_content]
215
+ restored_lines.append(' ' * original_indent + line_content)
216
+ else:
217
+ # 没有原始缩进信息,保持原样
218
+ restored_lines.append(line)
219
+ else:
220
+ # 空行保持原样
221
+ restored_lines.append(line)
222
+ return '\n'.join(restored_lines)
223
+ return obj
224
+ else:
225
+ return obj
226
+
227
+
228
+ def _convert_backtick_multiline_strings(s: str) -> str:
229
+ """
230
+ 将 JSON 值中的 ``` 多行字符串标识转换为 |||。
231
+
232
+ 此函数识别 JSON 值位置(如 "key": ```)的 ``` 标记,并将其转换为 |||,
233
+ 以便与 jsonnet 的 ||| 多行字符串语法兼容。
234
+
235
+ 识别规则:
236
+ - 在 JSON 值位置(冒号后)的 ``` 会被转换为 |||
237
+ - 已经去除 markdown 代码块标记后,剩余的 ``` 通常是多行字符串标识
238
+
239
+ 参数:
240
+ s: 输入字符串(应该已经去除 markdown 代码块标记)
241
+
242
+ 返回:
243
+ 转换后的字符串(``` 转换为 |||)
244
+ """
245
+ if not isinstance(s, str):
246
+ return s
247
+
248
+ import re
249
+
250
+ # 匹配 JSON 值中的 ``` 多行字符串
251
+ # 格式:": ``` 或 ":``` 后跟可选空白和换行,然后是内容,最后是换行和 ```
252
+ # 使用非贪婪匹配,确保匹配到最近的 ```
253
+ # 注意:这个模式匹配的是 JSON 值位置(冒号后)的 ```
254
+ pattern = r'(:\s*)(```)(\s*\n)(.*?)(\n\s*```)'
255
+
256
+ def convert_match(match):
257
+ colon = match.group(1) # 冒号和可选空白
258
+ match.group(2) # ``` (保留用于匹配,但不使用)
259
+ whitespace_after = match.group(3) # 空白和换行
260
+ content = match.group(4) # 多行内容
261
+ match.group(5) # 换行、空白和 ``` (保留用于匹配,但不使用)
262
+
263
+ # 将 ``` 转换为 |||
264
+ return colon + '|||' + whitespace_after + content + '\n|||'
265
+
266
+ # 替换所有匹配的模式
267
+ result = re.sub(pattern, convert_match, s, flags=re.DOTALL)
268
+
269
+ return result
270
+
271
+
272
+ def _strip_markdown_code_blocks(s: str) -> str:
273
+ """
274
+ 去除字符串中的 markdown 代码块标记(如 ```json5、```json、``` 等)
275
+
276
+ 支持以下场景:
277
+ - 代码块前后有空白/换行:\n```json\n{...}\n```
278
+ - 代码块不在字符串开头:prefix\n```json\n{...}\n```
279
+ - 标准格式:```json\n{...}\n```
280
+
281
+ 参数:
282
+ s: 输入字符串
283
+
284
+ 返回:
285
+ 清理后的字符串
286
+ """
287
+ if not isinstance(s, str):
288
+ return s
289
+
290
+ import re
291
+
292
+ # 先去除首尾空白,但保留内部结构
293
+ block = s.strip()
294
+
295
+ # 使用正则表达式匹配并去除代码块标记
296
+ # 尝试多种模式,从严格到宽松
297
+
298
+ # 模式1:标准格式,代码块在开头和结尾
299
+ # 匹配:```language + 可选空白 + 可选换行 + 内容 + 可选换行 + 可选空白 + ```
300
+ pattern1 = r'^```[a-zA-Z0-9_+-]*\s*\n?(.*?)\n?\s*```\s*$'
301
+ match = re.match(pattern1, block, re.DOTALL)
302
+ if match:
303
+ return match.group(1).strip()
304
+
305
+ # 模式2:代码块前后可能有额外空白/换行,但要求代码块在字符串的开头或结尾
306
+ # 只匹配整个字符串被代码块包裹的情况,不匹配 JSON 值内部的 ```
307
+ # 匹配:字符串开头(可选空白)+ ```language + 可选空白 + 换行 + 内容 + 换行 + 可选空白 + ``` + 字符串结尾(可选空白)
308
+ pattern2 = r'^\s*```[a-zA-Z0-9_+-]*\s*\n(.*?)\n\s*```\s*$'
309
+ match = re.match(pattern2, block, re.DOTALL)
310
+ if match:
311
+ return match.group(1).strip()
312
+
313
+ # 模式3:更宽松的匹配,不要求换行,但要求代码块在字符串的开头或结尾
314
+ # 只匹配整个字符串被代码块包裹的情况,不匹配 JSON 值内部的 ```
315
+ # 匹配:字符串开头(可选空白)+ ```language + 可选空白 + 内容 + 可选空白 + ``` + 字符串结尾(可选空白)
316
+ pattern3 = r'^\s*```[a-zA-Z0-9_+-]*\s*(.*?)\s*```\s*$'
317
+ match = re.match(pattern3, block, re.DOTALL)
318
+ if match:
319
+ return match.group(1).strip()
320
+
321
+ # 如果正则都不匹配,尝试手动去除(向后兼容)
322
+ # 但只处理整个字符串被代码块包裹的情况(代码块在开头且结尾也有 ```)
323
+ block_stripped = block.strip()
324
+ if block_stripped.startswith("```") and block_stripped.rstrip().endswith("```"):
325
+ # 找到开头的 ``` 后的内容
326
+ after_start = 3 # 跳过 ```
327
+ # 跳过语言标识(如果有)
328
+ while after_start < len(block_stripped) and block_stripped[after_start] not in ('\n', '\r', ' ', '\t'):
329
+ after_start += 1
330
+ # 跳过空白字符
331
+ while after_start < len(block_stripped) and block_stripped[after_start] in (' ', '\t'):
332
+ after_start += 1
333
+ # 跳过换行符(如果有)
334
+ if after_start < len(block_stripped) and block_stripped[after_start] in ('\n', '\r'):
335
+ after_start += 1
336
+ # 处理 \r\n
337
+ if after_start < len(block_stripped) and block_stripped[after_start] == '\n' and block_stripped[after_start - 1] == '\r':
338
+ after_start += 1
339
+
340
+ # 找到结尾的 ``` 的位置
341
+ before_end = block_stripped.rfind("```")
342
+ if before_end > after_start:
343
+ # 提取内容(去除结尾的 ``` 和前面的空白)
344
+ content = block_stripped[after_start:before_end].rstrip()
345
+ return content
346
+
347
+ return block.strip()
348
+
349
+
350
+ def loads(s: str) -> Any:
351
+ """
352
+ 解析 JSON/Jsonnet 格式的字符串,返回 Python 对象
353
+
354
+ 使用 jsonnet 来解析,支持 JSON5 特性(注释、尾随逗号、||| 或 ``` 分隔符多行字符串等)
355
+
356
+ 自动处理:
357
+ - markdown 代码块标记:如果输入包含 ```json5、```json、``` 等代码块标记,
358
+ 会自动去除这些标记后再解析。
359
+ - ``` 多行字符串:支持使用 ``` 代替 ||| 作为多行字符串标识(在 JSON 值位置)。
360
+ - ||| 多行字符串缩进:自动为 ||| 多行字符串的第一行添加必要的缩进,
361
+ 避免 "text block's first line must start with whitespace" 错误。
362
+
363
+ 参数:
364
+ s: 要解析的字符串(可能包含 markdown 代码块标记)
365
+
366
+ 返回:
367
+ 解析后的 Python 对象
368
+
369
+ 异常:
370
+ ValueError: 如果解析失败
371
+ """
372
+ if not isinstance(s, str) or not s.strip():
373
+ raise ValueError("输入字符串为空")
374
+
375
+ # 自动去除 markdown 代码块标记
376
+ cleaned = _strip_markdown_code_blocks(s)
377
+
378
+ # 验证:确保没有残留的代码块标记(在字符串开头或结尾)
379
+ # 字符串内容中的 ``` 是合法的,不需要处理
380
+ cleaned_stripped = cleaned.strip()
381
+ if cleaned_stripped.startswith("```") or cleaned_stripped.rstrip().endswith("```"):
382
+ # 如果还有代码块标记,可能是手动去除逻辑没有正确工作
383
+ # 再次尝试去除(防止边界情况)
384
+ cleaned = _strip_markdown_code_blocks(cleaned)
385
+ cleaned_stripped = cleaned.strip()
386
+ # 如果仍然有,说明可能是格式问题,记录警告但继续处理
387
+ if cleaned_stripped.startswith("```") or cleaned_stripped.rstrip().endswith("```"):
388
+ # 最后尝试:手动去除开头和结尾的 ```
389
+ while cleaned_stripped.startswith("```"):
390
+ # 找到第一个换行或字符串结尾
391
+ first_newline = cleaned_stripped.find("\n", 3)
392
+ if first_newline >= 0:
393
+ cleaned_stripped = cleaned_stripped[first_newline + 1:]
394
+ else:
395
+ # 没有换行,可能是 ```language 格式
396
+ cleaned_stripped = cleaned_stripped[3:].lstrip()
397
+ # 跳过语言标识
398
+ while cleaned_stripped and cleaned_stripped[0] not in ('\n', '\r', ' ', '\t'):
399
+ cleaned_stripped = cleaned_stripped[1:]
400
+ break
401
+ while cleaned_stripped.rstrip().endswith("```"):
402
+ last_backticks = cleaned_stripped.rfind("```")
403
+ if last_backticks >= 0:
404
+ cleaned_stripped = cleaned_stripped[:last_backticks].rstrip()
405
+ else:
406
+ break
407
+ cleaned = cleaned_stripped
408
+
409
+ # 将 JSON 值中的 ``` 多行字符串标识转换为 |||
410
+ cleaned = _convert_backtick_multiline_strings(cleaned)
411
+
412
+ # 自动修复 ||| 多行字符串的缩进问题
413
+ cleaned, indent_info = _fix_jsonnet_multiline_strings(cleaned)
414
+
415
+ # 使用 jsonnet 解析,支持 JSON5 和 Jsonnet 语法
416
+ try:
417
+ result_json = _jsonnet.evaluate_snippet("<input>", cleaned)
418
+ except RuntimeError as e:
419
+ # 提供更详细的错误信息
420
+ error_msg = str(e)
421
+ if "Could not lex the character" in error_msg or "`" in error_msg:
422
+ # 检查是否还有残留的代码块标记
423
+ if "```" in cleaned:
424
+ # 找到所有 ``` 的位置
425
+ import re
426
+ matches = list(re.finditer(r'```', cleaned))
427
+ for match in matches:
428
+ pos = match.start()
429
+ context = cleaned[max(0, pos-30):min(len(cleaned), pos+50)]
430
+ # 检查是否在字符串内部(被引号包围)
431
+ before = cleaned[:pos]
432
+ # 简单检查:如果前面有奇数个引号,说明在字符串内部
433
+ quote_count = before.count('"') - before.count('\\"')
434
+ if quote_count % 2 == 0:
435
+ # 不在字符串内部,可能是残留的代码块标记
436
+ raise ValueError(
437
+ f"检测到残留的代码块标记 ``` 在位置 {pos}。"
438
+ f"上下文: {repr(context)}。"
439
+ f"原始错误: {error_msg}"
440
+ )
441
+ raise ValueError(f"JSON 解析失败: {error_msg}")
442
+
443
+ # jsonnet 返回的是 JSON 字符串,需要再次解析
444
+ result = json.loads(result_json)
445
+
446
+ # 如果第一行原本有缩进,恢复第一行的缩进
447
+ if indent_info:
448
+ result = _restore_first_line_indent(result, indent_info)
449
+
450
+ return result
451
+
452
+
453
+ def dumps(obj: Any, **kwargs) -> str:
454
+ """
455
+ 将 Python 对象序列化为 JSON 字符串
456
+
457
+ 参数:
458
+ obj: 要序列化的对象
459
+ **kwargs: 传递给 json.dumps 的其他参数
460
+
461
+ 返回:
462
+ JSON 字符串
463
+ """
464
+ return json.dumps(obj, **kwargs)
465
+
@@ -20,7 +20,6 @@ from jarvis.jarvis_utils.config import (
20
20
  get_central_methodology_repo,
21
21
  get_max_input_token_count,
22
22
  )
23
- from jarvis.jarvis_utils.output import OutputType, PrettyOutput
24
23
  from jarvis.jarvis_utils.utils import daily_check_git_updates
25
24
  from jarvis.jarvis_utils.embedding import get_context_token_count
26
25
 
@@ -37,7 +36,7 @@ def _get_methodology_directory() -> str:
37
36
  try:
38
37
  os.makedirs(methodology_dir, exist_ok=True)
39
38
  except Exception as e:
40
- PrettyOutput.print(f"创建方法论目录失败: {str(e)}", OutputType.ERROR)
39
+ print(f"创建方法论目录失败: {str(e)}")
41
40
  return methodology_dir
42
41
 
43
42
 
@@ -54,25 +53,27 @@ def _load_all_methodologies() -> Dict[str, str]:
54
53
  # 如果配置了中心方法论仓库,将其添加到加载路径
55
54
  central_repo = get_central_methodology_repo()
56
55
  if central_repo:
57
- # 中心方法论仓库存储在数据目录下的特定位置
58
- central_repo_path = os.path.join(get_data_dir(), "central_methodology_repo")
59
- methodology_dirs.append(central_repo_path)
60
-
61
- # 确保中心方法论仓库被克隆/更新
62
- if not os.path.exists(central_repo_path):
63
- try:
64
- import subprocess
65
-
66
- PrettyOutput.print(
67
- f"正在克隆中心方法论仓库: {central_repo}", OutputType.INFO
68
- )
69
- subprocess.run(
70
- ["git", "clone", central_repo, central_repo_path], check=True
71
- )
72
- except Exception as e:
73
- PrettyOutput.print(
74
- f"克隆中心方法论仓库失败: {str(e)}", OutputType.ERROR
75
- )
56
+ # 支持本地目录路径或Git仓库URL
57
+ expanded = os.path.expanduser(os.path.expandvars(central_repo))
58
+ if os.path.isdir(expanded):
59
+ # 直接使用本地目录(支持Git仓库的子目录)
60
+ methodology_dirs.append(expanded)
61
+ else:
62
+ # 中心方法论仓库存储在数据目录下的特定位置
63
+ central_repo_path = os.path.join(get_data_dir(), "central_methodology_repo")
64
+ methodology_dirs.append(central_repo_path)
65
+
66
+ # 确保中心方法论仓库被克隆/更新
67
+ if not os.path.exists(central_repo_path):
68
+ try:
69
+ import subprocess
70
+
71
+ print(f"ℹ️ 正在克隆中心方法论仓库: {central_repo}")
72
+ subprocess.run(
73
+ ["git", "clone", central_repo, central_repo_path], check=True
74
+ )
75
+ except Exception as e:
76
+ print(f"❌ 克隆中心方法论仓库失败: {str(e)}")
76
77
 
77
78
  # --- 全局每日更新检查 ---
78
79
  daily_check_git_updates(methodology_dirs, "methodologies")
@@ -104,9 +105,9 @@ def _load_all_methodologies() -> Dict[str, str]:
104
105
 
105
106
  # 统一打印目录警告与文件加载失败信息
106
107
  if warn_dirs:
107
- PrettyOutput.print("\n".join(warn_dirs), OutputType.WARNING)
108
+ print("⚠️ " + "\n⚠️ ".join(warn_dirs))
108
109
  if error_lines:
109
- PrettyOutput.print("\n".join(error_lines), OutputType.WARNING)
110
+ print("⚠️ " + "\n⚠️ ".join(error_lines))
110
111
  return all_methodologies
111
112
 
112
113
 
@@ -139,7 +140,7 @@ def _create_methodology_temp_file(methodologies: Dict[str, str]) -> Optional[str
139
140
 
140
141
  return temp_path
141
142
  except Exception as e:
142
- PrettyOutput.print(f"创建方法论临时文件失败: {str(e)}", OutputType.ERROR)
143
+ print(f"创建方法论临时文件失败: {str(e)}")
143
144
  return None
144
145
 
145
146
 
@@ -155,12 +156,12 @@ def upload_methodology(platform: BasePlatform, other_files: List[str] = []) -> b
155
156
  """
156
157
  methodology_dir = _get_methodology_directory()
157
158
  if not os.path.exists(methodology_dir):
158
- PrettyOutput.print("方法论文档不存在", OutputType.WARNING)
159
+ print("⚠️ 方法论文档不存在")
159
160
  return False
160
161
 
161
162
  methodologies = _load_all_methodologies()
162
163
  if not methodologies:
163
- PrettyOutput.print("没有可用的方法论文档", OutputType.WARNING)
164
+ print("⚠️ 没有可用的方法论文档")
164
165
  return False
165
166
 
166
167
  temp_file_path = _create_methodology_temp_file(methodologies)
@@ -206,24 +207,23 @@ def load_methodology(
206
207
 
207
208
  try:
208
209
  # 加载所有方法论
209
- PrettyOutput.print("📁 加载方法论文件...", OutputType.INFO)
210
+ print("📁 加载方法论文件...")
210
211
  methodologies = _load_all_methodologies()
211
212
  if not methodologies:
212
- PrettyOutput.print("没有找到方法论文件", OutputType.WARNING)
213
+ print("⚠️ 没有找到方法论文件")
213
214
  return ""
214
- PrettyOutput.print(
215
- f"加载方法论文件完成 (共 {len(methodologies)} 个)", OutputType.SUCCESS
216
- )
215
+ print(f"✅ 加载方法论文件完成 (共 {len(methodologies)} 个)")
217
216
 
218
217
  if platform_name:
219
218
  platform = PlatformRegistry().create_platform(platform_name)
220
219
  if platform and model_name:
221
220
  platform.set_model_name(model_name)
222
221
  else:
223
- platform = PlatformRegistry().get_normal_platform()
222
+ # 方法论推荐使用cheap模型以降低成本
223
+ platform = PlatformRegistry().get_cheap_platform()
224
224
 
225
225
  if not platform:
226
- PrettyOutput.print("无法创建平台实例", OutputType.ERROR)
226
+ print("无法创建平台实例")
227
227
  return ""
228
228
 
229
229
  platform.set_suppress_output(True)
@@ -297,9 +297,21 @@ def load_methodology(
297
297
  if not selected_methodologies:
298
298
  return "没有历史方法论可参考"
299
299
 
300
- # 获取最大输入token数的2/3作为方法论的token限制
301
- max_input_tokens = get_max_input_token_count()
302
- methodology_token_limit = int(max_input_tokens * 2 / 3)
300
+ # 优先使用剩余token数量,回退到输入窗口限制
301
+ methodology_token_limit = None
302
+ try:
303
+ remaining_tokens = platform.get_remaining_token_count()
304
+ # 使用剩余token的2/3作为限制,保留1/3作为安全余量
305
+ methodology_token_limit = int(remaining_tokens * 2 / 3)
306
+ if methodology_token_limit <= 0:
307
+ methodology_token_limit = None
308
+ except Exception:
309
+ pass
310
+
311
+ # 回退方案:使用输入窗口的2/3
312
+ if methodology_token_limit is None:
313
+ max_input_tokens = get_max_input_token_count()
314
+ methodology_token_limit = int(max_input_tokens * 2 / 3)
303
315
 
304
316
  # 步骤3:将选择出来的方法论内容提供给大模型生成步骤
305
317
  # 首先构建基础提示词部分
@@ -341,10 +353,7 @@ def load_methodology(
341
353
 
342
354
  # 检查是否会超过token限制
343
355
  if total_methodology_tokens + methodology_tokens > available_tokens:
344
- PrettyOutput.print(
345
- f"达到方法论token限制 ({total_methodology_tokens}/{available_tokens}),停止加载更多方法论",
346
- OutputType.INFO,
347
- )
356
+ print(f"ℹ️ 达到方法论token限制 ({total_methodology_tokens}/{available_tokens}),停止加载更多方法论")
348
357
  break
349
358
 
350
359
  final_prompt += methodology_text
@@ -353,21 +362,16 @@ def load_methodology(
353
362
 
354
363
  # 如果一个方法论都没有加载成功
355
364
  if selected_count == 0:
356
- PrettyOutput.print(
357
- "警告:由于token限制,无法加载任何方法论内容", OutputType.WARNING
358
- )
365
+ print("⚠️ 警告:由于token限制,无法加载任何方法论内容")
359
366
  return "没有历史方法论可参考"
360
367
 
361
368
  final_prompt += suffix_prompt
362
369
 
363
- PrettyOutput.print(
364
- f"成功加载 {selected_count} 个方法论,总token数: {total_methodology_tokens}",
365
- OutputType.INFO,
366
- )
370
+ print(f"ℹ️ 成功加载 {selected_count} 个方法论,总token数: {total_methodology_tokens}")
367
371
 
368
372
  # 如果内容不大,直接使用chat_until_success
369
373
  return platform.chat_until_success(final_prompt)
370
374
 
371
375
  except Exception as e:
372
- PrettyOutput.print(f"加载方法论失败: {str(e)}", OutputType.ERROR)
376
+ print(f"加载方法论失败: {str(e)}")
373
377
  return ""