kweaver-dolphin 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- DolphinLanguageSDK/__init__.py +58 -0
- dolphin/__init__.py +62 -0
- dolphin/cli/__init__.py +20 -0
- dolphin/cli/args/__init__.py +9 -0
- dolphin/cli/args/parser.py +567 -0
- dolphin/cli/builtin_agents/__init__.py +22 -0
- dolphin/cli/commands/__init__.py +4 -0
- dolphin/cli/interrupt/__init__.py +8 -0
- dolphin/cli/interrupt/handler.py +205 -0
- dolphin/cli/interrupt/keyboard.py +82 -0
- dolphin/cli/main.py +49 -0
- dolphin/cli/multimodal/__init__.py +34 -0
- dolphin/cli/multimodal/clipboard.py +327 -0
- dolphin/cli/multimodal/handler.py +249 -0
- dolphin/cli/multimodal/image_processor.py +214 -0
- dolphin/cli/multimodal/input_parser.py +149 -0
- dolphin/cli/runner/__init__.py +8 -0
- dolphin/cli/runner/runner.py +989 -0
- dolphin/cli/ui/__init__.py +10 -0
- dolphin/cli/ui/console.py +2795 -0
- dolphin/cli/ui/input.py +340 -0
- dolphin/cli/ui/layout.py +425 -0
- dolphin/cli/ui/stream_renderer.py +302 -0
- dolphin/cli/utils/__init__.py +8 -0
- dolphin/cli/utils/helpers.py +135 -0
- dolphin/cli/utils/version.py +49 -0
- dolphin/core/__init__.py +107 -0
- dolphin/core/agent/__init__.py +10 -0
- dolphin/core/agent/agent_state.py +69 -0
- dolphin/core/agent/base_agent.py +970 -0
- dolphin/core/code_block/__init__.py +0 -0
- dolphin/core/code_block/agent_init_block.py +0 -0
- dolphin/core/code_block/assign_block.py +98 -0
- dolphin/core/code_block/basic_code_block.py +1865 -0
- dolphin/core/code_block/explore_block.py +1327 -0
- dolphin/core/code_block/explore_block_v2.py +712 -0
- dolphin/core/code_block/explore_strategy.py +672 -0
- dolphin/core/code_block/judge_block.py +220 -0
- dolphin/core/code_block/prompt_block.py +32 -0
- dolphin/core/code_block/skill_call_deduplicator.py +291 -0
- dolphin/core/code_block/tool_block.py +129 -0
- dolphin/core/common/__init__.py +17 -0
- dolphin/core/common/constants.py +176 -0
- dolphin/core/common/enums.py +1173 -0
- dolphin/core/common/exceptions.py +133 -0
- dolphin/core/common/multimodal.py +539 -0
- dolphin/core/common/object_type.py +165 -0
- dolphin/core/common/output_format.py +432 -0
- dolphin/core/common/types.py +36 -0
- dolphin/core/config/__init__.py +16 -0
- dolphin/core/config/global_config.py +1289 -0
- dolphin/core/config/ontology_config.py +133 -0
- dolphin/core/context/__init__.py +12 -0
- dolphin/core/context/context.py +1580 -0
- dolphin/core/context/context_manager.py +161 -0
- dolphin/core/context/var_output.py +82 -0
- dolphin/core/context/variable_pool.py +356 -0
- dolphin/core/context_engineer/__init__.py +41 -0
- dolphin/core/context_engineer/config/__init__.py +5 -0
- dolphin/core/context_engineer/config/settings.py +402 -0
- dolphin/core/context_engineer/core/__init__.py +7 -0
- dolphin/core/context_engineer/core/budget_manager.py +327 -0
- dolphin/core/context_engineer/core/context_assembler.py +583 -0
- dolphin/core/context_engineer/core/context_manager.py +637 -0
- dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
- dolphin/core/context_engineer/example/incremental_example.py +267 -0
- dolphin/core/context_engineer/example/traditional_example.py +334 -0
- dolphin/core/context_engineer/services/__init__.py +5 -0
- dolphin/core/context_engineer/services/compressor.py +399 -0
- dolphin/core/context_engineer/utils/__init__.py +6 -0
- dolphin/core/context_engineer/utils/context_utils.py +441 -0
- dolphin/core/context_engineer/utils/message_formatter.py +270 -0
- dolphin/core/context_engineer/utils/token_utils.py +139 -0
- dolphin/core/coroutine/__init__.py +15 -0
- dolphin/core/coroutine/context_snapshot.py +154 -0
- dolphin/core/coroutine/context_snapshot_profile.py +922 -0
- dolphin/core/coroutine/context_snapshot_store.py +268 -0
- dolphin/core/coroutine/execution_frame.py +145 -0
- dolphin/core/coroutine/execution_state_registry.py +161 -0
- dolphin/core/coroutine/resume_handle.py +101 -0
- dolphin/core/coroutine/step_result.py +101 -0
- dolphin/core/executor/__init__.py +18 -0
- dolphin/core/executor/debug_controller.py +630 -0
- dolphin/core/executor/dolphin_executor.py +1063 -0
- dolphin/core/executor/executor.py +624 -0
- dolphin/core/flags/__init__.py +27 -0
- dolphin/core/flags/definitions.py +49 -0
- dolphin/core/flags/manager.py +113 -0
- dolphin/core/hook/__init__.py +95 -0
- dolphin/core/hook/expression_evaluator.py +499 -0
- dolphin/core/hook/hook_dispatcher.py +380 -0
- dolphin/core/hook/hook_types.py +248 -0
- dolphin/core/hook/isolated_variable_pool.py +284 -0
- dolphin/core/interfaces.py +53 -0
- dolphin/core/llm/__init__.py +0 -0
- dolphin/core/llm/llm.py +495 -0
- dolphin/core/llm/llm_call.py +100 -0
- dolphin/core/llm/llm_client.py +1285 -0
- dolphin/core/llm/message_sanitizer.py +120 -0
- dolphin/core/logging/__init__.py +20 -0
- dolphin/core/logging/logger.py +526 -0
- dolphin/core/message/__init__.py +8 -0
- dolphin/core/message/compressor.py +749 -0
- dolphin/core/parser/__init__.py +8 -0
- dolphin/core/parser/parser.py +405 -0
- dolphin/core/runtime/__init__.py +10 -0
- dolphin/core/runtime/runtime_graph.py +926 -0
- dolphin/core/runtime/runtime_instance.py +446 -0
- dolphin/core/skill/__init__.py +14 -0
- dolphin/core/skill/context_retention.py +157 -0
- dolphin/core/skill/skill_function.py +686 -0
- dolphin/core/skill/skill_matcher.py +282 -0
- dolphin/core/skill/skillkit.py +700 -0
- dolphin/core/skill/skillset.py +72 -0
- dolphin/core/trajectory/__init__.py +10 -0
- dolphin/core/trajectory/recorder.py +189 -0
- dolphin/core/trajectory/trajectory.py +522 -0
- dolphin/core/utils/__init__.py +9 -0
- dolphin/core/utils/cache_kv.py +212 -0
- dolphin/core/utils/tools.py +340 -0
- dolphin/lib/__init__.py +93 -0
- dolphin/lib/debug/__init__.py +8 -0
- dolphin/lib/debug/visualizer.py +409 -0
- dolphin/lib/memory/__init__.py +28 -0
- dolphin/lib/memory/async_processor.py +220 -0
- dolphin/lib/memory/llm_calls.py +195 -0
- dolphin/lib/memory/manager.py +78 -0
- dolphin/lib/memory/sandbox.py +46 -0
- dolphin/lib/memory/storage.py +245 -0
- dolphin/lib/memory/utils.py +51 -0
- dolphin/lib/ontology/__init__.py +12 -0
- dolphin/lib/ontology/basic/__init__.py +0 -0
- dolphin/lib/ontology/basic/base.py +102 -0
- dolphin/lib/ontology/basic/concept.py +130 -0
- dolphin/lib/ontology/basic/object.py +11 -0
- dolphin/lib/ontology/basic/relation.py +63 -0
- dolphin/lib/ontology/datasource/__init__.py +27 -0
- dolphin/lib/ontology/datasource/datasource.py +66 -0
- dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
- dolphin/lib/ontology/datasource/sql.py +845 -0
- dolphin/lib/ontology/mapping.py +177 -0
- dolphin/lib/ontology/ontology.py +733 -0
- dolphin/lib/ontology/ontology_context.py +16 -0
- dolphin/lib/ontology/ontology_manager.py +107 -0
- dolphin/lib/skill_results/__init__.py +31 -0
- dolphin/lib/skill_results/cache_backend.py +559 -0
- dolphin/lib/skill_results/result_processor.py +181 -0
- dolphin/lib/skill_results/result_reference.py +179 -0
- dolphin/lib/skill_results/skillkit_hook.py +324 -0
- dolphin/lib/skill_results/strategies.py +328 -0
- dolphin/lib/skill_results/strategy_registry.py +150 -0
- dolphin/lib/skillkits/__init__.py +44 -0
- dolphin/lib/skillkits/agent_skillkit.py +155 -0
- dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
- dolphin/lib/skillkits/env_skillkit.py +250 -0
- dolphin/lib/skillkits/mcp_adapter.py +616 -0
- dolphin/lib/skillkits/mcp_skillkit.py +771 -0
- dolphin/lib/skillkits/memory_skillkit.py +650 -0
- dolphin/lib/skillkits/noop_skillkit.py +31 -0
- dolphin/lib/skillkits/ontology_skillkit.py +89 -0
- dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
- dolphin/lib/skillkits/resource/__init__.py +52 -0
- dolphin/lib/skillkits/resource/models/__init__.py +6 -0
- dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
- dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
- dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
- dolphin/lib/skillkits/resource/skill_cache.py +215 -0
- dolphin/lib/skillkits/resource/skill_loader.py +395 -0
- dolphin/lib/skillkits/resource/skill_validator.py +406 -0
- dolphin/lib/skillkits/resource_skillkit.py +11 -0
- dolphin/lib/skillkits/search_skillkit.py +163 -0
- dolphin/lib/skillkits/sql_skillkit.py +274 -0
- dolphin/lib/skillkits/system_skillkit.py +509 -0
- dolphin/lib/skillkits/vm_skillkit.py +65 -0
- dolphin/lib/utils/__init__.py +9 -0
- dolphin/lib/utils/data_process.py +207 -0
- dolphin/lib/utils/handle_progress.py +178 -0
- dolphin/lib/utils/security.py +139 -0
- dolphin/lib/utils/text_retrieval.py +462 -0
- dolphin/lib/vm/__init__.py +11 -0
- dolphin/lib/vm/env_executor.py +895 -0
- dolphin/lib/vm/python_session_manager.py +453 -0
- dolphin/lib/vm/vm.py +610 -0
- dolphin/sdk/__init__.py +60 -0
- dolphin/sdk/agent/__init__.py +12 -0
- dolphin/sdk/agent/agent_factory.py +236 -0
- dolphin/sdk/agent/dolphin_agent.py +1106 -0
- dolphin/sdk/api/__init__.py +4 -0
- dolphin/sdk/runtime/__init__.py +8 -0
- dolphin/sdk/runtime/env.py +363 -0
- dolphin/sdk/skill/__init__.py +10 -0
- dolphin/sdk/skill/global_skills.py +706 -0
- dolphin/sdk/skill/traditional_toolkit.py +260 -0
- kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
- kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
- kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
- kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
- kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
- kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import ast
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import glob
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any, List, Union, Optional
|
|
10
|
+
|
|
11
|
+
from dolphin.core.skill.skillkit import SkillFunction, Skillkit
|
|
12
|
+
|
|
13
|
+
"""System function configuration mapping:
|
|
14
|
+
|
|
15
|
+
Configuration Name Actual Function
|
|
16
|
+
system_functions._date -> _date
|
|
17
|
+
system_functions._write_file -> _write_file
|
|
18
|
+
system_functions._read_file -> _read_file
|
|
19
|
+
system_functions._read_folder -> _read_folder
|
|
20
|
+
system_functions._grep -> _grep
|
|
21
|
+
system_functions._extract_code -> _extract_code
|
|
22
|
+
system_functions._write_jsonl -> _write_jsonl
|
|
23
|
+
system_functions._sleep -> _sleep
|
|
24
|
+
|
|
25
|
+
Usage example:
|
|
26
|
+
skill:
|
|
27
|
+
enabled_skills:
|
|
28
|
+
- "system_functions.*" # Load all system functions (recommended)
|
|
29
|
+
- "system_functions._date" # Or load specific functions
|
|
30
|
+
- "system_functions._write_file"
|
|
31
|
+
- "system_functions._read_file"
|
|
32
|
+
- "vm_skillkit" # Other skills configured normally
|
|
33
|
+
|
|
34
|
+
Notes:
|
|
35
|
+
- Function names include an underscore prefix (e.g., _date)
|
|
36
|
+
- Use wildcard "system_functions.*" to load all functions
|
|
37
|
+
- When configuring specific functions, use the full system_functions._date format
|
|
38
|
+
- If enabled_skills is None, all system functions will be loaded (backward compatibility)
|
|
39
|
+
- If enabled_skills is an empty list [], no system functions will be loaded
|
|
40
|
+
- If enabled_skills includes other skills but not system_functions.*, system functions will not be loaded
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SystemFunctionsSkillKit(Skillkit):
|
|
45
|
+
def __init__(self, enabled_functions: List[str] | None = None):
|
|
46
|
+
"""Initialize system function toolkit
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
enabled_functions: List of enabled functions
|
|
50
|
+
- None: Load all functions (backward compatibility)
|
|
51
|
+
- []: Load no functions
|
|
52
|
+
- ["date", "write_file"]: Load only specified functions
|
|
53
|
+
"""
|
|
54
|
+
super().__init__()
|
|
55
|
+
self.enabled_functions = enabled_functions
|
|
56
|
+
|
|
57
|
+
def getName(self) -> str:
|
|
58
|
+
return "system_functions"
|
|
59
|
+
|
|
60
|
+
def _date(self, **kwargs) -> str:
|
|
61
|
+
"""Get current date"""
|
|
62
|
+
return datetime.now().strftime("%Y-%m-%d")
|
|
63
|
+
|
|
64
|
+
def _extract_code(self, content: str, **kwargs) -> str:
|
|
65
|
+
"""Extract code from markdown block, removing language specifier if present.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
content (str): content
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
str: code
|
|
72
|
+
"""
|
|
73
|
+
# Split the content by triple backticks
|
|
74
|
+
parts = content.split("```")
|
|
75
|
+
if len(parts) < 3:
|
|
76
|
+
return "" # No code block found
|
|
77
|
+
|
|
78
|
+
code_section = parts[1].strip()
|
|
79
|
+
|
|
80
|
+
# Split into lines
|
|
81
|
+
lines = code_section.splitlines()
|
|
82
|
+
|
|
83
|
+
# Check if first line is likely a language identifier
|
|
84
|
+
if lines and lines[0].strip() and lines[0].strip().isidentifier():
|
|
85
|
+
# Remove the first line (language) and join the rest
|
|
86
|
+
code = "\n".join(lines[1:]).strip()
|
|
87
|
+
else:
|
|
88
|
+
code = code_section
|
|
89
|
+
|
|
90
|
+
return code
|
|
91
|
+
|
|
92
|
+
def _write_file(self, file_path: str, content: str, **kwargs) -> str:
|
|
93
|
+
"""Write content to file, create file if it does not exist
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
file_path (str): File path
|
|
97
|
+
content (str): File content
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
str: File path
|
|
101
|
+
"""
|
|
102
|
+
file_dir = os.path.dirname(file_path)
|
|
103
|
+
# Only create directory if there is a directory component
|
|
104
|
+
if file_dir and not os.path.exists(file_dir):
|
|
105
|
+
os.makedirs(file_dir, exist_ok=True)
|
|
106
|
+
|
|
107
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
108
|
+
f.write(str(content))
|
|
109
|
+
return file_path
|
|
110
|
+
|
|
111
|
+
def _write_jsonl(self, file_path: str, content: Any) -> str:
|
|
112
|
+
"""Write content to a JSONL file, creating the file if it does not exist
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
file_path (str): File path
|
|
116
|
+
content (List[Dict[str, Any]]): Content
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
str: File path
|
|
120
|
+
"""
|
|
121
|
+
file_dir = os.path.dirname(file_path)
|
|
122
|
+
if not os.path.exists(file_dir):
|
|
123
|
+
os.makedirs(file_dir, exist_ok=True)
|
|
124
|
+
|
|
125
|
+
if isinstance(content, str):
|
|
126
|
+
try:
|
|
127
|
+
content = ast.literal_eval(content)
|
|
128
|
+
except Exception:
|
|
129
|
+
try:
|
|
130
|
+
content = json.loads(content, strict=False)
|
|
131
|
+
except Exception:
|
|
132
|
+
raise ValueError(f"Invalid content: {content}")
|
|
133
|
+
|
|
134
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
135
|
+
for item in content:
|
|
136
|
+
f.write(json.dumps(item, ensure_ascii=False) + "\n")
|
|
137
|
+
return file_path
|
|
138
|
+
|
|
139
|
+
def _read_file(self, file_path: str, **kwargs) -> str:
|
|
140
|
+
"""Read file
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
file_path (str): File path
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
str: File content
|
|
147
|
+
"""
|
|
148
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
149
|
+
return f.read()
|
|
150
|
+
|
|
151
|
+
def _read_folder(
|
|
152
|
+
self,
|
|
153
|
+
folder_path: str,
|
|
154
|
+
extensions: Union[str, List[str]] = None,
|
|
155
|
+
start_symbol: str = None,
|
|
156
|
+
end_symbol: str = None,
|
|
157
|
+
**kwargs,
|
|
158
|
+
) -> str:
|
|
159
|
+
"""Read the content of files with specific extensions in a folder.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
folder_path (str): Folder path
|
|
163
|
+
extensions (str or List[str], optional): File extensions, can be a single extension or a list of extensions
|
|
164
|
+
e.g., "txt" or ["txt", "md", "py"]
|
|
165
|
+
start_symbol (str, optional): Start symbol, only read content after start_symbol
|
|
166
|
+
end_symbol (str, optional): End symbol, only read content before end_symbol
|
|
167
|
+
Used together with start_symbol to read content between the two symbols
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
str: Content of all matching files, sorted by filename, with each file's content prefixed by its filename
|
|
171
|
+
"""
|
|
172
|
+
if not os.path.exists(folder_path):
|
|
173
|
+
raise FileNotFoundError(f"Folder not found: {folder_path}")
|
|
174
|
+
|
|
175
|
+
if not os.path.isdir(folder_path):
|
|
176
|
+
raise ValueError(f"Path is not a directory: {folder_path}")
|
|
177
|
+
|
|
178
|
+
# Handling the extension parameter
|
|
179
|
+
if extensions is None:
|
|
180
|
+
# If no extension is specified, read all files
|
|
181
|
+
pattern = "*"
|
|
182
|
+
elif isinstance(extensions, str):
|
|
183
|
+
# Single extension
|
|
184
|
+
pattern = f"*.{extensions}"
|
|
185
|
+
elif isinstance(extensions, list):
|
|
186
|
+
# Multiple extensions, using glob's brace syntax
|
|
187
|
+
if len(extensions) == 1:
|
|
188
|
+
pattern = f"*.{extensions[0]}"
|
|
189
|
+
else:
|
|
190
|
+
ext_pattern = "{" + ",".join(extensions) + "}"
|
|
191
|
+
pattern = f"*.{ext_pattern}"
|
|
192
|
+
else:
|
|
193
|
+
raise ValueError("extensions must be None, str, or List[str]")
|
|
194
|
+
|
|
195
|
+
# Get matching files
|
|
196
|
+
search_pattern = os.path.join(folder_path, pattern)
|
|
197
|
+
files = glob.glob(search_pattern)
|
|
198
|
+
|
|
199
|
+
# Filter out files (excluding directories) and sort them
|
|
200
|
+
files = [f for f in files if os.path.isfile(f)]
|
|
201
|
+
files.sort()
|
|
202
|
+
|
|
203
|
+
if not files:
|
|
204
|
+
return f"No files found in {folder_path} with pattern {pattern}"
|
|
205
|
+
|
|
206
|
+
result_parts = []
|
|
207
|
+
|
|
208
|
+
for file_path in files:
|
|
209
|
+
try:
|
|
210
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
211
|
+
content = f.read()
|
|
212
|
+
|
|
213
|
+
# Handling start and end symbols
|
|
214
|
+
if start_symbol is not None or end_symbol is not None:
|
|
215
|
+
if start_symbol is not None:
|
|
216
|
+
start_pos = content.find(start_symbol)
|
|
217
|
+
if start_pos != -1:
|
|
218
|
+
content = content[start_pos + len(start_symbol) :]
|
|
219
|
+
else:
|
|
220
|
+
# If the start symbol is not found, skip this file or include a warning message.
|
|
221
|
+
content = f"[WARNING: start_symbol '{start_symbol}' not found in {os.path.basename(file_path)}]"
|
|
222
|
+
|
|
223
|
+
if end_symbol is not None and not content.startswith("[WARNING"):
|
|
224
|
+
end_pos = content.find(end_symbol)
|
|
225
|
+
if end_pos != -1:
|
|
226
|
+
content = content[:end_pos]
|
|
227
|
+
else:
|
|
228
|
+
# If the ending symbol is not found, a warning message is included.
|
|
229
|
+
content = f"[WARNING: end_symbol '{end_symbol}' not found in {os.path.basename(file_path)}]\n{content}"
|
|
230
|
+
|
|
231
|
+
# Add file identifier and content
|
|
232
|
+
file_name = os.path.basename(file_path)
|
|
233
|
+
result_parts.append(f"=== FILE: {file_name} ===\n{content.strip()}")
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
# If reading the file fails, add an error message
|
|
237
|
+
file_name = os.path.basename(file_path)
|
|
238
|
+
result_parts.append(
|
|
239
|
+
f"=== FILE: {file_name} ===\n[ERROR: Failed to read file - {str(e)}]"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return "\n\n".join(result_parts)
|
|
243
|
+
|
|
244
|
+
def _grep(
|
|
245
|
+
self,
|
|
246
|
+
target_path: str,
|
|
247
|
+
pattern: str,
|
|
248
|
+
before: int = 10,
|
|
249
|
+
after: int = 10,
|
|
250
|
+
recursive: bool = True,
|
|
251
|
+
file_extensions: Union[str, List[str], None] = None,
|
|
252
|
+
case_sensitive: bool = True,
|
|
253
|
+
use_regex: bool = True,
|
|
254
|
+
**kwargs,
|
|
255
|
+
) -> str:
|
|
256
|
+
"""Similar to grep functionality, searches for matching content under the specified path and can display context.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
target_path (str): Path to a file or folder.
|
|
260
|
+
pattern (str): Matching pattern, default is regex matching.
|
|
261
|
+
before (int, optional): Number of lines to show before matches. Default is 0.
|
|
262
|
+
after (int, optional): Number of lines to show after matches. Default is 0.
|
|
263
|
+
recursive (bool, optional): Whether to recursively search folders. Default is True.
|
|
264
|
+
file_extensions (Union[str, List[str], None], optional): Only match files with specified extensions (do not include the dot).
|
|
265
|
+
case_sensitive (bool, optional): Whether to distinguish case. Default is True.
|
|
266
|
+
use_regex (bool, optional): Whether to treat pattern as a regular expression. Default is True.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
str: Matching results, including file paths, line numbers, and context.
|
|
270
|
+
"""
|
|
271
|
+
if not target_path:
|
|
272
|
+
raise ValueError("target_path is required")
|
|
273
|
+
if pattern is None or pattern == "":
|
|
274
|
+
raise ValueError("pattern cannot be empty")
|
|
275
|
+
if before < 0 or after < 0:
|
|
276
|
+
raise ValueError("before and after must be non-negative integers")
|
|
277
|
+
|
|
278
|
+
if not os.path.exists(target_path):
|
|
279
|
+
raise FileNotFoundError(f"Path not found: {target_path}")
|
|
280
|
+
|
|
281
|
+
if isinstance(file_extensions, str):
|
|
282
|
+
file_exts = [file_extensions]
|
|
283
|
+
else:
|
|
284
|
+
file_exts = file_extensions
|
|
285
|
+
|
|
286
|
+
normalized_exts = None
|
|
287
|
+
if file_exts:
|
|
288
|
+
normalized_exts = []
|
|
289
|
+
for ext in file_exts:
|
|
290
|
+
if not isinstance(ext, str) or not ext:
|
|
291
|
+
raise ValueError("file_extensions must contain non-empty strings")
|
|
292
|
+
normalized_exts.append(ext if ext.startswith(".") else f".{ext}")
|
|
293
|
+
|
|
294
|
+
files: List[str] = []
|
|
295
|
+
if os.path.isfile(target_path):
|
|
296
|
+
files = [target_path]
|
|
297
|
+
elif os.path.isdir(target_path):
|
|
298
|
+
if recursive:
|
|
299
|
+
for root, _, filenames in os.walk(target_path):
|
|
300
|
+
for filename in filenames:
|
|
301
|
+
file_path = os.path.join(root, filename)
|
|
302
|
+
if normalized_exts and not file_path.endswith(
|
|
303
|
+
tuple(normalized_exts)
|
|
304
|
+
):
|
|
305
|
+
continue
|
|
306
|
+
files.append(file_path)
|
|
307
|
+
else:
|
|
308
|
+
for filename in os.listdir(target_path):
|
|
309
|
+
file_path = os.path.join(target_path, filename)
|
|
310
|
+
if not os.path.isfile(file_path):
|
|
311
|
+
continue
|
|
312
|
+
if normalized_exts and not file_path.endswith(
|
|
313
|
+
tuple(normalized_exts)
|
|
314
|
+
):
|
|
315
|
+
continue
|
|
316
|
+
files.append(file_path)
|
|
317
|
+
else:
|
|
318
|
+
raise ValueError(f"Path is neither file nor directory: {target_path}")
|
|
319
|
+
|
|
320
|
+
if not files:
|
|
321
|
+
return f"No files to search in {target_path}"
|
|
322
|
+
|
|
323
|
+
flags = 0 if case_sensitive else re.IGNORECASE
|
|
324
|
+
try:
|
|
325
|
+
regex = re.compile(pattern if use_regex else re.escape(pattern), flags)
|
|
326
|
+
except re.error as exc:
|
|
327
|
+
raise ValueError(f"Invalid pattern: {exc}") from exc
|
|
328
|
+
|
|
329
|
+
results: List[str] = []
|
|
330
|
+
match_found = False
|
|
331
|
+
for file_path in sorted(files):
|
|
332
|
+
try:
|
|
333
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
334
|
+
lines = f.readlines()
|
|
335
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
336
|
+
results.append(
|
|
337
|
+
f"=== {file_path} ===\n[ERROR: Failed to read file - {str(exc)}]"
|
|
338
|
+
)
|
|
339
|
+
continue
|
|
340
|
+
|
|
341
|
+
match_indexes: List[int] = []
|
|
342
|
+
for idx, line in enumerate(lines):
|
|
343
|
+
if regex.search(line):
|
|
344
|
+
match_indexes.append(idx)
|
|
345
|
+
|
|
346
|
+
if not match_indexes:
|
|
347
|
+
continue
|
|
348
|
+
|
|
349
|
+
match_found = True
|
|
350
|
+
match_index_set = set(match_indexes)
|
|
351
|
+
context_indexes: List[int] = []
|
|
352
|
+
for idx in match_indexes:
|
|
353
|
+
start = max(0, idx - before)
|
|
354
|
+
end = min(len(lines) - 1, idx + after)
|
|
355
|
+
context_indexes.extend(range(start, end + 1))
|
|
356
|
+
|
|
357
|
+
unique_indexes = sorted(set(context_indexes))
|
|
358
|
+
|
|
359
|
+
groups: List[List[int]] = []
|
|
360
|
+
current_group: List[int] = []
|
|
361
|
+
prev_index = None
|
|
362
|
+
for index in unique_indexes:
|
|
363
|
+
if prev_index is not None and index - prev_index > 1:
|
|
364
|
+
if current_group:
|
|
365
|
+
groups.append(current_group)
|
|
366
|
+
current_group = []
|
|
367
|
+
current_group.append(index)
|
|
368
|
+
prev_index = index
|
|
369
|
+
if current_group:
|
|
370
|
+
groups.append(current_group)
|
|
371
|
+
|
|
372
|
+
file_results: List[str] = []
|
|
373
|
+
file_results.append(f"=== FILE: {os.path.basename(file_path)} ===")
|
|
374
|
+
for group_idx, group in enumerate(groups):
|
|
375
|
+
if group_idx > 0:
|
|
376
|
+
file_results.append("--")
|
|
377
|
+
for index in group:
|
|
378
|
+
line_number = index + 1
|
|
379
|
+
marker = ">" if index in match_index_set else " "
|
|
380
|
+
line_content = lines[index].rstrip("\r\n")
|
|
381
|
+
file_results.append(
|
|
382
|
+
f"{file_path}:{line_number}:{marker} {line_content}"
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
results.append("\n".join(file_results))
|
|
386
|
+
|
|
387
|
+
if not match_found:
|
|
388
|
+
return f"No matches found for '{pattern}' in {target_path}"
|
|
389
|
+
|
|
390
|
+
return "\n".join(results)
|
|
391
|
+
|
|
392
|
+
def _sleep(self, seconds: float, **kwargs) -> str:
|
|
393
|
+
"""Pause execution for a specified number of seconds.
|
|
394
|
+
|
|
395
|
+
This is useful for:
|
|
396
|
+
- Waiting for page loads or animations in browser automation
|
|
397
|
+
- Rate limiting API calls
|
|
398
|
+
- Waiting for long-running operations to complete
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
seconds (float): Number of seconds to sleep. Can be a decimal for sub-second waits.
|
|
402
|
+
Maximum allowed is 300 seconds (5 minutes).
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
str: Confirmation message with actual sleep duration.
|
|
406
|
+
"""
|
|
407
|
+
# Validate and cap the sleep duration
|
|
408
|
+
if seconds < 0:
|
|
409
|
+
raise ValueError("seconds must be non-negative")
|
|
410
|
+
if seconds > 300:
|
|
411
|
+
seconds = 300 # Cap at 5 minutes for safety
|
|
412
|
+
|
|
413
|
+
start_time = time.time()
|
|
414
|
+
time.sleep(seconds)
|
|
415
|
+
actual_duration = time.time() - start_time
|
|
416
|
+
|
|
417
|
+
return f"Slept for {actual_duration:.2f} seconds"
|
|
418
|
+
|
|
419
|
+
def _get_result_detail(
|
|
420
|
+
self,
|
|
421
|
+
reference_id: str,
|
|
422
|
+
offset: int = 0,
|
|
423
|
+
limit: int = 2000,
|
|
424
|
+
**kwargs,
|
|
425
|
+
) -> str:
|
|
426
|
+
"""Get detailed content from a previous result.
|
|
427
|
+
|
|
428
|
+
When tool output is omitted, use this method to fetch full content
|
|
429
|
+
or a specific range.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
reference_id: Result reference ID (from previous omitted output)
|
|
433
|
+
offset: Start position (character offset), default 0
|
|
434
|
+
limit: Maximum characters to return, default 2000
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
Content within the specified range
|
|
438
|
+
|
|
439
|
+
Note:
|
|
440
|
+
This skill receives the context's skillkit_hook via the 'props' parameter
|
|
441
|
+
which is injected by the skill execution flow. The hook instance contains
|
|
442
|
+
the same cache backend where the original results are stored.
|
|
443
|
+
"""
|
|
444
|
+
# Get hook from props (injected by skill execution flow)
|
|
445
|
+
# The props contain the context as 'gvp' (global variable pool)
|
|
446
|
+
props = kwargs.get("props", {})
|
|
447
|
+
context = props.get("gvp", None) # Note: context is passed as 'gvp' in skill_run()
|
|
448
|
+
|
|
449
|
+
hook = None
|
|
450
|
+
if context and hasattr(context, "skillkit_hook") and context.skillkit_hook:
|
|
451
|
+
hook = context.skillkit_hook
|
|
452
|
+
else:
|
|
453
|
+
# Fallback: Try to get from global reference (not recommended path)
|
|
454
|
+
# This may not find the result if cache is instance-based
|
|
455
|
+
from dolphin.lib.skill_results.skillkit_hook import SkillkitHook
|
|
456
|
+
hook = SkillkitHook()
|
|
457
|
+
|
|
458
|
+
raw_result = hook.get_raw_result(reference_id)
|
|
459
|
+
|
|
460
|
+
if raw_result is None:
|
|
461
|
+
return f"Error: reference_id '{reference_id}' not found or expired. The result may have been cleaned up or the reference ID is incorrect."
|
|
462
|
+
|
|
463
|
+
content = str(raw_result)
|
|
464
|
+
total_length = len(content)
|
|
465
|
+
|
|
466
|
+
# Get specified range
|
|
467
|
+
result = content[offset : offset + limit]
|
|
468
|
+
|
|
469
|
+
# Append meta info to help LLM understand position
|
|
470
|
+
if offset + limit < total_length:
|
|
471
|
+
remaining = total_length - offset - limit
|
|
472
|
+
result += f"\n... ({remaining} chars remaining, total {total_length})"
|
|
473
|
+
|
|
474
|
+
return result
|
|
475
|
+
|
|
476
|
+
def _createSkills(self) -> List[SkillFunction]:
|
|
477
|
+
all_skills = [
|
|
478
|
+
SkillFunction(self._date),
|
|
479
|
+
SkillFunction(self._write_file),
|
|
480
|
+
SkillFunction(self._write_jsonl),
|
|
481
|
+
SkillFunction(self._read_file),
|
|
482
|
+
SkillFunction(self._read_folder),
|
|
483
|
+
SkillFunction(self._extract_code),
|
|
484
|
+
SkillFunction(self._grep),
|
|
485
|
+
SkillFunction(self._sleep),
|
|
486
|
+
SkillFunction(self._get_result_detail),
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
# If no enable function is specified, return all skills (backward compatibility)
|
|
490
|
+
if self.enabled_functions is None:
|
|
491
|
+
return all_skills
|
|
492
|
+
|
|
493
|
+
# If wildcard "*" is in enabled_functions, return all skills
|
|
494
|
+
if "*" in self.enabled_functions:
|
|
495
|
+
return all_skills
|
|
496
|
+
|
|
497
|
+
# Filter enabled skills
|
|
498
|
+
enabled_skills = []
|
|
499
|
+
for skill in all_skills:
|
|
500
|
+
# Get the function name and convert it to a skill name (e.g., _date -> system_date)
|
|
501
|
+
function_name = skill.get_function_name()
|
|
502
|
+
if function_name in self.enabled_functions:
|
|
503
|
+
enabled_skills.append(skill)
|
|
504
|
+
|
|
505
|
+
return enabled_skills
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
SystemFunctions = SystemFunctionsSkillKit()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from dolphin.core.skill.skill_function import SkillFunction
|
|
4
|
+
from dolphin.core.skill.skillkit import Skillkit
|
|
5
|
+
from dolphin.lib.vm.vm import VM
|
|
6
|
+
from dolphin.lib.vm.python_session_manager import PythonSessionManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VMSkillkit(Skillkit):
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__()
|
|
12
|
+
self.vm: VM = None
|
|
13
|
+
self.session_manager = PythonSessionManager()
|
|
14
|
+
|
|
15
|
+
def getName(self) -> str:
|
|
16
|
+
return "vm_skillkit"
|
|
17
|
+
|
|
18
|
+
def setVM(self, vm: VM):
|
|
19
|
+
self.vm = vm
|
|
20
|
+
|
|
21
|
+
def _bash(self, cmd: str, **kwargs) -> str:
|
|
22
|
+
"""Execute a bash command in the virtual machine and return the execution result.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
cmd (str): The bash command to execute
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: The execution result
|
|
29
|
+
"""
|
|
30
|
+
if self.vm is None:
|
|
31
|
+
raise RuntimeError(
|
|
32
|
+
"VM is not configured. Please set VM before executing bash commands."
|
|
33
|
+
)
|
|
34
|
+
return self.vm.execBash(cmd)
|
|
35
|
+
|
|
36
|
+
def _python(self, cmd: str, **kwargs) -> str:
|
|
37
|
+
"""Execute a Python command in the virtual machine and return the execution result.
|
|
38
|
+
Support session state persistence, running continuously like Jupyter Notebook.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
cmd (str): The Python command to execute; result variables need to be printed
|
|
42
|
+
**kwargs: Additional parameters, including gvp from context
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
str: Execution result
|
|
46
|
+
"""
|
|
47
|
+
if self.vm is None:
|
|
48
|
+
raise RuntimeError(
|
|
49
|
+
"VM is not configured. Please set VM before executing Python commands."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
session_id = self.getSessionId(
|
|
53
|
+
session_id=kwargs.get("session_id"), props=kwargs.get("props")
|
|
54
|
+
)
|
|
55
|
+
if session_id:
|
|
56
|
+
kwargs["session_id"] = session_id
|
|
57
|
+
kwargs["session_manager"] = self.session_manager
|
|
58
|
+
|
|
59
|
+
return self.vm.execPython(cmd, **kwargs)
|
|
60
|
+
|
|
61
|
+
def _createSkills(self) -> List[SkillFunction]:
|
|
62
|
+
return [
|
|
63
|
+
SkillFunction(self._bash, block_as_parameter=("bash", "cmd")),
|
|
64
|
+
SkillFunction(self._python, block_as_parameter=("python", "cmd")),
|
|
65
|
+
]
|