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.
Files changed (199) hide show
  1. DolphinLanguageSDK/__init__.py +58 -0
  2. dolphin/__init__.py +62 -0
  3. dolphin/cli/__init__.py +20 -0
  4. dolphin/cli/args/__init__.py +9 -0
  5. dolphin/cli/args/parser.py +567 -0
  6. dolphin/cli/builtin_agents/__init__.py +22 -0
  7. dolphin/cli/commands/__init__.py +4 -0
  8. dolphin/cli/interrupt/__init__.py +8 -0
  9. dolphin/cli/interrupt/handler.py +205 -0
  10. dolphin/cli/interrupt/keyboard.py +82 -0
  11. dolphin/cli/main.py +49 -0
  12. dolphin/cli/multimodal/__init__.py +34 -0
  13. dolphin/cli/multimodal/clipboard.py +327 -0
  14. dolphin/cli/multimodal/handler.py +249 -0
  15. dolphin/cli/multimodal/image_processor.py +214 -0
  16. dolphin/cli/multimodal/input_parser.py +149 -0
  17. dolphin/cli/runner/__init__.py +8 -0
  18. dolphin/cli/runner/runner.py +989 -0
  19. dolphin/cli/ui/__init__.py +10 -0
  20. dolphin/cli/ui/console.py +2795 -0
  21. dolphin/cli/ui/input.py +340 -0
  22. dolphin/cli/ui/layout.py +425 -0
  23. dolphin/cli/ui/stream_renderer.py +302 -0
  24. dolphin/cli/utils/__init__.py +8 -0
  25. dolphin/cli/utils/helpers.py +135 -0
  26. dolphin/cli/utils/version.py +49 -0
  27. dolphin/core/__init__.py +107 -0
  28. dolphin/core/agent/__init__.py +10 -0
  29. dolphin/core/agent/agent_state.py +69 -0
  30. dolphin/core/agent/base_agent.py +970 -0
  31. dolphin/core/code_block/__init__.py +0 -0
  32. dolphin/core/code_block/agent_init_block.py +0 -0
  33. dolphin/core/code_block/assign_block.py +98 -0
  34. dolphin/core/code_block/basic_code_block.py +1865 -0
  35. dolphin/core/code_block/explore_block.py +1327 -0
  36. dolphin/core/code_block/explore_block_v2.py +712 -0
  37. dolphin/core/code_block/explore_strategy.py +672 -0
  38. dolphin/core/code_block/judge_block.py +220 -0
  39. dolphin/core/code_block/prompt_block.py +32 -0
  40. dolphin/core/code_block/skill_call_deduplicator.py +291 -0
  41. dolphin/core/code_block/tool_block.py +129 -0
  42. dolphin/core/common/__init__.py +17 -0
  43. dolphin/core/common/constants.py +176 -0
  44. dolphin/core/common/enums.py +1173 -0
  45. dolphin/core/common/exceptions.py +133 -0
  46. dolphin/core/common/multimodal.py +539 -0
  47. dolphin/core/common/object_type.py +165 -0
  48. dolphin/core/common/output_format.py +432 -0
  49. dolphin/core/common/types.py +36 -0
  50. dolphin/core/config/__init__.py +16 -0
  51. dolphin/core/config/global_config.py +1289 -0
  52. dolphin/core/config/ontology_config.py +133 -0
  53. dolphin/core/context/__init__.py +12 -0
  54. dolphin/core/context/context.py +1580 -0
  55. dolphin/core/context/context_manager.py +161 -0
  56. dolphin/core/context/var_output.py +82 -0
  57. dolphin/core/context/variable_pool.py +356 -0
  58. dolphin/core/context_engineer/__init__.py +41 -0
  59. dolphin/core/context_engineer/config/__init__.py +5 -0
  60. dolphin/core/context_engineer/config/settings.py +402 -0
  61. dolphin/core/context_engineer/core/__init__.py +7 -0
  62. dolphin/core/context_engineer/core/budget_manager.py +327 -0
  63. dolphin/core/context_engineer/core/context_assembler.py +583 -0
  64. dolphin/core/context_engineer/core/context_manager.py +637 -0
  65. dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
  66. dolphin/core/context_engineer/example/incremental_example.py +267 -0
  67. dolphin/core/context_engineer/example/traditional_example.py +334 -0
  68. dolphin/core/context_engineer/services/__init__.py +5 -0
  69. dolphin/core/context_engineer/services/compressor.py +399 -0
  70. dolphin/core/context_engineer/utils/__init__.py +6 -0
  71. dolphin/core/context_engineer/utils/context_utils.py +441 -0
  72. dolphin/core/context_engineer/utils/message_formatter.py +270 -0
  73. dolphin/core/context_engineer/utils/token_utils.py +139 -0
  74. dolphin/core/coroutine/__init__.py +15 -0
  75. dolphin/core/coroutine/context_snapshot.py +154 -0
  76. dolphin/core/coroutine/context_snapshot_profile.py +922 -0
  77. dolphin/core/coroutine/context_snapshot_store.py +268 -0
  78. dolphin/core/coroutine/execution_frame.py +145 -0
  79. dolphin/core/coroutine/execution_state_registry.py +161 -0
  80. dolphin/core/coroutine/resume_handle.py +101 -0
  81. dolphin/core/coroutine/step_result.py +101 -0
  82. dolphin/core/executor/__init__.py +18 -0
  83. dolphin/core/executor/debug_controller.py +630 -0
  84. dolphin/core/executor/dolphin_executor.py +1063 -0
  85. dolphin/core/executor/executor.py +624 -0
  86. dolphin/core/flags/__init__.py +27 -0
  87. dolphin/core/flags/definitions.py +49 -0
  88. dolphin/core/flags/manager.py +113 -0
  89. dolphin/core/hook/__init__.py +95 -0
  90. dolphin/core/hook/expression_evaluator.py +499 -0
  91. dolphin/core/hook/hook_dispatcher.py +380 -0
  92. dolphin/core/hook/hook_types.py +248 -0
  93. dolphin/core/hook/isolated_variable_pool.py +284 -0
  94. dolphin/core/interfaces.py +53 -0
  95. dolphin/core/llm/__init__.py +0 -0
  96. dolphin/core/llm/llm.py +495 -0
  97. dolphin/core/llm/llm_call.py +100 -0
  98. dolphin/core/llm/llm_client.py +1285 -0
  99. dolphin/core/llm/message_sanitizer.py +120 -0
  100. dolphin/core/logging/__init__.py +20 -0
  101. dolphin/core/logging/logger.py +526 -0
  102. dolphin/core/message/__init__.py +8 -0
  103. dolphin/core/message/compressor.py +749 -0
  104. dolphin/core/parser/__init__.py +8 -0
  105. dolphin/core/parser/parser.py +405 -0
  106. dolphin/core/runtime/__init__.py +10 -0
  107. dolphin/core/runtime/runtime_graph.py +926 -0
  108. dolphin/core/runtime/runtime_instance.py +446 -0
  109. dolphin/core/skill/__init__.py +14 -0
  110. dolphin/core/skill/context_retention.py +157 -0
  111. dolphin/core/skill/skill_function.py +686 -0
  112. dolphin/core/skill/skill_matcher.py +282 -0
  113. dolphin/core/skill/skillkit.py +700 -0
  114. dolphin/core/skill/skillset.py +72 -0
  115. dolphin/core/trajectory/__init__.py +10 -0
  116. dolphin/core/trajectory/recorder.py +189 -0
  117. dolphin/core/trajectory/trajectory.py +522 -0
  118. dolphin/core/utils/__init__.py +9 -0
  119. dolphin/core/utils/cache_kv.py +212 -0
  120. dolphin/core/utils/tools.py +340 -0
  121. dolphin/lib/__init__.py +93 -0
  122. dolphin/lib/debug/__init__.py +8 -0
  123. dolphin/lib/debug/visualizer.py +409 -0
  124. dolphin/lib/memory/__init__.py +28 -0
  125. dolphin/lib/memory/async_processor.py +220 -0
  126. dolphin/lib/memory/llm_calls.py +195 -0
  127. dolphin/lib/memory/manager.py +78 -0
  128. dolphin/lib/memory/sandbox.py +46 -0
  129. dolphin/lib/memory/storage.py +245 -0
  130. dolphin/lib/memory/utils.py +51 -0
  131. dolphin/lib/ontology/__init__.py +12 -0
  132. dolphin/lib/ontology/basic/__init__.py +0 -0
  133. dolphin/lib/ontology/basic/base.py +102 -0
  134. dolphin/lib/ontology/basic/concept.py +130 -0
  135. dolphin/lib/ontology/basic/object.py +11 -0
  136. dolphin/lib/ontology/basic/relation.py +63 -0
  137. dolphin/lib/ontology/datasource/__init__.py +27 -0
  138. dolphin/lib/ontology/datasource/datasource.py +66 -0
  139. dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
  140. dolphin/lib/ontology/datasource/sql.py +845 -0
  141. dolphin/lib/ontology/mapping.py +177 -0
  142. dolphin/lib/ontology/ontology.py +733 -0
  143. dolphin/lib/ontology/ontology_context.py +16 -0
  144. dolphin/lib/ontology/ontology_manager.py +107 -0
  145. dolphin/lib/skill_results/__init__.py +31 -0
  146. dolphin/lib/skill_results/cache_backend.py +559 -0
  147. dolphin/lib/skill_results/result_processor.py +181 -0
  148. dolphin/lib/skill_results/result_reference.py +179 -0
  149. dolphin/lib/skill_results/skillkit_hook.py +324 -0
  150. dolphin/lib/skill_results/strategies.py +328 -0
  151. dolphin/lib/skill_results/strategy_registry.py +150 -0
  152. dolphin/lib/skillkits/__init__.py +44 -0
  153. dolphin/lib/skillkits/agent_skillkit.py +155 -0
  154. dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
  155. dolphin/lib/skillkits/env_skillkit.py +250 -0
  156. dolphin/lib/skillkits/mcp_adapter.py +616 -0
  157. dolphin/lib/skillkits/mcp_skillkit.py +771 -0
  158. dolphin/lib/skillkits/memory_skillkit.py +650 -0
  159. dolphin/lib/skillkits/noop_skillkit.py +31 -0
  160. dolphin/lib/skillkits/ontology_skillkit.py +89 -0
  161. dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
  162. dolphin/lib/skillkits/resource/__init__.py +52 -0
  163. dolphin/lib/skillkits/resource/models/__init__.py +6 -0
  164. dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
  165. dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
  166. dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
  167. dolphin/lib/skillkits/resource/skill_cache.py +215 -0
  168. dolphin/lib/skillkits/resource/skill_loader.py +395 -0
  169. dolphin/lib/skillkits/resource/skill_validator.py +406 -0
  170. dolphin/lib/skillkits/resource_skillkit.py +11 -0
  171. dolphin/lib/skillkits/search_skillkit.py +163 -0
  172. dolphin/lib/skillkits/sql_skillkit.py +274 -0
  173. dolphin/lib/skillkits/system_skillkit.py +509 -0
  174. dolphin/lib/skillkits/vm_skillkit.py +65 -0
  175. dolphin/lib/utils/__init__.py +9 -0
  176. dolphin/lib/utils/data_process.py +207 -0
  177. dolphin/lib/utils/handle_progress.py +178 -0
  178. dolphin/lib/utils/security.py +139 -0
  179. dolphin/lib/utils/text_retrieval.py +462 -0
  180. dolphin/lib/vm/__init__.py +11 -0
  181. dolphin/lib/vm/env_executor.py +895 -0
  182. dolphin/lib/vm/python_session_manager.py +453 -0
  183. dolphin/lib/vm/vm.py +610 -0
  184. dolphin/sdk/__init__.py +60 -0
  185. dolphin/sdk/agent/__init__.py +12 -0
  186. dolphin/sdk/agent/agent_factory.py +236 -0
  187. dolphin/sdk/agent/dolphin_agent.py +1106 -0
  188. dolphin/sdk/api/__init__.py +4 -0
  189. dolphin/sdk/runtime/__init__.py +8 -0
  190. dolphin/sdk/runtime/env.py +363 -0
  191. dolphin/sdk/skill/__init__.py +10 -0
  192. dolphin/sdk/skill/global_skills.py +706 -0
  193. dolphin/sdk/skill/traditional_toolkit.py +260 -0
  194. kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
  195. kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
  196. kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
  197. kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
  198. kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
  199. kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,1106 @@
1
+ """Dolphin Language Agent - A concrete implementation based on BaseAgent"""
2
+
3
+ import os
4
+ import re
5
+ import inspect
6
+ from dolphin.core.context_engineer.config.settings import BuildInBucket
7
+ import aiofiles
8
+ import logging
9
+ from typing import AsyncGenerator, Any, Dict, Optional
10
+ import asyncio
11
+
12
+ from dolphin.core.context.context import Context
13
+ from dolphin.core.skill.skillkit import Skillkit
14
+ from dolphin.core.common.object_type import ObjectTypeFactory
15
+ from dolphin.core.parser.parser import Parser
16
+
17
+ from dolphin.core.agent.base_agent import BaseAgent
18
+ from dolphin.core.agent.agent_state import AgentState
19
+ from dolphin.core.config.global_config import GlobalConfig
20
+ from dolphin.core.coroutine.step_result import StepResult
21
+ import dolphin.core.executor.dolphin_executor as dolphin_language
22
+ from dolphin.core.executor.dolphin_executor import DolphinExecutor
23
+ from dolphin.core.common.exceptions import DolphinAgentException
24
+
25
+
26
+ class DolphinAgent(BaseAgent):
27
+ """DPH file execution Agent implementation based on BaseAgent
28
+ Supports full lifecycle management and state control
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ name: Optional[str] = None,
34
+ description: Optional[str] = None,
35
+ skillkit: Optional[Skillkit] = None,
36
+ variables: Optional[Dict[str, Any]] = None,
37
+ global_skills=None,
38
+ file_path: Optional[str] = None,
39
+ content: Optional[str] = None,
40
+ global_config: Optional[GlobalConfig] = None,
41
+ global_config_path: str = "",
42
+ global_types: ObjectTypeFactory = ObjectTypeFactory(),
43
+ verbose: bool = False,
44
+ is_cli: bool = False,
45
+ log_level: int = logging.INFO,
46
+ output_variables: Optional[list] = None,
47
+ ):
48
+ """Initialize Dolphin Agent
49
+
50
+ Args:
51
+ name: Agent name, automatically generated if not provided
52
+ description: Agent description
53
+ skillkit: Skillkit instance
54
+ variables: Initial variables
55
+ file_path: DPH file path (optional, mutually exclusive with content)
56
+ content: DPH content as a string (optional, mutually exclusive with file_path)
57
+ global_config: Global configuration
58
+ global_config_path: Path to the global configuration file
59
+ global_skills: Global skills
60
+ global_types: Global type definitions
61
+ verbose: Whether to enable verbose output mode (detailed logging)
62
+ is_cli: Whether running in CLI mode (controls Rich/terminal beautification)
63
+ log_level: Log level, such as logging.DEBUG, logging.INFO, etc.
64
+ output_variables: List of variable names to return, corresponding to VariablePool variable names.
65
+ If specified, only these variables are returned; if empty list or None, all variables are returned
66
+ """
67
+ # Parameter Validation
68
+ if file_path is None and content is None:
69
+ raise DolphinAgentException(
70
+ "INVALID_ARGUMENT", "必须提供 file_path 或 content 参数"
71
+ )
72
+ if file_path is not None and content is not None:
73
+ raise DolphinAgentException(
74
+ "INVALID_ARGUMENT", "不能同时提供 file_path 和 content 参数"
75
+ )
76
+
77
+ # Set name according to content source
78
+ if file_path is not None:
79
+ # File Mode
80
+ self.content_source = "file"
81
+ agent_name = name or os.path.splitext(os.path.basename(file_path))[0]
82
+
83
+ # Verify file existence
84
+ if not os.path.exists(file_path):
85
+ raise DolphinAgentException(
86
+ "FILE_NOT_FOUND", f"DPH file not found: {file_path}"
87
+ )
88
+ else:
89
+ # Content Mode
90
+ self.content_source = "content"
91
+ agent_name = name or "content_agent"
92
+
93
+ super().__init__(
94
+ name=agent_name, description=description, global_config=global_config
95
+ )
96
+
97
+ self.content = content
98
+ self.skillkit = skillkit
99
+ self.variables = variables
100
+ self.file_path = file_path
101
+ self.global_config = global_config
102
+ self.global_config_path = global_config_path
103
+ self.global_skills = global_skills
104
+ self.global_types = global_types
105
+ self.verbose = verbose # Store the verbose parameter
106
+ self.is_cli = is_cli # Store CLI mode flag
107
+ self.log_level = log_level # Storage log level parameter
108
+ self.executor: Optional[DolphinExecutor] = None
109
+
110
+ self.execution_context = None
111
+ # Normalize output variable parameters to avoid sharing issues caused by mutable default arguments
112
+ self.output_variables = output_variables or [] # Store output variable list
113
+ self.header_info = {} # Store parsed header information
114
+
115
+ # Initialize components (completed in the _initialize method)
116
+
117
+ # Set log level
118
+ from dolphin.core.logging.logger import set_log_level
119
+
120
+ set_log_level(self.log_level)
121
+
122
+ async def achat(self, message = None, **kwargs) -> AsyncGenerator[Any, None]:
123
+ """Interactive dialogue mode execution, used to continue multi-turn dialogues after the Agent is initialized and running.
124
+
125
+ .. deprecated:: 2.1
126
+ Use :meth:`continue_chat` instead. The achat method returns raw progress data
127
+ without wrapping, which is inconsistent with arun. The new continue_chat method
128
+ provides a consistent API by wrapping results in _progress list.
129
+
130
+ Args:
131
+ message: The message input by the user. Can be:
132
+ - str: Plain text message
133
+ - List[Dict]: Multimodal content (e.g., [{"type": "text", "text": "..."}, {"type": "image_url", ...}])
134
+ - None: Assume the message has been added to the Context.
135
+ **kwargs: Other parameters
136
+
137
+ Yields:
138
+ Execution results
139
+ """
140
+ import warnings
141
+ warnings.warn(
142
+ "achat() is deprecated and will be removed in v3.0. "
143
+ "Use continue_chat() instead for consistent API with arun().",
144
+ DeprecationWarning,
145
+ stacklevel=2
146
+ )
147
+
148
+ if self.executor is None:
149
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
150
+
151
+ # Pass message through kwargs to continue_exploration
152
+ # Note: Do NOT call reset_for_block() here - continue_exploration will handle it
153
+ # and we need to pass content via kwargs, not bucket (which would be cleared)
154
+ if message:
155
+ kwargs["content"] = message
156
+
157
+ # Handled by the continue_exploration method of the executor
158
+ # continue_exploration will automatically get the previously used model (if available) from context
159
+ # This can reduce the coupling between DolphinAgent and internal implementation classes.
160
+ async for result in self.executor.continue_exploration(
161
+ **kwargs
162
+ ):
163
+ yield result
164
+
165
+ async def continue_chat(
166
+ self,
167
+ message: str = None,
168
+ stream_variables: bool = True,
169
+ stream_mode: str = "full",
170
+ **kwargs
171
+ ) -> AsyncGenerator[Any, None]:
172
+ """Continue multi-turn dialogue with consistent API format as arun().
173
+
174
+ This is the recommended method for multi-turn conversations, replacing achat().
175
+ Returns results wrapped in _progress list for consistency with arun().
176
+
177
+ Args:
178
+ message: The message input by the user. Can be:
179
+ - str: Plain text message
180
+ - List[Dict]: Multimodal content
181
+ - None: Assume the message has been added to the Context
182
+ stream_variables: Whether to enable streaming variable output (default: True)
183
+ stream_mode: Streaming mode:
184
+ - "full": Return full accumulated text (default)
185
+ - "delta": Return only incremental text changes
186
+ **kwargs: Other parameters
187
+
188
+ Yields:
189
+ Results in the same format as arun():
190
+ {
191
+ '_status': 'running' | 'completed' | 'interrupted',
192
+ '_progress': [
193
+ {
194
+ 'stage': 'llm' | 'tool_call' | ...,
195
+ 'status': 'running' | 'completed' | 'failed',
196
+ 'answer': str, # Full text (stream_mode='full') or delta (stream_mode='delta')
197
+ 'delta': str, # Only present when stream_mode='delta'
198
+ ...
199
+ }
200
+ ],
201
+ '_interrupt': {...} # Only present when _status='interrupted'
202
+ }
203
+
204
+ Raises:
205
+ DolphinAgentException: If EXPLORE_BLOCK_V2 flag is not disabled or agent not initialized
206
+
207
+ Example:
208
+ # First run
209
+ async for result in agent.arun(query="Hello"):
210
+ process(result)
211
+
212
+ # Continue conversation
213
+ async for result in agent.continue_chat(message="Continue..."):
214
+ process(result) # Same format as arun!
215
+ """
216
+ # Fail-fast check: EXPLORE_BLOCK_V2 must be disabled
217
+ from dolphin.core import flags
218
+ if flags.is_enabled(flags.EXPLORE_BLOCK_V2):
219
+ raise DolphinAgentException(
220
+ "INVALID_FLAG_STATE",
221
+ "continue_chat() requires EXPLORE_BLOCK_V2 flag to be disabled. "
222
+ "Set flags.set_flag(flags.EXPLORE_BLOCK_V2, False) before using continue_chat()."
223
+ )
224
+
225
+ # Lazy initialization if needed
226
+ if self.executor is None:
227
+ await self.initialize()
228
+
229
+ # Pass message through kwargs
230
+ if message:
231
+ kwargs["content"] = message
232
+
233
+ # Validate stream_mode
234
+ if stream_mode not in ("full", "delta"):
235
+ raise DolphinAgentException(
236
+ "INVALID_PARAMETER",
237
+ f"stream_mode must be 'full' or 'delta', got '{stream_mode}'"
238
+ )
239
+
240
+ # Track last answer for delta calculation
241
+ last_answer = {}
242
+
243
+ # Direct passthrough pattern (like achat), with optional wrapping
244
+ # This ensures streaming works correctly by yielding each result immediately
245
+ async for result in self.executor.continue_exploration(**kwargs):
246
+ # Interrupt: wrap in consistent format with _status="interrupted"
247
+ if isinstance(result, dict) and result.get("status") == "interrupted":
248
+ yield {
249
+ "_status": "interrupted",
250
+ "_progress": [],
251
+ "_interrupt": result # Preserve original interrupt info
252
+ }
253
+ return
254
+
255
+ # Streaming mode: get context variables (contains _progress)
256
+ ctx = self.executor.context if self.executor else None
257
+ if ctx is not None and stream_variables:
258
+ if self.output_variables is not None and len(self.output_variables) > 0:
259
+ data = ctx.get_variables_values(self.output_variables)
260
+ else:
261
+ data = ctx.get_all_variables_values()
262
+
263
+ # Apply delta mode if requested
264
+ if stream_mode == "delta" and "_progress" in data:
265
+ data = self._apply_delta_mode(data, last_answer)
266
+
267
+ # data already contains _status and _progress from context
268
+ yield data
269
+ else:
270
+ # Non-streaming or no context: wrap raw result
271
+ yield {
272
+ "_status": "running",
273
+ "_progress": [result] if isinstance(result, dict) else []
274
+ }
275
+
276
+ # Final state: mark as completed
277
+ try:
278
+ ctx = self.executor.context if self.executor else None
279
+ if ctx is not None:
280
+ if self.output_variables is not None and len(self.output_variables) > 0:
281
+ final_data = ctx.get_variables_values(self.output_variables)
282
+ else:
283
+ final_data = ctx.get_all_variables_values()
284
+
285
+ # Explicitly mark completion status
286
+ final_data["_status"] = "completed"
287
+
288
+ # Apply delta mode to final data
289
+ if stream_mode == "delta" and "_progress" in final_data:
290
+ final_data = self._apply_delta_mode(final_data, last_answer)
291
+
292
+ yield final_data
293
+ except (AttributeError, KeyError) as e:
294
+ # Expected errors when context/variables are not fully initialized
295
+ self._logger.debug(f"Could not get final variables in continue_chat: {e}")
296
+
297
+ def _apply_delta_mode(self, data: dict, last_answer: dict) -> dict:
298
+ """Apply delta mode to progress data by calculating incremental changes.
299
+
300
+ Args:
301
+ data: Current data with _progress list
302
+ last_answer: Dictionary tracking last answer text per stage
303
+
304
+ Returns:
305
+ Modified data with delta field added to each progress item
306
+ """
307
+ if "_progress" not in data or not isinstance(data["_progress"], list):
308
+ return data
309
+
310
+ for prog in data["_progress"]:
311
+ if not isinstance(prog, dict):
312
+ continue
313
+
314
+ stage = prog.get("stage", "")
315
+ stage_id = prog.get("id", stage) # Use ID if available
316
+ current_answer = prog.get("answer", "")
317
+
318
+ # Calculate delta
319
+ last_text = last_answer.get(stage_id, "")
320
+ if not last_text:
321
+ # First time: full text is delta
322
+ delta = current_answer
323
+ elif current_answer.startswith(last_text):
324
+ # Normal case: extract new portion
325
+ delta = current_answer[len(last_text):]
326
+ else:
327
+ # Text changed unexpectedly: reset
328
+ delta = current_answer
329
+
330
+ # Update tracking and add delta field
331
+ last_answer[stage_id] = current_answer
332
+ prog["delta"] = delta
333
+
334
+ return data
335
+
336
+ async def _on_initialize(self):
337
+ """Initialize Agent component"""
338
+ try:
339
+ # Get content according to the source
340
+ if self.content_source == "file" and self.file_path is not None:
341
+ # File mode: Asynchronously read file contents to avoid blocking the event loop
342
+ async with aiofiles.open(self.file_path, "r", encoding="utf-8") as f:
343
+ self.content = await f.read()
344
+ # Content mode: Content has been set in the constructor
345
+
346
+ # Parse and remove the header information block (such as @DESC ... @DESC)
347
+ self._parse_header_info()
348
+
349
+ # Create Executor
350
+ self.executor = dolphin_language.DolphinExecutor(
351
+ global_configpath=self.global_config_path,
352
+ global_config=self.global_config,
353
+ global_skills=self.global_skills,
354
+ global_types=self.global_types,
355
+ verbose=self.verbose,
356
+ is_cli=self.is_cli,
357
+ )
358
+
359
+ await self.executor.executor_init(
360
+ {
361
+ "skillkit": self.skillkit,
362
+ "variables": self.variables,
363
+ }
364
+ )
365
+
366
+ # Validate DPH syntax
367
+ self._validate_syntax()
368
+
369
+ self._logger.debug(f"Dolphin Agent '{self.name}' initialized successfully")
370
+
371
+ except Exception as e:
372
+ raise DolphinAgentException(
373
+ "INIT_FAILED", f"Failed to initialize agent: {str(e)}"
374
+ )
375
+
376
+ def _parse_header_info(self):
377
+ """Parse the header information block of DPH files, supporting the general @XX ... @XX format.
378
+ 同时从content中移除这些header块,避免干扰后续解析
379
+
380
+ Examples:
381
+ @DESC
382
+ This is the agent description
383
+ @DESC
384
+
385
+ @VERSION
386
+ 1.0.0
387
+ @VERSION
388
+ """
389
+ if self.content is None:
390
+ return
391
+
392
+ self.header_info = {}
393
+
394
+ # Use regular expressions to match information blocks in the format @XX ... @XX
395
+ # Pattern: @-prefixed tags (allowing only uppercase letters, numbers, and underscores), non-greedy match until the closing tag of the same type
396
+ pattern = r"@([A-Z][A-Z_0-9]+)\s*(.*?)\s*@\1"
397
+
398
+ matches = re.findall(pattern, self.content, re.DOTALL)
399
+
400
+ for tag_name, content in matches:
401
+ # Clean up extra blank lines in the content
402
+ clean_content = re.sub(r"\n\s*\n", "\n", content.strip())
403
+ self.header_info[tag_name] = clean_content
404
+
405
+ # Remove all matching header blocks from self.content
406
+ self.content = re.sub(pattern, "", self.content, flags=re.DOTALL)
407
+
408
+ # Clean up extra blank lines that may be generated after removing the header
409
+ self.content = re.sub(r"\n\s*\n\s*\n", "\n\n", self.content).strip()
410
+
411
+ # If DESC is parsed and description is not provided during construction, use the parsed DESC
412
+ if "DESC" in self.header_info and not self.description:
413
+ self.description = self.header_info["DESC"]
414
+
415
+ def _validate_syntax(self):
416
+ """Validate DPH file syntax"""
417
+ if self.content is None:
418
+ raise DolphinAgentException("INVALID_CONTENT", "DPH content is empty")
419
+
420
+ is_valid, error_message = Parser.validate_syntax(self.content)
421
+ if not is_valid:
422
+ raise DolphinAgentException("SYNTAX_ERROR", error_message)
423
+
424
+ async def _on_execute(self, **kwargs) -> AsyncGenerator[Any, None]:
425
+ """Execute DPH file content
426
+
427
+ Args:
428
+ **kwargs: Additional parameters passed to the executor
429
+
430
+ Yields:
431
+ Execution results
432
+ """
433
+ if self.executor is None:
434
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
435
+
436
+ try:
437
+ # Initialize executor parameters
438
+ if kwargs:
439
+ init_params = {"variables": kwargs}
440
+ await self.executor.executor_init(init_params)
441
+
442
+ # Set the current Agent
443
+ self.executor.context.set_cur_agent(self)
444
+
445
+ # Execute DPH content
446
+ async for result in self.executor.run(
447
+ content=self.content, output_variables=self.output_variables, **kwargs
448
+ ):
449
+ yield result
450
+
451
+ except Exception as e:
452
+ self._logger.error(f"Execution error: {e}")
453
+ raise DolphinAgentException("EXECUTION_ERROR", str(e))
454
+
455
+ async def _on_pause(self):
456
+ """Pause Agent execution"""
457
+ self._logger.debug(f"Agent '{self.name}' paused")
458
+ # Here you can add cleanup logic when paused
459
+ # For example: save the current execution state, release resources, etc.
460
+
461
+ async def _on_resume(self):
462
+ """Restore Agent Execution"""
463
+ self._logger.debug(f"Agent '{self.name}' resumed")
464
+ # Here you can add logic for recovery.
465
+ # For example: restoring execution state, re-initializing resources, etc.
466
+
467
+ async def _on_terminate(self):
468
+ """Terminate Agent execution"""
469
+ self._logger.debug(f"Agent '{self.name}' terminated")
470
+ # Clean up resources
471
+ if self.executor:
472
+ try:
473
+ self.executor.shutdown()
474
+ except Exception as e:
475
+ self._logger.warning(f"Error during executor shutdown: {e}")
476
+
477
+ # === Coroutine Series Method Implementation ===
478
+ async def _on_start_coroutine(self, **kwargs):
479
+ """Start coroutine execution"""
480
+ if self.executor is None:
481
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
482
+
483
+ try:
484
+ # Initialize executor parameters
485
+ if kwargs:
486
+ init_params = {"variables": kwargs}
487
+ await self.executor.executor_init(init_params)
488
+
489
+ # Set the current Agent
490
+ self.executor.context.set_cur_agent(self)
491
+
492
+ # Start coroutine execution (compatible with MagicMock scenarios)
493
+ start_result = self.executor.start_coroutine(content=self.content, **kwargs)
494
+ frame = (
495
+ await start_result
496
+ if inspect.isawaitable(start_result)
497
+ else start_result
498
+ )
499
+
500
+ # Inject interrupt event into executor context for user interrupt detection
501
+ # This enables Context.check_user_interrupt() to work correctly
502
+ if self.executor and self.executor.context:
503
+ self.executor.context.set_interrupt_event(self._interrupt_event)
504
+ self._logger.debug("Interrupt event injected into executor context")
505
+
506
+ return frame
507
+
508
+ except Exception as e:
509
+ self._logger.error(f"Start coroutine error: {e}")
510
+ raise DolphinAgentException("START_COROUTINE_FAILED", str(e)) from e
511
+
512
+ async def _on_step_coroutine(self) -> StepResult:
513
+ """Execute one step"""
514
+ if self.executor is None:
515
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
516
+
517
+ if self._current_frame is None:
518
+ raise DolphinAgentException("NO_FRAME", "No execution frame available")
519
+
520
+ try:
521
+ # Execute one step, return StepResult
522
+ result = await self.executor.step_coroutine(self._current_frame.frame_id)
523
+
524
+ # Return StepResult directly, the caller can use is_interrupted/is_completed/is_running to determine.
525
+ return result
526
+
527
+ except Exception as e:
528
+ self._logger.error(f"Step coroutine error: {e}")
529
+ raise DolphinAgentException("STEP_COROUTINE_FAILED", str(e)) from e
530
+
531
+ async def _on_run_coroutine(self):
532
+ """Continuously execute until the tool interrupts or completes (fast mode)"""
533
+ if self.executor is None:
534
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
535
+
536
+ if self._current_frame is None:
537
+ raise DolphinAgentException("NO_FRAME", "No execution frame available")
538
+
539
+ try:
540
+ # If a progress callback exists (for streaming variable output), pass it to the executor.
541
+ progress_cb = getattr(self, "_progress_callback", None)
542
+ result = await self.executor.run_coroutine(
543
+ self._current_frame.frame_id, progress_callback=progress_cb
544
+ )
545
+
546
+ # Return StepResult directly, the caller can use is_interrupted/is_completed to determine.
547
+ return result
548
+
549
+ except Exception as e:
550
+ self._logger.error(f"Run coroutine error: {e}")
551
+ raise DolphinAgentException("RUN_COROUTINE_FAILED", str(e)) from e
552
+
553
+ async def arun(
554
+ self,
555
+ run_mode: bool = True,
556
+ stream_variables: bool = True,
557
+ stream_mode: str = "full",
558
+ **kwargs,
559
+ ) -> AsyncGenerator[Any, None]:
560
+ """Run the Agent asynchronously.
561
+
562
+ - The default behavior is consistent with BaseAgent.arun, producing a run status/completion/interrupt structure.
563
+ - When stream_variables=True, execute in fast mode (run_mode=True) to avoid snapshot overhead at each step,
564
+ while streaming current context variables at each block's progress point (filtered as needed if output_variables is set).
565
+
566
+ Important: When stream_variables=True, fast mode is enforced (equivalent to run_mode=True),
567
+ the run_mode passed by the caller will be ignored and only effective in this branch.
568
+
569
+ Args:
570
+ run_mode: Whether to run in fast mode (default: True)
571
+ stream_variables: Whether to enable streaming variable output (default: True)
572
+ stream_mode: Streaming mode (default: "full"):
573
+ - "full": Return full accumulated text
574
+ - "delta": Return only incremental text changes (framework calculates automatically)
575
+ **kwargs: Additional runtime variables
576
+
577
+ Note: Tool interruption handling when stream_variables=True:
578
+ 1. During streaming variable views (dict), if a tool interruption occurs, an interruption information dictionary is produced:
579
+ {"status": "interrupted", "handle": ResumeHandle}
580
+ 2. The Agent state becomes PAUSED
581
+ 3. The caller must handle the tool interruption, then call agent.resume(updates) to resume execution
582
+ 4. After resuming, arun(stream_variables=True) can be called again to continue streaming
583
+ 5. On normal completion, the final variable snapshot is produced, which may be identical to the penultimate output (ensuring no final state is missed)
584
+
585
+ Queue strategy:
586
+ - Use a bounded queue (maxsize=32) + discard old, keep new to prevent memory bloat (balancing high-frequency updates and memory usage)
587
+ - The caller always receives the latest variable state
588
+ - If execution is too fast, some intermediate states may be skipped (only the latest is retained)
589
+
590
+ Example:
591
+ async for data in agent.arun(stream_variables=True):
592
+ if isinstance(data, dict) and data.get("status") == "interrupted":
593
+ # Handle tool interruption
594
+ handle = data["handle"]
595
+ updates = handle_tool_interrupt(handle)
596
+ await agent.resume(updates)
597
+ # Continue execution
598
+ async for data in agent.arun(stream_variables=True):
599
+ process_variable_data(data)
600
+ else:
601
+ # Normal variable view data
602
+ process_variable_data(data)
603
+ """
604
+ if not stream_variables:
605
+ # Keep the original arun semantics
606
+ async for item in super().arun(run_mode=run_mode, **kwargs):
607
+ yield item
608
+ return
609
+
610
+ # Validate stream_mode
611
+ if stream_mode not in ("full", "delta"):
612
+ raise DolphinAgentException(
613
+ "INVALID_PARAMETER",
614
+ f"stream_mode must be 'full' or 'delta', got '{stream_mode}'"
615
+ )
616
+
617
+ # Streaming variable pattern: capture execution progress via progress_callback, while using fast mode to avoid frequent snapshots
618
+ if self.executor is None:
619
+ # If not yet initialized, trigger initialization (lazy loading)
620
+ await self.initialize()
621
+
622
+ # Queue for callbacks (bounded, discard old, keep new, to avoid memory bloat)
623
+ # maxsize=32 can buffer high-frequency data for 1-2 seconds, balancing memory usage and data integrity
624
+ queue: asyncio.Queue = asyncio.Queue(maxsize=32)
625
+ # Used to pass tool interrupt information
626
+ interrupt_info = None
627
+ # Track last answer for delta calculation
628
+ last_answer = {} if stream_mode == "delta" else None
629
+
630
+ def _queue_put_latest(payload):
631
+ try:
632
+ queue.put_nowait(payload)
633
+ except asyncio.QueueFull:
634
+ try:
635
+ queue.get_nowait() # Drop old items
636
+ except asyncio.QueueEmpty:
637
+ pass
638
+ try:
639
+ queue.put_nowait(payload)
640
+ except asyncio.QueueFull:
641
+ # Ignore this update in extreme cases to avoid blocking
642
+ pass
643
+
644
+ def _progress_cb(_resp):
645
+ # Self-check: Only deliver when the current callback is still this closure, to avoid incorrect delivery caused by concurrent overwriting.
646
+ if getattr(self, "_progress_callback", None) is not _progress_cb:
647
+ return
648
+ try:
649
+ ctx = self.executor.context if self.executor else None
650
+ if ctx is None:
651
+ return
652
+ if self.output_variables is not None and len(self.output_variables) > 0:
653
+ data = ctx.get_variables_values(self.output_variables)
654
+ else:
655
+ data = ctx.get_all_variables_values()
656
+
657
+ # Apply delta mode if requested
658
+ if stream_mode == "delta" and last_answer is not None:
659
+ data = self._apply_delta_mode(data, last_answer)
660
+
661
+ _queue_put_latest(data)
662
+ except Exception:
663
+ # Callback exceptions must not affect main execution
664
+ pass
665
+
666
+ # Install callback
667
+ setattr(self, "_progress_callback", _progress_cb)
668
+
669
+ async def _drive_base_arun():
670
+ nonlocal interrupt_info
671
+ # Reuse base class state machine/event: Fast mode execution
672
+ try:
673
+ async for item in super(DolphinAgent, self).arun(
674
+ run_mode=True, **kwargs
675
+ ):
676
+ # Check if it is an interruption (unified status, type in interrupt_type field)
677
+ if isinstance(item, dict) and item.get("status") == "interrupted":
678
+ interrupt_info = item
679
+ # Do not push to the queue, output uniformly by the outer layer (to avoid duplication)
680
+ return # Stop execution and let the outer layer pass interrupt information
681
+ finally:
682
+ # Clean up callbacks to avoid leakage into subsequent runs
683
+ if hasattr(self, "_progress_callback"):
684
+ delattr(self, "_progress_callback")
685
+
686
+ # Start Execution
687
+ runner = asyncio.create_task(_drive_base_arun())
688
+
689
+ try:
690
+ # Consume the progress queue until execution ends
691
+ while True:
692
+ try:
693
+ # 优先从队列Get数据,使用较短的超时避免runner完成后检测延迟过大
694
+ # Timeout values balance responsiveness and CPU efficiency: even if the item interval > 0.1s, it will still be fetched in the next loop iteration.
695
+ item = await asyncio.wait_for(queue.get(), timeout=0.1)
696
+ yield item
697
+ except asyncio.TimeoutError:
698
+ # Check runner status only after timeout to avoid race conditions
699
+ if runner.done():
700
+ # Clear the remaining data in the queue, using exception handling instead of empty() checks
701
+ while True:
702
+ try:
703
+ yield queue.get_nowait()
704
+ except asyncio.QueueEmpty:
705
+ break
706
+ break
707
+ finally:
708
+ # Ensure backend tasks are completed
709
+ if not runner.done():
710
+ runner.cancel()
711
+ try:
712
+ # Always await, regardless of whether done or not, to re-raise any possible exceptions.
713
+ await runner
714
+ except asyncio.CancelledError:
715
+ # Active cancellation is expected behavior
716
+ pass
717
+ except Exception:
718
+ # Log and re-raise the exception to inform the caller that an error has occurred.
719
+ self._logger.exception(
720
+ "Background execution failed in stream_variables mode"
721
+ )
722
+ raise
723
+
724
+ # At the end:
725
+ if interrupt_info is not None:
726
+ # If there is tool interruption information, ensure the caller definitely obtains it again (even with queue consumption race conditions)
727
+ yield interrupt_info
728
+ else:
729
+ # Complete normally by capturing one final frame of variable snapshots to avoid missing the last state.
730
+ try:
731
+ ctx = self.executor.context if self.executor else None
732
+ if ctx is not None:
733
+ if (
734
+ self.output_variables is not None
735
+ and len(self.output_variables) > 0
736
+ ):
737
+ final_data = ctx.get_variables_values(self.output_variables)
738
+ else:
739
+ final_data = ctx.get_all_variables_values()
740
+
741
+ # Apply delta mode to final data
742
+ if stream_mode == "delta" and last_answer is not None:
743
+ final_data = self._apply_delta_mode(final_data, last_answer)
744
+
745
+ yield final_data
746
+ except (AttributeError, KeyError) as e:
747
+ # Expected errors when context/variables are not fully initialized
748
+ self._logger.debug(f"Could not get final variables in arun: {e}")
749
+
750
+ async def _on_pause_coroutine(self):
751
+ """Pause coroutine"""
752
+ if self.executor is None:
753
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
754
+
755
+ if self._current_frame is None:
756
+ raise DolphinAgentException("NO_FRAME", "No execution frame available")
757
+
758
+ try:
759
+ handle = await self.executor.pause_coroutine(self._current_frame.frame_id)
760
+ return handle
761
+
762
+ except Exception as e:
763
+ self._logger.error(f"Pause coroutine error: {e}")
764
+ raise DolphinAgentException("PAUSE_COROUTINE_FAILED", str(e))
765
+
766
+ async def _on_resume_coroutine(self, updates: Optional[Dict[str, Any]] = None):
767
+ """Resume coroutine"""
768
+ if self.executor is None:
769
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
770
+
771
+ if self._resume_handle is None:
772
+ raise DolphinAgentException(
773
+ "NO_RESUME_HANDLE", "No resume handle available"
774
+ )
775
+
776
+ try:
777
+ frame = await self.executor.resume_coroutine(self._resume_handle, updates)
778
+ return frame
779
+
780
+ except Exception as e:
781
+ self._logger.error(f"Resume coroutine error: {e}")
782
+ raise DolphinAgentException("RESUME_COROUTINE_FAILED", str(e))
783
+
784
+ async def _on_terminate_coroutine(self):
785
+ """Terminate coroutine"""
786
+ if self.executor is None:
787
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
788
+
789
+ if self._current_frame is None:
790
+ # No frame executed, return directly
791
+ return
792
+
793
+ try:
794
+ await self.executor.terminate_coroutine(self._current_frame.frame_id)
795
+
796
+ except Exception as e:
797
+ self._logger.error(f"Terminate coroutine error: {e}")
798
+ raise DolphinAgentException("TERMINATE_COROUTINE_FAILED", str(e))
799
+
800
+ def get_content_source(self) -> str:
801
+ """Get content source type"""
802
+ return getattr(self, "content_source", "unknown")
803
+
804
+ def get_file_path(self) -> Optional[str]:
805
+ """Get DPH file path"""
806
+ return self.file_path
807
+
808
+ def get_execution_trace(self, title=None):
809
+ """Get Execution Trace"""
810
+ if self.executor:
811
+ return self.executor.get_execution_trace(title)
812
+ else:
813
+ return "Execution trace not available: Agent not initialized"
814
+
815
+ # Backward compatibility: retain old method names
816
+ def get_profile(self, title=None):
817
+ """[Deprecated] Please use get_execution_trace() instead"""
818
+ import warnings
819
+ warnings.warn("get_profile() 已废弃,请使用 get_execution_trace()", DeprecationWarning, stacklevel=2)
820
+ return self.get_execution_trace(title)
821
+
822
+ def get_content(self) -> Optional[str]:
823
+ """Get Agent content"""
824
+ return self.content
825
+
826
+ def get_header_info(self) -> Dict[str, str]:
827
+ """Get all parsed header information
828
+
829
+ Returns:
830
+ A dictionary containing all header information, for example {'DESC': '...', 'VERSION': '...'}
831
+ """
832
+ return self.header_info.copy()
833
+
834
+ def get_desc(self) -> str:
835
+ """Get Agent description (from @DESC header or constructor parameters)
836
+
837
+ Returns:
838
+ Agent description string
839
+ """
840
+ return self.description or self.header_info.get("DESC", "")
841
+
842
+ async def append_incremental_message(self, payload: dict):
843
+ """Append an incremental message to the current coroutine (using prefix cache)
844
+
845
+ Args:
846
+ payload: Status or event information, such as {"event_type": "...", "content": "...", "metadata": {...}}
847
+ """
848
+ if self.executor is None:
849
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
850
+
851
+ await self.executor.append_incremental_message(payload)
852
+
853
+ async def append_round_message(self, round_info: dict):
854
+ """Compatible with old names: Please use append_incremental_message instead."""
855
+ await self.append_incremental_message(round_info)
856
+
857
+ def set_context(self, context: Context):
858
+ """Set the Agent's context"""
859
+ if self.executor:
860
+ self.executor.set_context(context)
861
+
862
+ def sync_variables(self, context: Context):
863
+ """Synchronize variables to the specified context"""
864
+ if self.executor:
865
+ # Synchronize the variables of the current execution context to the target context
866
+ context.sync_variables(self.executor.context)
867
+
868
+ def get_context(self) -> Optional[Context]:
869
+ """Get the execution context of the Agent"""
870
+ if self.executor is None:
871
+ return None
872
+ return self.executor.context
873
+
874
+ def set_user_id(self, user_id: str):
875
+ """Set user ID"""
876
+ if self.executor is None:
877
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
878
+ self.executor.context.set_user_id(user_id)
879
+
880
+ def set_session_id(self, session_id: str):
881
+ """Set session ID"""
882
+ if self.executor is None:
883
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
884
+ self.executor.context.set_session_id(session_id)
885
+
886
+ def add_user_message(self, content: str, bucket: str = None):
887
+ """Add user message"""
888
+ if self.executor is None:
889
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
890
+ self.executor.context.add_user_message(content, bucket=bucket)
891
+
892
+ def get_messages(self):
893
+ """Get message list"""
894
+ if self.executor is None:
895
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
896
+ return self.executor.context.get_messages()
897
+
898
+ def get_skillkit(self, skillNames: Optional[list] = None):
899
+ """Get Skill Package"""
900
+ if self.executor is None:
901
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
902
+ return self.executor.context.get_skillkit(skillNames)
903
+
904
+ def get_config(self):
905
+ """Get configuration"""
906
+ if self.executor is None:
907
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
908
+ return self.executor.context.get_config()
909
+
910
+ def save_trajectory(self, agent_name: str = None, force_save: bool = False, trajectory_path: str = None):
911
+ """Save execution trace"""
912
+ if self.executor is None:
913
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
914
+ self.executor.context.save_trajectory(
915
+ agent_name=agent_name or self.name,
916
+ force_save=force_save,
917
+ trajectory_path=trajectory_path
918
+ )
919
+
920
+ def get_snapshot_analysis(self, title=None, format='markdown', options=None):
921
+ """Get snapshot analysis"""
922
+ if self.executor is None:
923
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
924
+ return self.executor.context.get_snapshot_analysis(title=title, format=format, options=options)
925
+
926
+ def get_all_variables(self):
927
+ """Get all variables"""
928
+ if self.executor is None:
929
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
930
+ return self.executor.context.get_all_variables()
931
+
932
+ def get_var_value(self, name, default_value=None):
933
+ """Get variable value"""
934
+ if self.executor is None:
935
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
936
+ return self.executor.context.get_var_value(name, default_value)
937
+
938
+ def add_bucket(self, bucket_name: str, content, priority: float = 1.0, allocated_tokens: Optional[int] = None, message_role=None):
939
+ """Add context buckets"""
940
+ if self.executor is None:
941
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
942
+ self.executor.context.add_bucket(
943
+ bucket_name=bucket_name,
944
+ content=content,
945
+ priority=priority,
946
+ allocated_tokens=allocated_tokens,
947
+ message_role=message_role
948
+ )
949
+
950
+ def set_trajectorypath(self, trajectorypath: str):
951
+ """Set trajectory path"""
952
+ if self.executor is None:
953
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
954
+ self.executor.context.trajectorypath = trajectorypath
955
+
956
+ def set_agent_name(self, agent_name: str):
957
+ """Set agent name (in context)"""
958
+ if self.executor is None:
959
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
960
+ self.executor.context.agent_name = agent_name
961
+
962
+ def set_cur_agent(self, agent):
963
+ """Set the current agent (in context)"""
964
+ if self.executor is None:
965
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
966
+ self.executor.context.set_cur_agent(agent)
967
+
968
+ def set_skills(self, skillkit):
969
+ """Set Skill Pack"""
970
+ if self.executor is None:
971
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
972
+ self.executor.context.set_skills(skillkit)
973
+
974
+ def get_skillkit_raw(self):
975
+ """Get the original skillkit object (for scenarios where direct access to skillkit is required)"""
976
+ if self.executor is None:
977
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
978
+ return self.executor.context.skillkit
979
+
980
+ def get_context_messages_dict(self):
981
+ """Get the messages dictionary of context (for debugging)"""
982
+ if self.executor is None:
983
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
984
+ return self.executor.context.messages
985
+
986
+ def get_context_manager(self):
987
+ """Get context_manager (for debugging)"""
988
+ if self.executor is None:
989
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
990
+ return self.executor.context.context_manager
991
+
992
+ async def start_coroutine(self, **kwargs):
993
+ """Public method: explicitly start coroutine execution
994
+
995
+ Used in scenarios where manual control over coroutine startup is required
996
+ """
997
+ # Keep consistent with the state machine of BaseAgent
998
+ if self.state == AgentState.CREATED:
999
+ await self.initialize()
1000
+
1001
+ # If the previous one has been completed, clean up and return to INITIALIZED
1002
+ if self.state == AgentState.COMPLETED:
1003
+ try:
1004
+ if self._current_frame is not None:
1005
+ await self._on_terminate_coroutine()
1006
+ except Exception:
1007
+ pass
1008
+ self._current_frame = None
1009
+ self._resume_handle = None
1010
+ self._pause_type = None
1011
+ await self._change_state(
1012
+ AgentState.INITIALIZED, "Agent reinitialized for new run"
1013
+ )
1014
+
1015
+ # Explicit start is only allowed in the INITIALIZED state; explicit start in RUNNING/PAUSED states would create ambiguity, hence rejected.
1016
+ if self.state != AgentState.INITIALIZED:
1017
+ raise DolphinAgentException(
1018
+ "INVALID_STATE",
1019
+ f"start_coroutine() not allowed from state {self.state.value}",
1020
+ )
1021
+
1022
+ # Clean up control signals and switch to RUNNING
1023
+ self._terminate_event.clear()
1024
+ self._pause_event.set()
1025
+ await self._change_state(
1026
+ AgentState.RUNNING, "Agent started via start_coroutine()"
1027
+ )
1028
+
1029
+ # Start Execution Frame
1030
+ self._current_frame = await self._on_start_coroutine(**kwargs)
1031
+ return self._current_frame
1032
+
1033
+ async def resume_coroutine(self, handle, updates=None):
1034
+ """Resume coroutine execution (resume from tool interruption)
1035
+
1036
+ Args:
1037
+ handle: ResumeHandle resume handle
1038
+ updates: variable updates to inject
1039
+
1040
+ Returns:
1041
+ ExecutionFrame: the execution frame after resumption
1042
+ """
1043
+ if self.executor is None:
1044
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
1045
+
1046
+ # Explicit resume API is only allowed when in PAUSED state
1047
+ if self.state != AgentState.PAUSED:
1048
+ raise DolphinAgentException(
1049
+ "INVALID_STATE",
1050
+ f"resume_coroutine() not allowed from state {self.state.value}",
1051
+ )
1052
+
1053
+ frame = await self.executor.resume_coroutine(handle, updates)
1054
+ self._current_frame = frame
1055
+ self._resume_handle = None
1056
+
1057
+ # Keep the same behavior as BaseAgent.resume(): resume the pause, switch to RUNNING, and trigger on_resume
1058
+ self._pause_event.set()
1059
+ await self._change_state(
1060
+ AgentState.RUNNING, "Agent resumed via resume_coroutine()"
1061
+ )
1062
+ await self._on_resume()
1063
+ return frame
1064
+
1065
+ def get_intervention_data(self) -> dict:
1066
+ """Get interrupt data (such as tool call parameters)
1067
+
1068
+ Returns:
1069
+ dict: Interrupt-related data
1070
+ """
1071
+ if self.executor is None:
1072
+ raise DolphinAgentException("NOT_INITIALIZED", "Agent not initialized")
1073
+
1074
+ return self.executor.get_intervention_data()
1075
+
1076
+ def get_execution_info(self) -> Dict[str, Any]:
1077
+ """Get execution information"""
1078
+ info = {
1079
+ "name": self.name,
1080
+ "description": self.description,
1081
+ "state": self.state.value,
1082
+ "execution_mode": "coroutine", # Explicitly identified as coroutine mode
1083
+ "content_source": self.get_content_source(),
1084
+ }
1085
+
1086
+ if self._current_frame is not None:
1087
+ info["frame_id"] = self._current_frame.frame_id
1088
+ info["frame_status"] = self._current_frame.status.value
1089
+ info["block_pointer"] = self._current_frame.block_pointer
1090
+
1091
+ if self._resume_handle is not None:
1092
+ info["has_resume_handle"] = True
1093
+
1094
+ return info
1095
+
1096
+ def __str__(self) -> str:
1097
+ if self.get_content_source() == "file":
1098
+ return f"DolphinAgent(name={self.name}, file={self.file_path}, state={self.state.value})"
1099
+ else:
1100
+ if self.content is None:
1101
+ return f"DolphinAgent(name={self.name}, state={self.state.value})"
1102
+
1103
+ content_preview = (
1104
+ self.content[:50] + "..." if len(self.content) > 50 else self.content
1105
+ )
1106
+ return f"DolphinAgent(name={self.name}, content_source={self.get_content_source()}, content_preview='{content_preview}', state={self.state.value})"