jarvis-ai-assistant 0.1.124__py3-none-any.whl → 0.1.126__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.

Potentially problematic release.


This version of jarvis-ai-assistant might be problematic. Click here for more details.

Files changed (70) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +134 -136
  3. jarvis/jarvis_code_agent/code_agent.py +198 -52
  4. jarvis/jarvis_code_agent/file_select.py +6 -19
  5. jarvis/jarvis_code_agent/patch.py +183 -312
  6. jarvis/jarvis_code_agent/shell_input_handler.py +22 -0
  7. jarvis/jarvis_codebase/main.py +89 -86
  8. jarvis/jarvis_dev/main.py +695 -715
  9. jarvis/jarvis_git_squash/__init__.py +0 -0
  10. jarvis/jarvis_git_squash/main.py +81 -0
  11. jarvis/jarvis_lsp/base.py +0 -12
  12. jarvis/jarvis_lsp/cpp.py +1 -10
  13. jarvis/jarvis_lsp/go.py +1 -10
  14. jarvis/jarvis_lsp/python.py +0 -28
  15. jarvis/jarvis_lsp/registry.py +2 -3
  16. jarvis/jarvis_lsp/rust.py +1 -10
  17. jarvis/jarvis_multi_agent/__init__.py +53 -53
  18. jarvis/jarvis_platform/ai8.py +2 -1
  19. jarvis/jarvis_platform/base.py +19 -24
  20. jarvis/jarvis_platform/kimi.py +2 -3
  21. jarvis/jarvis_platform/ollama.py +3 -1
  22. jarvis/jarvis_platform/openai.py +1 -1
  23. jarvis/jarvis_platform/oyi.py +2 -1
  24. jarvis/jarvis_platform/registry.py +2 -1
  25. jarvis/jarvis_platform_manager/main.py +4 -6
  26. jarvis/jarvis_platform_manager/openai_test.py +0 -1
  27. jarvis/jarvis_rag/main.py +5 -2
  28. jarvis/jarvis_smart_shell/main.py +9 -4
  29. jarvis/jarvis_tools/ask_codebase.py +18 -13
  30. jarvis/jarvis_tools/ask_user.py +5 -4
  31. jarvis/jarvis_tools/base.py +22 -8
  32. jarvis/jarvis_tools/chdir.py +8 -9
  33. jarvis/jarvis_tools/code_review.py +19 -20
  34. jarvis/jarvis_tools/create_code_agent.py +6 -6
  35. jarvis/jarvis_tools/create_sub_agent.py +9 -9
  36. jarvis/jarvis_tools/execute_shell.py +55 -20
  37. jarvis/jarvis_tools/execute_shell_script.py +7 -7
  38. jarvis/jarvis_tools/file_operation.py +39 -10
  39. jarvis/jarvis_tools/git_commiter.py +20 -17
  40. jarvis/jarvis_tools/lsp_find_definition.py +8 -8
  41. jarvis/jarvis_tools/lsp_find_references.py +1 -1
  42. jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -11
  43. jarvis/jarvis_tools/lsp_get_document_symbols.py +1 -1
  44. jarvis/jarvis_tools/lsp_prepare_rename.py +8 -8
  45. jarvis/jarvis_tools/methodology.py +10 -7
  46. jarvis/jarvis_tools/rag.py +27 -20
  47. jarvis/jarvis_tools/read_webpage.py +4 -3
  48. jarvis/jarvis_tools/registry.py +143 -140
  49. jarvis/jarvis_tools/{search.py → search_web.py} +10 -7
  50. jarvis/jarvis_tools/select_code_files.py +4 -4
  51. jarvis/jarvis_tools/tool_generator.py +33 -34
  52. jarvis/jarvis_utils/__init__.py +19 -982
  53. jarvis/jarvis_utils/config.py +138 -0
  54. jarvis/jarvis_utils/embedding.py +201 -0
  55. jarvis/jarvis_utils/git_utils.py +120 -0
  56. jarvis/jarvis_utils/globals.py +82 -0
  57. jarvis/jarvis_utils/input.py +161 -0
  58. jarvis/jarvis_utils/methodology.py +128 -0
  59. jarvis/jarvis_utils/output.py +235 -0
  60. jarvis/jarvis_utils/utils.py +150 -0
  61. jarvis_ai_assistant-0.1.126.dist-info/METADATA +305 -0
  62. jarvis_ai_assistant-0.1.126.dist-info/RECORD +74 -0
  63. {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/WHEEL +1 -1
  64. {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/entry_points.txt +1 -0
  65. jarvis/jarvis_tools/lsp_validate_edit.py +0 -141
  66. jarvis/jarvis_tools/read_code.py +0 -191
  67. jarvis_ai_assistant-0.1.124.dist-info/METADATA +0 -460
  68. jarvis_ai_assistant-0.1.124.dist-info/RECORD +0 -65
  69. {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/LICENSE +0 -0
  70. {jarvis_ai_assistant-0.1.124.dist-info → jarvis_ai_assistant-0.1.126.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,138 @@
1
+ import os
2
+ """
3
+ Configuration Management Module
4
+ This module provides functions to retrieve various configuration settings for the Jarvis system.
5
+ All configurations are read from environment variables with fallback default values.
6
+ The module is organized into several categories:
7
+ - System Configuration
8
+ - Model Configuration
9
+ - Execution Configuration
10
+ - Text Processing Configuration
11
+ """
12
+ def get_max_token_count() -> int:
13
+ """
14
+ Get the maximum token count for API requests.
15
+
16
+ Returns:
17
+ int: Maximum token count, default is 131072 (128k)
18
+ """
19
+ return int(os.getenv('JARVIS_MAX_TOKEN_COUNT', '131072')) # 默认128k
20
+
21
+ def get_thread_count() -> int:
22
+ """
23
+ Get the number of threads to use for parallel processing.
24
+
25
+ Returns:
26
+ int: Thread count, default is 1
27
+ """
28
+ return int(os.getenv('JARVIS_THREAD_COUNT', '1'))
29
+ def dont_use_local_model() -> bool:
30
+ """
31
+ Check if local models should be avoided.
32
+
33
+ Returns:
34
+ bool: True if local models should not be used, default is False
35
+ """
36
+ return os.getenv('JARVIS_DONT_USE_LOCAL_MODEL', 'false') == 'true'
37
+
38
+ def is_auto_complete() -> bool:
39
+ """
40
+ Check if auto-completion is enabled.
41
+
42
+ Returns:
43
+ bool: True if auto-completion is enabled, default is False
44
+ """
45
+ return os.getenv('JARVIS_AUTO_COMPLETE', 'false') == 'true'
46
+
47
+ def is_use_methodology() -> bool:
48
+ """
49
+ Check if methodology should be used.
50
+
51
+ Returns:
52
+ bool: True if methodology should be used, default is True
53
+ """
54
+ return os.getenv('JARVIS_USE_METHODOLOGY', 'true') == 'true'
55
+ def is_record_methodology() -> bool:
56
+ """
57
+ Check if methodology should be recorded.
58
+
59
+ Returns:
60
+ bool: True if methodology should be recorded, default is True
61
+ """
62
+ return os.getenv('JARVIS_RECORD_METHODOLOGY', 'true') == 'true'
63
+ def is_need_summary() -> bool:
64
+ """
65
+ Check if summary generation is required.
66
+
67
+ Returns:
68
+ bool: True if summary is needed, default is True
69
+ """
70
+ return os.getenv('JARVIS_NEED_SUMMARY', 'true') == 'true'
71
+ def get_min_paragraph_length() -> int:
72
+ """
73
+ Get the minimum paragraph length for text processing.
74
+
75
+ Returns:
76
+ int: Minimum length in characters, default is 50
77
+ """
78
+ return int(os.getenv('JARVIS_MIN_PARAGRAPH_LENGTH', '50'))
79
+ def get_max_paragraph_length() -> int:
80
+ """
81
+ Get the maximum paragraph length for text processing.
82
+
83
+ Returns:
84
+ int: Maximum length in characters, default is 12800
85
+ """
86
+ return int(os.getenv('JARVIS_MAX_PARAGRAPH_LENGTH', '12800'))
87
+ def get_shell_name() -> str:
88
+ """
89
+ Get the system shell name.
90
+
91
+ Returns:
92
+ str: Shell name (e.g., bash, zsh), default is bash
93
+ """
94
+ return os.getenv('SHELL', 'bash')
95
+ def get_normal_platform_name() -> str:
96
+ """
97
+ Get the platform name for normal operations.
98
+
99
+ Returns:
100
+ str: Platform name, default is 'kimi'
101
+ """
102
+ return os.getenv('JARVIS_PLATFORM', 'kimi')
103
+ def get_normal_model_name() -> str:
104
+ """
105
+ Get the model name for normal operations.
106
+
107
+ Returns:
108
+ str: Model name, default is 'kimi'
109
+ """
110
+ return os.getenv('JARVIS_MODEL', 'kimi')
111
+ def get_codegen_platform_name() -> str:
112
+ return os.getenv('JARVIS_CODEGEN_PLATFORM', os.getenv('JARVIS_PLATFORM', 'kimi'))
113
+ def get_codegen_model_name() -> str:
114
+ return os.getenv('JARVIS_CODEGEN_MODEL', os.getenv('JARVIS_MODEL', 'kimi'))
115
+ def get_thinking_platform_name() -> str:
116
+ return os.getenv('JARVIS_THINKING_PLATFORM', os.getenv('JARVIS_PLATFORM', 'kimi'))
117
+ def get_thinking_model_name() -> str:
118
+ return os.getenv('JARVIS_THINKING_MODEL', os.getenv('JARVIS_MODEL', 'kimi'))
119
+ def get_cheap_platform_name() -> str:
120
+ return os.getenv('JARVIS_CHEAP_PLATFORM', os.getenv('JARVIS_PLATFORM', 'kimi'))
121
+ def get_cheap_model_name() -> str:
122
+ return os.getenv('JARVIS_CHEAP_MODEL', os.getenv('JARVIS_MODEL', 'kimi'))
123
+ def is_execute_tool_confirm() -> bool:
124
+ """
125
+ Check if tool execution requires confirmation.
126
+
127
+ Returns:
128
+ bool: True if confirmation is required, default is False
129
+ """
130
+ return os.getenv('JARVIS_EXECUTE_TOOL_CONFIRM', 'false') == 'true'
131
+ def is_confirm_before_apply_patch() -> bool:
132
+ """
133
+ Check if patch application requires confirmation.
134
+
135
+ Returns:
136
+ bool: True if confirmation is required, default is False
137
+ """
138
+ return os.getenv('JARVIS_CONFIRM_BEFORE_APPLY_PATCH', 'false') == 'true'
@@ -0,0 +1,201 @@
1
+ import os
2
+ import numpy as np
3
+ import torch
4
+ from sentence_transformers import SentenceTransformer
5
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
6
+ from typing import List, Any, Tuple
7
+ from jarvis.jarvis_utils.output import PrettyOutput, OutputType
8
+
9
+
10
+
11
+
12
+ def get_context_token_count(text: str) -> int:
13
+ """Get the token count of the text using the tokenizer.
14
+
15
+ Args:
16
+ text: The input text to count tokens for
17
+
18
+ Returns:
19
+ int: The number of tokens in the text
20
+ """
21
+ try:
22
+ # Use a fast tokenizer that's good at general text
23
+ tokenizer = load_tokenizer()
24
+ chunks = split_text_into_chunks(text, 512)
25
+ return sum([len(tokenizer.encode(chunk)) for chunk in chunks]) # type: ignore
26
+
27
+ except Exception as e:
28
+ PrettyOutput.print(f"计算token失败: {str(e)}", OutputType.WARNING)
29
+ # Fallback to rough character-based estimate
30
+ return len(text) // 4 # Rough estimate of 4 chars per token
31
+
32
+ def load_embedding_model() -> SentenceTransformer:
33
+ """
34
+ Load the sentence embedding model.
35
+
36
+ Returns:
37
+ SentenceTransformer: The loaded embedding model
38
+ """
39
+ model_name = "BAAI/bge-m3"
40
+ cache_dir = os.path.expanduser("~/.cache/huggingface/hub")
41
+
42
+ try:
43
+ embedding_model = SentenceTransformer(
44
+ model_name,
45
+ cache_folder=cache_dir,
46
+ local_files_only=True
47
+ )
48
+ except Exception:
49
+ embedding_model = SentenceTransformer(
50
+ model_name,
51
+ cache_folder=cache_dir,
52
+ local_files_only=False
53
+ )
54
+
55
+ return embedding_model
56
+ def get_embedding(embedding_model: Any, text: str) -> np.ndarray:
57
+ """
58
+ Generate embedding vector for the given text.
59
+
60
+ Args:
61
+ embedding_model: The embedding model to use
62
+ text: The input text to embed
63
+
64
+ Returns:
65
+ np.ndarray: The embedding vector
66
+ """
67
+ embedding = embedding_model.encode(text,
68
+ normalize_embeddings=True,
69
+ show_progress_bar=False)
70
+ return np.array(embedding, dtype=np.float32)
71
+ def get_embedding_batch(embedding_model: Any, texts: List[str]) -> np.ndarray:
72
+ """
73
+ Generate embeddings for a batch of texts.
74
+
75
+ Args:
76
+ embedding_model: The embedding model to use
77
+ texts: List of texts to embed
78
+
79
+ Returns:
80
+ np.ndarray: Stacked embedding vectors
81
+ """
82
+ try:
83
+ all_vectors = []
84
+ for text in texts:
85
+ vectors = get_embedding_with_chunks(embedding_model, text)
86
+ all_vectors.extend(vectors)
87
+ return np.vstack(all_vectors)
88
+ except Exception as e:
89
+ PrettyOutput.print(f"批量嵌入失败: {str(e)}", OutputType.ERROR)
90
+ return np.zeros((0, embedding_model.get_sentence_embedding_dimension()), dtype=np.float32)
91
+
92
+ def split_text_into_chunks(text: str, max_length: int = 512) -> List[str]:
93
+ """Split text into chunks with overlapping windows.
94
+
95
+ Args:
96
+ text: The input text to split
97
+ max_length: Maximum length of each chunk
98
+
99
+ Returns:
100
+ List[str]: List of text chunks
101
+ """
102
+ chunks = []
103
+ start = 0
104
+ while start < len(text):
105
+ end = start + max_length
106
+ # Find the nearest sentence boundary
107
+ if end < len(text):
108
+ while end > start and text[end] not in {'.', '!', '?', '\n'}:
109
+ end -= 1
110
+ if end == start: # No punctuation found, hard cut
111
+ end = start + max_length
112
+ chunk = text[start:end]
113
+ chunks.append(chunk)
114
+ # Overlap 20% of the window
115
+ start = end - int(max_length * 0.2)
116
+ return chunks
117
+
118
+ def get_embedding_with_chunks(embedding_model: Any, text: str) -> List[np.ndarray]:
119
+ """
120
+ Generate embeddings for text chunks.
121
+
122
+ Args:
123
+ embedding_model: The embedding model to use
124
+ text: The input text to process
125
+
126
+ Returns:
127
+ List[np.ndarray]: List of embedding vectors for each chunk
128
+ """
129
+ chunks = split_text_into_chunks(text, 512)
130
+ if not chunks:
131
+ return []
132
+
133
+ vectors = []
134
+ for chunk in chunks:
135
+ vector = get_embedding(embedding_model, chunk)
136
+ vectors.append(vector)
137
+ return vectors
138
+ def load_tokenizer() -> AutoTokenizer:
139
+ """
140
+ Load the tokenizer for text processing.
141
+
142
+ Returns:
143
+ AutoTokenizer: The loaded tokenizer
144
+ """
145
+ model_name = "gpt2"
146
+ cache_dir = os.path.expanduser("~/.cache/huggingface/hub")
147
+
148
+ try:
149
+ tokenizer = AutoTokenizer.from_pretrained(
150
+ model_name,
151
+ cache_dir=cache_dir,
152
+ local_files_only=True
153
+ )
154
+ except Exception:
155
+ tokenizer = AutoTokenizer.from_pretrained(
156
+ model_name,
157
+ cache_dir=cache_dir,
158
+ local_files_only=False
159
+ )
160
+
161
+ return tokenizer # type: ignore
162
+ def load_rerank_model() -> Tuple[AutoModelForSequenceClassification, AutoTokenizer]:
163
+ """
164
+ Load the reranking model and tokenizer.
165
+
166
+ Returns:
167
+ Tuple[AutoModelForSequenceClassification, AutoTokenizer]: The loaded model and tokenizer
168
+ """
169
+ model_name = "BAAI/bge-reranker-v2-m3"
170
+ cache_dir = os.path.expanduser("~/.cache/huggingface/hub")
171
+
172
+ PrettyOutput.print(f"加载重排序模型: {model_name}...", OutputType.INFO)
173
+
174
+ try:
175
+ tokenizer = AutoTokenizer.from_pretrained(
176
+ model_name,
177
+ cache_dir=cache_dir,
178
+ local_files_only=True
179
+ )
180
+ model = AutoModelForSequenceClassification.from_pretrained(
181
+ model_name,
182
+ cache_dir=cache_dir,
183
+ local_files_only=True
184
+ )
185
+ except Exception:
186
+ tokenizer = AutoTokenizer.from_pretrained(
187
+ model_name,
188
+ cache_dir=cache_dir,
189
+ local_files_only=False
190
+ )
191
+ model = AutoModelForSequenceClassification.from_pretrained(
192
+ model_name,
193
+ cache_dir=cache_dir,
194
+ local_files_only=False
195
+ )
196
+
197
+ if torch.cuda.is_available():
198
+ model = model.cuda()
199
+ model.eval()
200
+
201
+ return model, tokenizer # type: ignore
@@ -0,0 +1,120 @@
1
+ """
2
+ Git Utilities Module
3
+ This module provides utilities for interacting with Git repositories.
4
+ It includes functions for:
5
+ - Finding the root directory of a Git repository
6
+ - Checking for uncommitted changes
7
+ - Retrieving commit history between two hashes
8
+ - Getting the latest commit hash
9
+ - Extracting modified line ranges from Git diffs
10
+ """
11
+ import os
12
+ import re
13
+ import subprocess
14
+ from typing import List, Tuple, Dict
15
+ from jarvis.jarvis_utils.output import PrettyOutput, OutputType
16
+ def find_git_root(start_dir="."):
17
+ """Change to git root directory of the given path"""
18
+ os.chdir(start_dir)
19
+ git_root = os.popen("git rev-parse --show-toplevel").read().strip()
20
+ os.chdir(git_root)
21
+ return git_root
22
+ def has_uncommitted_changes():
23
+ """Check if there are uncommitted changes in the git repository"""
24
+ # Add all changes silently
25
+ subprocess.run(["git", "add", "."], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
26
+
27
+ # Check working directory changes
28
+ working_changes = subprocess.run(["git", "diff", "--exit-code"],
29
+ stdout=subprocess.DEVNULL,
30
+ stderr=subprocess.DEVNULL).returncode != 0
31
+
32
+ # Check staged changes
33
+ staged_changes = subprocess.run(["git", "diff", "--cached", "--exit-code"],
34
+ stdout=subprocess.DEVNULL,
35
+ stderr=subprocess.DEVNULL).returncode != 0
36
+
37
+ # Reset changes silently
38
+ subprocess.run(["git", "reset"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
39
+
40
+ return working_changes or staged_changes
41
+ def get_commits_between(start_hash: str, end_hash: str) -> List[Tuple[str, str]]:
42
+ """Get list of commits between two commit hashes
43
+
44
+ Args:
45
+ start_hash: Starting commit hash (exclusive)
46
+ end_hash: Ending commit hash (inclusive)
47
+
48
+ Returns:
49
+ List[Tuple[str, str]]: List of (commit_hash, commit_message) tuples
50
+ """
51
+ try:
52
+ # Use git log with pretty format to get hash and message
53
+ result = subprocess.run(
54
+ ['git', 'log', f'{start_hash}..{end_hash}', '--pretty=format:%H|%s'],
55
+ stdout=subprocess.PIPE,
56
+ stderr=subprocess.PIPE,
57
+ text=True
58
+ )
59
+ if result.returncode != 0:
60
+ PrettyOutput.print(f"获取commit历史失败: {result.stderr}", OutputType.ERROR)
61
+ return []
62
+
63
+ commits = []
64
+ for line in result.stdout.splitlines():
65
+ if '|' in line:
66
+ commit_hash, message = line.split('|', 1)
67
+ commits.append((commit_hash, message))
68
+ return commits
69
+
70
+ except Exception as e:
71
+ PrettyOutput.print(f"获取commit历史异常: {str(e)}", OutputType.ERROR)
72
+ return []
73
+ def get_latest_commit_hash() -> str:
74
+ """Get the latest commit hash of the current git repository
75
+
76
+ Returns:
77
+ str: The commit hash, or empty string if not in a git repo or error occurs
78
+ """
79
+ try:
80
+ result = subprocess.run(
81
+ ['git', 'rev-parse', 'HEAD'],
82
+ stdout=subprocess.PIPE,
83
+ stderr=subprocess.PIPE,
84
+ text=True
85
+ )
86
+ if result.returncode == 0:
87
+ return result.stdout.strip()
88
+ return ""
89
+ except Exception:
90
+ return ""
91
+ def get_modified_line_ranges() -> Dict[str, Tuple[int, int]]:
92
+ """Get modified line ranges from git diff for all changed files.
93
+
94
+ Returns:
95
+ Dictionary mapping file paths to tuple with (start_line, end_line) ranges
96
+ for modified sections. Line numbers are 1-based.
97
+ """
98
+ # Get git diff for all files
99
+ diff_output = os.popen("git show").read()
100
+
101
+ # Parse the diff to get modified files and their line ranges
102
+ result = {}
103
+ current_file = None
104
+
105
+ for line in diff_output.splitlines():
106
+ # Match lines like "+++ b/path/to/file"
107
+ file_match = re.match(r"^\+\+\+ b/(.*)", line)
108
+ if file_match:
109
+ current_file = file_match.group(1)
110
+ continue
111
+
112
+ # Match lines like "@@ -100,5 +100,7 @@" where the + part shows new lines
113
+ range_match = re.match(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", line)
114
+ if range_match and current_file:
115
+ start_line = int(range_match.group(1)) # Keep as 1-based
116
+ line_count = int(range_match.group(2)) if range_match.group(2) else 1
117
+ end_line = start_line + line_count - 1
118
+ result[current_file] = (start_line, end_line)
119
+
120
+ return result
@@ -0,0 +1,82 @@
1
+ """
2
+ Global Variables and Configuration Module
3
+ This module manages global state and configurations for the Jarvis system.
4
+ It includes:
5
+ - Global agent management
6
+ - Console configuration with custom theme
7
+ - Environment initialization
8
+ """
9
+ from typing import Any, Set
10
+ import colorama
11
+ import os
12
+ from rich.console import Console
13
+ from rich.theme import Theme
14
+ # Initialize colorama for cross-platform colored text
15
+ colorama.init()
16
+ # Disable tokenizers parallelism to avoid issues with multiprocessing
17
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
18
+ # Global agent management
19
+ global_agents: Set[str] = set()
20
+ current_agent_name: str = ""
21
+ # Configure rich console with custom theme
22
+ custom_theme = Theme({
23
+ "INFO": "yellow",
24
+ "WARNING": "yellow",
25
+ "ERROR": "red",
26
+ "SUCCESS": "green",
27
+ "SYSTEM": "cyan",
28
+ "CODE": "green",
29
+ "RESULT": "blue",
30
+ "PLANNING": "magenta",
31
+ "PROGRESS": "white",
32
+ "DEBUG": "blue",
33
+ "USER": "green",
34
+ "TOOL": "yellow",
35
+ })
36
+ console = Console(theme=custom_theme)
37
+ def make_agent_name(agent_name: str) -> str:
38
+ """
39
+ Generate a unique agent name by appending a suffix if necessary.
40
+
41
+ Args:
42
+ agent_name: The base agent name
43
+
44
+ Returns:
45
+ str: Unique agent name
46
+ """
47
+ if agent_name in global_agents:
48
+ i = 1
49
+ while f"{agent_name}_{i}" in global_agents:
50
+ i += 1
51
+ return f"{agent_name}_{i}"
52
+ return agent_name
53
+ def set_agent(agent_name: str, agent: Any) -> None:
54
+ """
55
+ Set the current agent and add it to the global agents set.
56
+
57
+ Args:
58
+ agent_name: The name of the agent
59
+ agent: The agent object
60
+ """
61
+ global_agents.add(agent_name)
62
+ global current_agent_name
63
+ current_agent_name = agent_name
64
+ def get_agent_list() -> str:
65
+ """
66
+ Get a formatted string representing the current agent status.
67
+
68
+ Returns:
69
+ str: Formatted string with agent count and current agent name
70
+ """
71
+ return "[" + str(len(global_agents)) + "]" + current_agent_name if global_agents else ""
72
+ def delete_agent(agent_name: str) -> None:
73
+ """
74
+ Delete an agent from the global agents set.
75
+
76
+ Args:
77
+ agent_name: The name of the agent to delete
78
+ """
79
+ if agent_name in global_agents:
80
+ global_agents.remove(agent_name)
81
+ global current_agent_name
82
+ current_agent_name = ""
@@ -0,0 +1,161 @@
1
+ """
2
+ Input Handling Module
3
+ This module provides utilities for handling user input in the Jarvis system.
4
+ It includes:
5
+ - Single line input with history support
6
+ - Multi-line input with enhanced completion
7
+ - File path completion with fuzzy matching
8
+ - Custom key bindings for input control
9
+ """
10
+ from prompt_toolkit import PromptSession
11
+ from prompt_toolkit.styles import Style as PromptStyle
12
+ from prompt_toolkit.formatted_text import FormattedText
13
+ from prompt_toolkit.completion import Completer, Completion, PathCompleter
14
+ from prompt_toolkit.document import Document
15
+ from prompt_toolkit.key_binding import KeyBindings
16
+ from fuzzywuzzy import process
17
+ from colorama import Fore, Style as ColoramaStyle
18
+ from ..jarvis_utils.output import PrettyOutput, OutputType
19
+ def get_single_line_input(tip: str) -> str:
20
+ """
21
+ Get single line input with history support.
22
+
23
+ Args:
24
+ tip: The prompt message to display
25
+
26
+ Returns:
27
+ str: The user's input
28
+ """
29
+ session = PromptSession(history=None)
30
+ style = PromptStyle.from_dict({
31
+ 'prompt': 'ansicyan',
32
+ })
33
+ return session.prompt(f"{tip}", style=style)
34
+ class FileCompleter(Completer):
35
+ """
36
+ Custom completer for file paths with fuzzy matching.
37
+
38
+ Attributes:
39
+ path_completer: Base path completer
40
+ max_suggestions: Maximum number of suggestions to show
41
+ min_score: Minimum matching score for suggestions
42
+ """
43
+ def __init__(self):
44
+ """Initialize the file completer with default settings."""
45
+ self.path_completer = PathCompleter()
46
+ self.max_suggestions = 10
47
+ self.min_score = 10
48
+ def get_completions(self, document: Document, complete_event) -> Completion: # type: ignore
49
+ """
50
+ Generate completions for file paths with fuzzy matching.
51
+
52
+ Args:
53
+ document: The current document being edited
54
+ complete_event: The completion event
55
+
56
+ Yields:
57
+ Completion: Suggested completions
58
+ """
59
+ text = document.text_before_cursor
60
+ cursor_pos = document.cursor_position
61
+ # Find all @ positions in text
62
+ at_positions = [i for i, char in enumerate(text) if char == '@']
63
+ if not at_positions:
64
+ return
65
+ # Get the last @ position
66
+ current_at_pos = at_positions[-1]
67
+ # If cursor is not after the last @, don't complete
68
+ if cursor_pos <= current_at_pos:
69
+ return
70
+ # Check if there's a space after @
71
+ text_after_at = text[current_at_pos + 1:cursor_pos]
72
+ if ' ' in text_after_at:
73
+ return
74
+ # Get the text after the current @
75
+ file_path = text_after_at.strip()
76
+ # Calculate replacement length
77
+ replace_length = len(text_after_at) + 1
78
+ # Get all possible files using git ls-files
79
+ all_files = []
80
+ try:
81
+ import subprocess
82
+ result = subprocess.run(['git', 'ls-files'],
83
+ stdout=subprocess.PIPE,
84
+ stderr=subprocess.PIPE,
85
+ text=True)
86
+ if result.returncode == 0:
87
+ all_files = [line.strip() for line in result.stdout.splitlines() if line.strip()]
88
+ except Exception:
89
+ pass
90
+ # Generate completions
91
+ if not file_path:
92
+ scored_files = [(path, 100) for path in all_files[:self.max_suggestions]]
93
+ else:
94
+ scored_files_data = process.extract(file_path, all_files, limit=self.max_suggestions)
95
+ scored_files = [(m[0], m[1]) for m in scored_files_data]
96
+ scored_files.sort(key=lambda x: x[1], reverse=True)
97
+ scored_files = scored_files[:self.max_suggestions]
98
+ # Yield completions
99
+ for path, score in scored_files:
100
+ if not file_path or score > self.min_score:
101
+ display_text = path
102
+ if file_path and score < 100:
103
+ display_text = f"{path} ({score}%)"
104
+ yield Completion(
105
+ text=f"'{path}'",
106
+ start_position=-replace_length,
107
+ display=display_text,
108
+ display_meta="File"
109
+ ) # type: ignore
110
+ def get_multiline_input(tip: str) -> str:
111
+ """
112
+ Get multi-line input with enhanced completion and confirmation.
113
+
114
+ Args:
115
+ tip: The prompt message to display
116
+
117
+ Returns:
118
+ str: The user's input, or empty string if canceled
119
+ """
120
+ # Display input instructions
121
+ PrettyOutput.section("用户输入 - 使用 @ 触发文件补全,Tab 选择补全项,Ctrl+J 提交,按 Ctrl+C 取消输入", OutputType.USER)
122
+ print(f"{Fore.GREEN}{tip}{ColoramaStyle.RESET_ALL}")
123
+ # Configure key bindings
124
+ bindings = KeyBindings()
125
+ @bindings.add('enter')
126
+ def _(event):
127
+ """Handle enter key for completion or new line."""
128
+ if event.current_buffer.complete_state:
129
+ event.current_buffer.apply_completion(event.current_buffer.complete_state.current_completion)
130
+ else:
131
+ event.current_buffer.insert_text('\n')
132
+ @bindings.add('c-j')
133
+ def _(event):
134
+ """Handle Ctrl+J for submission."""
135
+ event.current_buffer.validate_and_handle()
136
+ # Configure prompt session
137
+ style = PromptStyle.from_dict({
138
+ 'prompt': 'ansicyan',
139
+ })
140
+ try:
141
+ session = PromptSession(
142
+ history=None,
143
+ completer=FileCompleter(),
144
+ key_bindings=bindings,
145
+ complete_while_typing=True,
146
+ multiline=True,
147
+ vi_mode=False,
148
+ mouse_support=False
149
+ )
150
+ prompt = FormattedText([
151
+ ('class:prompt', '>>> ')
152
+ ])
153
+ # Get input
154
+ text = session.prompt(
155
+ prompt,
156
+ style=style,
157
+ ).strip()
158
+ return text
159
+ except KeyboardInterrupt:
160
+ PrettyOutput.print("输入已取消", OutputType.INFO)
161
+ return ""