jarvis-ai-assistant 0.7.0__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 (159) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +243 -139
  3. jarvis/jarvis_agent/agent_manager.py +5 -10
  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 +265 -15
  8. jarvis/jarvis_agent/file_methodology_manager.py +3 -4
  9. jarvis/jarvis_agent/jarvis.py +113 -98
  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 +6 -12
  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 +77 -14
  27. jarvis/jarvis_agent/session_manager.py +2 -3
  28. jarvis/jarvis_agent/share_manager.py +12 -21
  29. jarvis/jarvis_agent/shell_input_handler.py +1 -2
  30. jarvis/jarvis_agent/task_analyzer.py +26 -4
  31. jarvis/jarvis_agent/task_manager.py +11 -27
  32. jarvis/jarvis_agent/tool_executor.py +2 -3
  33. jarvis/jarvis_agent/tool_share_manager.py +12 -24
  34. jarvis/jarvis_agent/web_server.py +55 -20
  35. jarvis/jarvis_c2rust/__init__.py +5 -5
  36. jarvis/jarvis_c2rust/cli.py +461 -499
  37. jarvis/jarvis_c2rust/collector.py +45 -53
  38. jarvis/jarvis_c2rust/constants.py +26 -0
  39. jarvis/jarvis_c2rust/library_replacer.py +264 -132
  40. jarvis/jarvis_c2rust/llm_module_agent.py +162 -190
  41. jarvis/jarvis_c2rust/loaders.py +207 -0
  42. jarvis/jarvis_c2rust/models.py +28 -0
  43. jarvis/jarvis_c2rust/optimizer.py +1592 -395
  44. jarvis/jarvis_c2rust/transpiler.py +1722 -1064
  45. jarvis/jarvis_c2rust/utils.py +385 -0
  46. jarvis/jarvis_code_agent/build_validation_config.py +2 -3
  47. jarvis/jarvis_code_agent/code_agent.py +394 -320
  48. jarvis/jarvis_code_agent/code_analyzer/__init__.py +3 -0
  49. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +4 -0
  50. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +17 -2
  51. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +3 -0
  52. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +36 -4
  53. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +9 -0
  54. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +9 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +12 -1
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +22 -5
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +57 -32
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +62 -6
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +8 -9
  60. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +290 -5
  61. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -0
  62. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +21 -3
  63. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +72 -4
  64. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +35 -3
  65. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +212 -0
  66. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +254 -0
  67. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +52 -2
  68. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +73 -1
  69. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +280 -0
  70. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +306 -152
  71. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +556 -0
  72. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +193 -18
  73. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +18 -8
  74. jarvis/jarvis_code_agent/lint.py +258 -27
  75. jarvis/jarvis_code_agent/utils.py +0 -1
  76. jarvis/jarvis_code_analysis/code_review.py +19 -24
  77. jarvis/jarvis_data/config_schema.json +53 -26
  78. jarvis/jarvis_git_squash/main.py +4 -5
  79. jarvis/jarvis_git_utils/git_commiter.py +44 -49
  80. jarvis/jarvis_mcp/sse_mcp_client.py +20 -27
  81. jarvis/jarvis_mcp/stdio_mcp_client.py +11 -12
  82. jarvis/jarvis_mcp/streamable_mcp_client.py +15 -14
  83. jarvis/jarvis_memory_organizer/memory_organizer.py +55 -74
  84. jarvis/jarvis_methodology/main.py +32 -48
  85. jarvis/jarvis_multi_agent/__init__.py +79 -61
  86. jarvis/jarvis_multi_agent/main.py +3 -7
  87. jarvis/jarvis_platform/base.py +469 -199
  88. jarvis/jarvis_platform/human.py +7 -8
  89. jarvis/jarvis_platform/kimi.py +30 -36
  90. jarvis/jarvis_platform/openai.py +65 -27
  91. jarvis/jarvis_platform/registry.py +26 -10
  92. jarvis/jarvis_platform/tongyi.py +24 -25
  93. jarvis/jarvis_platform/yuanbao.py +31 -42
  94. jarvis/jarvis_platform_manager/main.py +66 -77
  95. jarvis/jarvis_platform_manager/service.py +8 -13
  96. jarvis/jarvis_rag/cli.py +49 -51
  97. jarvis/jarvis_rag/embedding_manager.py +13 -18
  98. jarvis/jarvis_rag/llm_interface.py +8 -9
  99. jarvis/jarvis_rag/query_rewriter.py +10 -21
  100. jarvis/jarvis_rag/rag_pipeline.py +24 -27
  101. jarvis/jarvis_rag/reranker.py +4 -5
  102. jarvis/jarvis_rag/retriever.py +28 -30
  103. jarvis/jarvis_sec/__init__.py +220 -3520
  104. jarvis/jarvis_sec/agents.py +143 -0
  105. jarvis/jarvis_sec/analysis.py +276 -0
  106. jarvis/jarvis_sec/cli.py +29 -6
  107. jarvis/jarvis_sec/clustering.py +1439 -0
  108. jarvis/jarvis_sec/file_manager.py +427 -0
  109. jarvis/jarvis_sec/parsers.py +73 -0
  110. jarvis/jarvis_sec/prompts.py +268 -0
  111. jarvis/jarvis_sec/report.py +83 -4
  112. jarvis/jarvis_sec/review.py +453 -0
  113. jarvis/jarvis_sec/utils.py +499 -0
  114. jarvis/jarvis_sec/verification.py +848 -0
  115. jarvis/jarvis_sec/workflow.py +7 -0
  116. jarvis/jarvis_smart_shell/main.py +38 -87
  117. jarvis/jarvis_stats/cli.py +1 -1
  118. jarvis/jarvis_stats/stats.py +7 -7
  119. jarvis/jarvis_stats/storage.py +15 -21
  120. jarvis/jarvis_tools/clear_memory.py +3 -20
  121. jarvis/jarvis_tools/cli/main.py +20 -23
  122. jarvis/jarvis_tools/edit_file.py +1066 -0
  123. jarvis/jarvis_tools/execute_script.py +42 -21
  124. jarvis/jarvis_tools/file_analyzer.py +6 -9
  125. jarvis/jarvis_tools/generate_new_tool.py +11 -20
  126. jarvis/jarvis_tools/lsp_client.py +1552 -0
  127. jarvis/jarvis_tools/methodology.py +2 -3
  128. jarvis/jarvis_tools/read_code.py +1525 -87
  129. jarvis/jarvis_tools/read_symbols.py +2 -3
  130. jarvis/jarvis_tools/read_webpage.py +7 -10
  131. jarvis/jarvis_tools/registry.py +370 -181
  132. jarvis/jarvis_tools/retrieve_memory.py +20 -19
  133. jarvis/jarvis_tools/rewrite_file.py +105 -0
  134. jarvis/jarvis_tools/save_memory.py +3 -15
  135. jarvis/jarvis_tools/search_web.py +3 -7
  136. jarvis/jarvis_tools/sub_agent.py +17 -6
  137. jarvis/jarvis_tools/sub_code_agent.py +14 -16
  138. jarvis/jarvis_tools/virtual_tty.py +54 -32
  139. jarvis/jarvis_utils/clipboard.py +7 -10
  140. jarvis/jarvis_utils/config.py +98 -63
  141. jarvis/jarvis_utils/embedding.py +5 -5
  142. jarvis/jarvis_utils/fzf.py +8 -8
  143. jarvis/jarvis_utils/git_utils.py +81 -67
  144. jarvis/jarvis_utils/input.py +24 -49
  145. jarvis/jarvis_utils/jsonnet_compat.py +465 -0
  146. jarvis/jarvis_utils/methodology.py +33 -35
  147. jarvis/jarvis_utils/utils.py +245 -202
  148. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/METADATA +205 -70
  149. jarvis_ai_assistant-0.7.6.dist-info/RECORD +218 -0
  150. jarvis/jarvis_agent/edit_file_handler.py +0 -584
  151. jarvis/jarvis_agent/rewrite_file_handler.py +0 -141
  152. jarvis/jarvis_agent/task_planner.py +0 -496
  153. jarvis/jarvis_platform/ai8.py +0 -332
  154. jarvis/jarvis_tools/ask_user.py +0 -54
  155. jarvis_ai_assistant-0.7.0.dist-info/RECORD +0 -192
  156. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/WHEEL +0 -0
  157. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/entry_points.txt +0 -0
  158. {jarvis_ai_assistant-0.7.0.dist-info → jarvis_ai_assistant-0.7.6.dist-info}/licenses/LICENSE +0 -0
  159. {jarvis_ai_assistant-0.7.0.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
 
@@ -69,16 +68,12 @@ def _load_all_methodologies() -> Dict[str, str]:
69
68
  try:
70
69
  import subprocess
71
70
 
72
- PrettyOutput.print(
73
- f"正在克隆中心方法论仓库: {central_repo}", OutputType.INFO
74
- )
71
+ print(f"ℹ️ 正在克隆中心方法论仓库: {central_repo}")
75
72
  subprocess.run(
76
73
  ["git", "clone", central_repo, central_repo_path], check=True
77
74
  )
78
75
  except Exception as e:
79
- PrettyOutput.print(
80
- f"克隆中心方法论仓库失败: {str(e)}", OutputType.ERROR
81
- )
76
+ print(f"❌ 克隆中心方法论仓库失败: {str(e)}")
82
77
 
83
78
  # --- 全局每日更新检查 ---
84
79
  daily_check_git_updates(methodology_dirs, "methodologies")
@@ -110,9 +105,9 @@ def _load_all_methodologies() -> Dict[str, str]:
110
105
 
111
106
  # 统一打印目录警告与文件加载失败信息
112
107
  if warn_dirs:
113
- PrettyOutput.print("\n".join(warn_dirs), OutputType.WARNING)
108
+ print("⚠️ " + "\n⚠️ ".join(warn_dirs))
114
109
  if error_lines:
115
- PrettyOutput.print("\n".join(error_lines), OutputType.WARNING)
110
+ print("⚠️ " + "\n⚠️ ".join(error_lines))
116
111
  return all_methodologies
117
112
 
118
113
 
@@ -145,7 +140,7 @@ def _create_methodology_temp_file(methodologies: Dict[str, str]) -> Optional[str
145
140
 
146
141
  return temp_path
147
142
  except Exception as e:
148
- PrettyOutput.print(f"创建方法论临时文件失败: {str(e)}", OutputType.ERROR)
143
+ print(f"创建方法论临时文件失败: {str(e)}")
149
144
  return None
150
145
 
151
146
 
@@ -161,12 +156,12 @@ def upload_methodology(platform: BasePlatform, other_files: List[str] = []) -> b
161
156
  """
162
157
  methodology_dir = _get_methodology_directory()
163
158
  if not os.path.exists(methodology_dir):
164
- PrettyOutput.print("方法论文档不存在", OutputType.WARNING)
159
+ print("⚠️ 方法论文档不存在")
165
160
  return False
166
161
 
167
162
  methodologies = _load_all_methodologies()
168
163
  if not methodologies:
169
- PrettyOutput.print("没有可用的方法论文档", OutputType.WARNING)
164
+ print("⚠️ 没有可用的方法论文档")
170
165
  return False
171
166
 
172
167
  temp_file_path = _create_methodology_temp_file(methodologies)
@@ -212,24 +207,23 @@ def load_methodology(
212
207
 
213
208
  try:
214
209
  # 加载所有方法论
215
- PrettyOutput.print("📁 加载方法论文件...", OutputType.INFO)
210
+ print("📁 加载方法论文件...")
216
211
  methodologies = _load_all_methodologies()
217
212
  if not methodologies:
218
- PrettyOutput.print("没有找到方法论文件", OutputType.WARNING)
213
+ print("⚠️ 没有找到方法论文件")
219
214
  return ""
220
- PrettyOutput.print(
221
- f"加载方法论文件完成 (共 {len(methodologies)} 个)", OutputType.SUCCESS
222
- )
215
+ print(f"✅ 加载方法论文件完成 (共 {len(methodologies)} 个)")
223
216
 
224
217
  if platform_name:
225
218
  platform = PlatformRegistry().create_platform(platform_name)
226
219
  if platform and model_name:
227
220
  platform.set_model_name(model_name)
228
221
  else:
229
- platform = PlatformRegistry().get_normal_platform()
222
+ # 方法论推荐使用cheap模型以降低成本
223
+ platform = PlatformRegistry().get_cheap_platform()
230
224
 
231
225
  if not platform:
232
- PrettyOutput.print("无法创建平台实例", OutputType.ERROR)
226
+ print("无法创建平台实例")
233
227
  return ""
234
228
 
235
229
  platform.set_suppress_output(True)
@@ -303,9 +297,21 @@ def load_methodology(
303
297
  if not selected_methodologies:
304
298
  return "没有历史方法论可参考"
305
299
 
306
- # 获取最大输入token数的2/3作为方法论的token限制
307
- max_input_tokens = get_max_input_token_count()
308
- 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)
309
315
 
310
316
  # 步骤3:将选择出来的方法论内容提供给大模型生成步骤
311
317
  # 首先构建基础提示词部分
@@ -347,10 +353,7 @@ def load_methodology(
347
353
 
348
354
  # 检查是否会超过token限制
349
355
  if total_methodology_tokens + methodology_tokens > available_tokens:
350
- PrettyOutput.print(
351
- f"达到方法论token限制 ({total_methodology_tokens}/{available_tokens}),停止加载更多方法论",
352
- OutputType.INFO,
353
- )
356
+ print(f"ℹ️ 达到方法论token限制 ({total_methodology_tokens}/{available_tokens}),停止加载更多方法论")
354
357
  break
355
358
 
356
359
  final_prompt += methodology_text
@@ -359,21 +362,16 @@ def load_methodology(
359
362
 
360
363
  # 如果一个方法论都没有加载成功
361
364
  if selected_count == 0:
362
- PrettyOutput.print(
363
- "警告:由于token限制,无法加载任何方法论内容", OutputType.WARNING
364
- )
365
+ print("⚠️ 警告:由于token限制,无法加载任何方法论内容")
365
366
  return "没有历史方法论可参考"
366
367
 
367
368
  final_prompt += suffix_prompt
368
369
 
369
- PrettyOutput.print(
370
- f"成功加载 {selected_count} 个方法论,总token数: {total_methodology_tokens}",
371
- OutputType.INFO,
372
- )
370
+ print(f"ℹ️ 成功加载 {selected_count} 个方法论,总token数: {total_methodology_tokens}")
373
371
 
374
372
  # 如果内容不大,直接使用chat_until_success
375
373
  return platform.chat_until_success(final_prompt)
376
374
 
377
375
  except Exception as e:
378
- PrettyOutput.print(f"加载方法论失败: {str(e)}", OutputType.ERROR)
376
+ print(f"加载方法论失败: {str(e)}")
379
377
  return ""