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,109 @@
|
|
|
1
|
+
"""Configuration models for ResourceSkillkit.
|
|
2
|
+
|
|
3
|
+
This module contains configuration dataclasses for ResourceSkillkit settings.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Default values as module constants to avoid duplication
|
|
12
|
+
_DEFAULT_DIRECTORIES = ["./skills"]
|
|
13
|
+
_DEFAULT_MAX_SKILL_SIZE_MB = 8
|
|
14
|
+
_DEFAULT_MAX_CONTENT_TOKENS = 8000
|
|
15
|
+
_DEFAULT_CACHE_TTL_SECONDS = 300
|
|
16
|
+
_DEFAULT_MAX_CACHE_SIZE = 100
|
|
17
|
+
_DEFAULT_MAX_SCAN_DEPTH = 6
|
|
18
|
+
_DEFAULT_ALLOWED_EXTENSIONS = [
|
|
19
|
+
".py", ".sh", ".js", ".ts", ".md", ".txt", ".json", ".yaml", ".yml"
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ResourceSkillConfig:
|
|
25
|
+
"""Configuration for ResourceSkillkit.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
enabled: Whether ResourceSkillkit is enabled
|
|
29
|
+
directories: List of directories to scan for skills (in priority order)
|
|
30
|
+
max_skill_size_mb: Maximum size of a single skill package in MB
|
|
31
|
+
max_content_tokens: Maximum tokens to return in a single load
|
|
32
|
+
cache_ttl_seconds: TTL for metadata cache in seconds
|
|
33
|
+
max_cache_size: Maximum number of items in cache
|
|
34
|
+
max_scan_depth: Maximum directory depth when scanning for skills
|
|
35
|
+
allowed_extensions: File extensions allowed for resource files
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
enabled: bool = True
|
|
39
|
+
directories: List[str] = field(default_factory=lambda: list(_DEFAULT_DIRECTORIES))
|
|
40
|
+
max_skill_size_mb: int = _DEFAULT_MAX_SKILL_SIZE_MB
|
|
41
|
+
max_content_tokens: int = _DEFAULT_MAX_CONTENT_TOKENS
|
|
42
|
+
cache_ttl_seconds: int = _DEFAULT_CACHE_TTL_SECONDS
|
|
43
|
+
max_cache_size: int = _DEFAULT_MAX_CACHE_SIZE
|
|
44
|
+
max_scan_depth: int = _DEFAULT_MAX_SCAN_DEPTH
|
|
45
|
+
allowed_extensions: List[str] = field(
|
|
46
|
+
default_factory=lambda: list(_DEFAULT_ALLOWED_EXTENSIONS)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_dict(cls, config_dict: dict) -> "ResourceSkillConfig":
|
|
51
|
+
"""Create config from dictionary (e.g., from YAML config).
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
config_dict: Dictionary with configuration values
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
ResourceSkillConfig instance
|
|
58
|
+
"""
|
|
59
|
+
resource_config = config_dict.get("resource_skills", {})
|
|
60
|
+
|
|
61
|
+
limits = resource_config.get("limits", {})
|
|
62
|
+
|
|
63
|
+
return cls(
|
|
64
|
+
enabled=resource_config.get("enabled", True),
|
|
65
|
+
directories=resource_config.get("directories", _DEFAULT_DIRECTORIES),
|
|
66
|
+
max_skill_size_mb=limits.get("max_skill_size_mb", _DEFAULT_MAX_SKILL_SIZE_MB),
|
|
67
|
+
max_content_tokens=limits.get("max_content_tokens", _DEFAULT_MAX_CONTENT_TOKENS),
|
|
68
|
+
cache_ttl_seconds=resource_config.get("cache_ttl_seconds", _DEFAULT_CACHE_TTL_SECONDS),
|
|
69
|
+
max_cache_size=resource_config.get("max_cache_size", _DEFAULT_MAX_CACHE_SIZE),
|
|
70
|
+
max_scan_depth=resource_config.get("max_scan_depth", _DEFAULT_MAX_SCAN_DEPTH),
|
|
71
|
+
allowed_extensions=resource_config.get("allowed_extensions", _DEFAULT_ALLOWED_EXTENSIONS),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def get_resolved_directories(self, base_path: Optional[Path] = None) -> List[Path]:
|
|
75
|
+
"""Resolve directory paths, expanding ~ and making absolute.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
base_path: Optional base path for resolving relative paths
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of resolved absolute Path objects
|
|
82
|
+
"""
|
|
83
|
+
resolved = []
|
|
84
|
+
for dir_str in self.directories:
|
|
85
|
+
path = Path(dir_str).expanduser()
|
|
86
|
+
if not path.is_absolute():
|
|
87
|
+
if base_path:
|
|
88
|
+
path = base_path / path
|
|
89
|
+
else:
|
|
90
|
+
path = Path.cwd() / path
|
|
91
|
+
resolved.append(path.resolve())
|
|
92
|
+
return resolved
|
|
93
|
+
|
|
94
|
+
def to_dict(self) -> dict:
|
|
95
|
+
"""Convert to dictionary representation.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Dictionary with all configuration fields
|
|
99
|
+
"""
|
|
100
|
+
return {
|
|
101
|
+
"enabled": self.enabled,
|
|
102
|
+
"directories": self.directories,
|
|
103
|
+
"max_skill_size_mb": self.max_skill_size_mb,
|
|
104
|
+
"max_content_tokens": self.max_content_tokens,
|
|
105
|
+
"cache_ttl_seconds": self.cache_ttl_seconds,
|
|
106
|
+
"max_cache_size": self.max_cache_size,
|
|
107
|
+
"max_scan_depth": self.max_scan_depth,
|
|
108
|
+
"allowed_extensions": self.allowed_extensions,
|
|
109
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Data models for ResourceSkillkit.
|
|
2
|
+
|
|
3
|
+
This module contains the SkillMeta and SkillContent dataclasses for
|
|
4
|
+
representing skill metadata (Level 1) and full content (Level 2).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import List, Optional, Dict, Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SkillMeta:
|
|
13
|
+
"""Level 1 metadata - lightweight skill information.
|
|
14
|
+
|
|
15
|
+
This represents the minimal metadata loaded for each skill,
|
|
16
|
+
used for system prompt injection (~100 tokens per skill).
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
name: Unique skill identifier
|
|
20
|
+
description: Brief description of what the skill does
|
|
21
|
+
base_path: Absolute path to the skill directory
|
|
22
|
+
version: Optional semantic version string
|
|
23
|
+
tags: Optional list of categorization tags
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
description: str
|
|
28
|
+
base_path: str
|
|
29
|
+
version: Optional[str] = None
|
|
30
|
+
tags: List[str] = field(default_factory=list)
|
|
31
|
+
|
|
32
|
+
def to_prompt_entry(self) -> str:
|
|
33
|
+
"""Generate a markdown entry for system prompt injection.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Formatted markdown string for this skill's metadata
|
|
37
|
+
"""
|
|
38
|
+
return f"### {self.name}\n{self.description}"
|
|
39
|
+
|
|
40
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
41
|
+
"""Convert to dictionary representation.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Dictionary with all metadata fields
|
|
45
|
+
"""
|
|
46
|
+
return {
|
|
47
|
+
"name": self.name,
|
|
48
|
+
"description": self.description,
|
|
49
|
+
"base_path": self.base_path,
|
|
50
|
+
"version": self.version,
|
|
51
|
+
"tags": self.tags,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class SkillContent:
|
|
57
|
+
"""Level 2 full content - complete SKILL.md content.
|
|
58
|
+
|
|
59
|
+
This represents the fully loaded skill content including
|
|
60
|
+
the YAML frontmatter and markdown body (~1500 tokens).
|
|
61
|
+
|
|
62
|
+
Attributes:
|
|
63
|
+
frontmatter: Parsed YAML frontmatter as dictionary
|
|
64
|
+
body: The markdown content after frontmatter
|
|
65
|
+
available_scripts: List of script file paths in scripts/ directory
|
|
66
|
+
available_references: List of reference file paths in references/ directory
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
frontmatter: Dict[str, Any]
|
|
70
|
+
body: str
|
|
71
|
+
available_scripts: List[str] = field(default_factory=list)
|
|
72
|
+
available_references: List[str] = field(default_factory=list)
|
|
73
|
+
|
|
74
|
+
def get_name(self) -> str:
|
|
75
|
+
"""Get skill name from frontmatter.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The skill name, or empty string if not found
|
|
79
|
+
"""
|
|
80
|
+
return self.frontmatter.get("name", "")
|
|
81
|
+
|
|
82
|
+
def get_description(self) -> str:
|
|
83
|
+
"""Get skill description from frontmatter.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The skill description, or empty string if not found
|
|
87
|
+
"""
|
|
88
|
+
return self.frontmatter.get("description", "")
|
|
89
|
+
|
|
90
|
+
def get_full_content(self) -> str:
|
|
91
|
+
"""Get the complete content for Level 2 loading.
|
|
92
|
+
|
|
93
|
+
This includes the markdown body plus a list of available
|
|
94
|
+
resources that can be loaded via Level 3.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Complete formatted content string
|
|
98
|
+
"""
|
|
99
|
+
content_parts = [self.body]
|
|
100
|
+
|
|
101
|
+
if self.available_scripts or self.available_references:
|
|
102
|
+
content_parts.append("\n\n## Available Resources\n")
|
|
103
|
+
|
|
104
|
+
if self.available_scripts:
|
|
105
|
+
content_parts.append("\n**Scripts:**")
|
|
106
|
+
for script in self.available_scripts:
|
|
107
|
+
content_parts.append(f"\n- `{script}`")
|
|
108
|
+
|
|
109
|
+
if self.available_references:
|
|
110
|
+
content_parts.append("\n\n**References:**")
|
|
111
|
+
for ref in self.available_references:
|
|
112
|
+
content_parts.append(f"\n- `{ref}`")
|
|
113
|
+
|
|
114
|
+
return "".join(content_parts)
|
|
115
|
+
|
|
116
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
117
|
+
"""Convert to dictionary representation.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Dictionary with all content fields
|
|
121
|
+
"""
|
|
122
|
+
return {
|
|
123
|
+
"frontmatter": self.frontmatter,
|
|
124
|
+
"body": self.body,
|
|
125
|
+
"available_scripts": self.available_scripts,
|
|
126
|
+
"available_references": self.available_references,
|
|
127
|
+
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""ResourceSkillkit - Claude Skill format support for Dolphin.
|
|
2
|
+
|
|
3
|
+
This module implements ResourceSkillkit, a Skillkit type that supports
|
|
4
|
+
resource/guidance skills in Claude Skill format (SKILL.md).
|
|
5
|
+
|
|
6
|
+
Key features:
|
|
7
|
+
- Progressive loading (Level 1/2/3)
|
|
8
|
+
- Prefix Cache optimization
|
|
9
|
+
- History bucket persistence
|
|
10
|
+
- Local-first design
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import List, Optional, Dict, Any
|
|
15
|
+
|
|
16
|
+
from dolphin.core.common.constants import PIN_MARKER
|
|
17
|
+
from dolphin.core.skill.skill_function import SkillFunction
|
|
18
|
+
from dolphin.core.skill.skillkit import Skillkit
|
|
19
|
+
from dolphin.core.logging.logger import get_logger
|
|
20
|
+
|
|
21
|
+
from .models.skill_meta import SkillMeta, SkillContent
|
|
22
|
+
from .models.skill_config import ResourceSkillConfig
|
|
23
|
+
from .skill_loader import SkillLoader, truncate_content
|
|
24
|
+
from .skill_cache import SkillMetaCache, SkillContentCache
|
|
25
|
+
from .skill_validator import validate_skill_name
|
|
26
|
+
|
|
27
|
+
logger = get_logger("resource_skillkit")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ResourceSkillkit(Skillkit):
|
|
31
|
+
"""ResourceSkillkit - Support for Claude Skill format resources.
|
|
32
|
+
|
|
33
|
+
This Skillkit type provides support for resource/guidance skills
|
|
34
|
+
that teach LLMs how to solve complex problems. Unlike execution
|
|
35
|
+
skillkits (SQL, Python, MCP), ResourceSkillkit loads knowledge
|
|
36
|
+
resources in Claude Skill format (SKILL.md).
|
|
37
|
+
|
|
38
|
+
Progressive Loading:
|
|
39
|
+
- Level 1: Metadata (~100 tokens) - auto-injected to system prompt
|
|
40
|
+
- Level 2: Full SKILL.md content (~1500 tokens) - via tool call
|
|
41
|
+
- Level 3: Resource files (scripts/references) - on-demand
|
|
42
|
+
|
|
43
|
+
Prefix Cache Optimization:
|
|
44
|
+
- Level 1 metadata is fixed in system prompt
|
|
45
|
+
- Level 2 content enters _history bucket via tool response
|
|
46
|
+
- System prompt remains stable for cache hits
|
|
47
|
+
|
|
48
|
+
Example usage:
|
|
49
|
+
```python
|
|
50
|
+
config = ResourceSkillConfig(
|
|
51
|
+
directories=["./skills", "~/.dolphin/skills"]
|
|
52
|
+
)
|
|
53
|
+
skillkit = ResourceSkillkit(config)
|
|
54
|
+
skillkit.initialize()
|
|
55
|
+
|
|
56
|
+
# Get metadata prompt for system injection
|
|
57
|
+
metadata_prompt = skillkit.getMetadataPrompt()
|
|
58
|
+
|
|
59
|
+
# Load full skill content (returns tool response)
|
|
60
|
+
content = skillkit.load_skill("data-pipeline")
|
|
61
|
+
```
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, config: Optional[ResourceSkillConfig] = None):
|
|
65
|
+
"""Initialize ResourceSkillkit.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
config: Optional configuration, uses defaults if not provided
|
|
69
|
+
"""
|
|
70
|
+
super().__init__()
|
|
71
|
+
self.config = config or ResourceSkillConfig()
|
|
72
|
+
self.loader = SkillLoader(self.config)
|
|
73
|
+
self._meta_cache = SkillMetaCache(
|
|
74
|
+
ttl_seconds=self.config.cache_ttl_seconds,
|
|
75
|
+
max_size=self.config.max_cache_size,
|
|
76
|
+
)
|
|
77
|
+
self._content_cache = SkillContentCache(
|
|
78
|
+
ttl_seconds=self.config.cache_ttl_seconds * 2,
|
|
79
|
+
max_size=max(1, self.config.max_cache_size // 2),
|
|
80
|
+
)
|
|
81
|
+
self._initialized = False
|
|
82
|
+
self._skills_meta: Dict[str, SkillMeta] = {}
|
|
83
|
+
self._base_path: Optional[Path] = None
|
|
84
|
+
|
|
85
|
+
def setGlobalConfig(self, globalConfig) -> None:
|
|
86
|
+
super().setGlobalConfig(globalConfig)
|
|
87
|
+
resource_skills_cfg = getattr(globalConfig, "resource_skills", None)
|
|
88
|
+
if not isinstance(resource_skills_cfg, dict):
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
new_config = ResourceSkillConfig.from_dict(
|
|
93
|
+
{"resource_skills": resource_skills_cfg}
|
|
94
|
+
)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.warning(f"Failed to parse resource_skills config: {e}")
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
self.config = new_config
|
|
100
|
+
self.loader = SkillLoader(self.config)
|
|
101
|
+
self._meta_cache = SkillMetaCache(
|
|
102
|
+
ttl_seconds=self.config.cache_ttl_seconds,
|
|
103
|
+
max_size=self.config.max_cache_size,
|
|
104
|
+
)
|
|
105
|
+
self._content_cache = SkillContentCache(
|
|
106
|
+
ttl_seconds=self.config.cache_ttl_seconds * 2,
|
|
107
|
+
max_size=max(1, self.config.max_cache_size // 2),
|
|
108
|
+
)
|
|
109
|
+
# Clear old state to avoid stale data
|
|
110
|
+
self._skills_meta.clear()
|
|
111
|
+
self._initialized = False
|
|
112
|
+
|
|
113
|
+
# Get base_dir from GlobalConfig to resolve relative paths
|
|
114
|
+
base_dir = getattr(globalConfig, "base_dir", None)
|
|
115
|
+
if base_dir:
|
|
116
|
+
from pathlib import Path
|
|
117
|
+
self._base_path = Path(base_dir)
|
|
118
|
+
|
|
119
|
+
def _ensure_initialized(self) -> None:
|
|
120
|
+
"""Ensure the skillkit has scanned and loaded Level 1 metadata."""
|
|
121
|
+
if self._initialized:
|
|
122
|
+
return
|
|
123
|
+
self.initialize(self._base_path)
|
|
124
|
+
|
|
125
|
+
def getName(self) -> str:
|
|
126
|
+
"""Get the name of this skillkit.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
The skillkit name
|
|
130
|
+
"""
|
|
131
|
+
return "resource_skillkit"
|
|
132
|
+
|
|
133
|
+
def initialize(self, base_path: Optional[Path] = None) -> None:
|
|
134
|
+
"""Initialize the skillkit by scanning for available skills.
|
|
135
|
+
|
|
136
|
+
This scans all configured directories and loads Level 1 metadata
|
|
137
|
+
for each valid skill found.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
base_path: Optional base path for resolving relative directories
|
|
141
|
+
"""
|
|
142
|
+
self._base_path = base_path
|
|
143
|
+
self._skills_meta.clear()
|
|
144
|
+
self._meta_cache.clear()
|
|
145
|
+
|
|
146
|
+
if not self.config.enabled:
|
|
147
|
+
logger.info("ResourceSkillkit is disabled")
|
|
148
|
+
self._initialized = True
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
# Scan directories and load metadata
|
|
152
|
+
metas = self.loader.scan_directories(base_path)
|
|
153
|
+
|
|
154
|
+
for meta in metas:
|
|
155
|
+
self._skills_meta[meta.name] = meta
|
|
156
|
+
self._meta_cache.set(meta.name, meta)
|
|
157
|
+
|
|
158
|
+
logger.info(f"Initialized ResourceSkillkit with {len(self._skills_meta)} skills")
|
|
159
|
+
self._initialized = True
|
|
160
|
+
|
|
161
|
+
def _createSkills(self) -> List[SkillFunction]:
|
|
162
|
+
"""Create the list of skill functions provided by this skillkit.
|
|
163
|
+
|
|
164
|
+
Note: _list_resource_skills is NOT exposed as a tool because
|
|
165
|
+
Level 1 metadata is auto-injected into system prompt via
|
|
166
|
+
get_metadata_prompt(). LLM sees available skills upfront.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
List of SkillFunction objects for Level 2/3 loading
|
|
170
|
+
"""
|
|
171
|
+
return [
|
|
172
|
+
SkillFunction(self._load_resource_skill),
|
|
173
|
+
SkillFunction(self._load_skill_resource),
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
def get_metadata_prompt(self) -> str:
|
|
177
|
+
"""Generate fixed metadata prompt for system injection (Level 1).
|
|
178
|
+
|
|
179
|
+
This generates the Level 1 metadata content that is automatically
|
|
180
|
+
injected into the system prompt. The content is stable and
|
|
181
|
+
sorted alphabetically to ensure Prefix Cache compatibility.
|
|
182
|
+
|
|
183
|
+
This overrides the base Skillkit.get_metadata_prompt() method.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Markdown-formatted metadata prompt with available resource skills.
|
|
187
|
+
Always includes the section header, even when no skills are available.
|
|
188
|
+
"""
|
|
189
|
+
self._ensure_initialized()
|
|
190
|
+
|
|
191
|
+
parts = ["## Available Resource Skills\n"]
|
|
192
|
+
|
|
193
|
+
if not self._skills_meta:
|
|
194
|
+
# Always include the section header to prevent hallucination
|
|
195
|
+
# when user's system prompt references this section
|
|
196
|
+
parts.append("\n_No resource skills are currently available._\n")
|
|
197
|
+
return "".join(parts)
|
|
198
|
+
|
|
199
|
+
# Sort by name for stable ordering (Prefix Cache)
|
|
200
|
+
for name in sorted(self._skills_meta.keys()):
|
|
201
|
+
meta = self._skills_meta[name]
|
|
202
|
+
parts.append(f"\n{meta.to_prompt_entry()}\n")
|
|
203
|
+
|
|
204
|
+
return "".join(parts)
|
|
205
|
+
|
|
206
|
+
def load_skill(self, name: str) -> str:
|
|
207
|
+
"""Load Level 2 full content for a skill.
|
|
208
|
+
|
|
209
|
+
This is the internal method called by _load_resource_skill.
|
|
210
|
+
The returned content enters _history bucket as tool response.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
name: The skill name to load
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Full SKILL.md content or error message
|
|
217
|
+
"""
|
|
218
|
+
self._ensure_initialized()
|
|
219
|
+
|
|
220
|
+
# Validate name
|
|
221
|
+
is_valid, error = validate_skill_name(name)
|
|
222
|
+
if not is_valid:
|
|
223
|
+
return f"Error: {error}"
|
|
224
|
+
|
|
225
|
+
# Check if skill exists
|
|
226
|
+
if name not in self._skills_meta:
|
|
227
|
+
available = sorted(self._skills_meta.keys())
|
|
228
|
+
return (
|
|
229
|
+
f"Error: Skill '{name}' not found.\n"
|
|
230
|
+
f"Available skills: {', '.join(available) if available else 'none'}"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Check content cache
|
|
234
|
+
cached_content = self._content_cache.get(name)
|
|
235
|
+
meta = self._skills_meta[name]
|
|
236
|
+
skill_dir = Path(meta.base_path)
|
|
237
|
+
|
|
238
|
+
# Build path info header to help Agent know where to execute commands
|
|
239
|
+
path_info = f"**Skill Root Directory:** `{skill_dir}`\n\n---\n\n"
|
|
240
|
+
|
|
241
|
+
if cached_content:
|
|
242
|
+
full_content = path_info + cached_content.get_full_content()
|
|
243
|
+
return truncate_content(full_content, self.config.max_content_tokens)
|
|
244
|
+
|
|
245
|
+
# Load from disk
|
|
246
|
+
content = self.loader.load_content(skill_dir)
|
|
247
|
+
if content is None:
|
|
248
|
+
return f"Error: Failed to load content for skill '{name}'"
|
|
249
|
+
|
|
250
|
+
# Cache the content
|
|
251
|
+
self._content_cache.set(name, content)
|
|
252
|
+
|
|
253
|
+
# Truncate if necessary - include path info at the beginning
|
|
254
|
+
full_content = path_info + content.get_full_content()
|
|
255
|
+
return truncate_content(full_content, self.config.max_content_tokens)
|
|
256
|
+
|
|
257
|
+
def load_resource(self, skill_name: str, resource_path: str) -> str:
|
|
258
|
+
"""Load Level 3 resource file content.
|
|
259
|
+
|
|
260
|
+
This is the internal method called by _load_skill_resource.
|
|
261
|
+
The returned content is temporary (scratchpad).
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
skill_name: The skill name
|
|
265
|
+
resource_path: Relative path to resource file
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Resource file content or error message
|
|
269
|
+
"""
|
|
270
|
+
self._ensure_initialized()
|
|
271
|
+
|
|
272
|
+
# Validate skill name
|
|
273
|
+
is_valid, error = validate_skill_name(skill_name)
|
|
274
|
+
if not is_valid:
|
|
275
|
+
return f"Error: {error}"
|
|
276
|
+
|
|
277
|
+
# Check if skill exists
|
|
278
|
+
if skill_name not in self._skills_meta:
|
|
279
|
+
available = sorted(self._skills_meta.keys())
|
|
280
|
+
return (
|
|
281
|
+
f"Error: Skill '{skill_name}' not found.\n"
|
|
282
|
+
f"Available skills: {', '.join(available) if available else 'none'}"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Load resource
|
|
286
|
+
meta = self._skills_meta[skill_name]
|
|
287
|
+
skill_dir = Path(meta.base_path)
|
|
288
|
+
|
|
289
|
+
content, error = self.loader.load_resource(skill_dir, resource_path)
|
|
290
|
+
if error:
|
|
291
|
+
return f"Error: {error}"
|
|
292
|
+
|
|
293
|
+
# Format with file info header
|
|
294
|
+
return f"# {resource_path}\n\n```\n{content}\n```"
|
|
295
|
+
|
|
296
|
+
def clear_caches(self) -> None:
|
|
297
|
+
"""Clear all internal caches.
|
|
298
|
+
|
|
299
|
+
This forces a reload from disk on next access.
|
|
300
|
+
"""
|
|
301
|
+
self._meta_cache.clear()
|
|
302
|
+
self._content_cache.clear()
|
|
303
|
+
|
|
304
|
+
def get_available_skills(self) -> List[str]:
|
|
305
|
+
"""Get list of available skill names.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Sorted list of skill names
|
|
309
|
+
"""
|
|
310
|
+
self._ensure_initialized()
|
|
311
|
+
return sorted(self._skills_meta.keys())
|
|
312
|
+
|
|
313
|
+
def get_skill_meta(self, name: str) -> Optional[SkillMeta]:
|
|
314
|
+
"""Get metadata for a specific skill.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
name: The skill name
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
SkillMeta if found, None otherwise
|
|
321
|
+
"""
|
|
322
|
+
return self._skills_meta.get(name)
|
|
323
|
+
|
|
324
|
+
# =====================================
|
|
325
|
+
# Tool Functions (exposed to LLM)
|
|
326
|
+
# =====================================
|
|
327
|
+
|
|
328
|
+
def _load_resource_skill(self, skill_name: str, **kwargs) -> str:
|
|
329
|
+
"""Load the full instructions for a resource skill.
|
|
330
|
+
|
|
331
|
+
Loads the complete SKILL.md content for the specified skill.
|
|
332
|
+
The loaded content will be available in conversation history
|
|
333
|
+
for subsequent turns.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
skill_name (str): Name of the skill to load
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
str: Full skill content including instructions and available resources
|
|
340
|
+
"""
|
|
341
|
+
content = self.load_skill(skill_name)
|
|
342
|
+
if content.startswith(PIN_MARKER):
|
|
343
|
+
return content
|
|
344
|
+
return f"{PIN_MARKER}\n{content}"
|
|
345
|
+
|
|
346
|
+
def _load_skill_resource(self, skill_name: str, resource_path: str, **kwargs) -> str:
|
|
347
|
+
"""Load a specific resource file from a skill package.
|
|
348
|
+
|
|
349
|
+
Loads content from scripts/ or references/ directories within
|
|
350
|
+
a skill package. The resource path should be relative to the
|
|
351
|
+
skill directory (e.g., "scripts/etl.py").
|
|
352
|
+
|
|
353
|
+
Note: Level 3 resources are designed to be ephemeral (single-turn only).
|
|
354
|
+
Unlike Level 2 SKILL.md content, these resources do NOT use PIN_MARKER
|
|
355
|
+
and will not be persisted to history. They can be reloaded on-demand.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
skill_name (str): Name of the skill
|
|
359
|
+
resource_path (str): Relative path to the resource file
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
str: Resource file content
|
|
363
|
+
"""
|
|
364
|
+
# Level 3: No PIN_MARKER - content goes to scratchpad, discarded after turn
|
|
365
|
+
return self.load_resource(skill_name, resource_path)
|
|
366
|
+
|
|
367
|
+
# =====================================
|
|
368
|
+
# Utility Methods
|
|
369
|
+
# =====================================
|
|
370
|
+
|
|
371
|
+
def refresh(self) -> int:
|
|
372
|
+
"""Refresh the skill list by rescanning directories.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Number of skills found
|
|
376
|
+
"""
|
|
377
|
+
self.initialize(self._base_path)
|
|
378
|
+
return len(self._skills_meta)
|
|
379
|
+
|
|
380
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
381
|
+
"""Get statistics about the skillkit.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Dictionary with statistics
|
|
385
|
+
"""
|
|
386
|
+
return {
|
|
387
|
+
"enabled": self.config.enabled,
|
|
388
|
+
"initialized": self._initialized,
|
|
389
|
+
"skill_count": len(self._skills_meta),
|
|
390
|
+
"meta_cache": self._meta_cache.stats(),
|
|
391
|
+
"content_cache": self._content_cache.stats(),
|
|
392
|
+
"directories": self.config.directories,
|
|
393
|
+
}
|