sourcebot 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 (110) hide show
  1. sourcebot/__init__.py +9 -0
  2. sourcebot/__main__.py +17 -0
  3. sourcebot/bus/__init__.py +4 -0
  4. sourcebot/bus/channel_adapter.py +21 -0
  5. sourcebot/bus/event_bus.py +15 -0
  6. sourcebot/bus/message_models.py +33 -0
  7. sourcebot/bus/outbound_dispatcher.py +15 -0
  8. sourcebot/bus/session_manager.py +20 -0
  9. sourcebot/cli/commands/core/__init__.py +3 -0
  10. sourcebot/cli/commands/core/command_line.py +26 -0
  11. sourcebot/cli/commands/init_commands/__init__.py +3 -0
  12. sourcebot/cli/commands/init_commands/init_global_config.py +30 -0
  13. sourcebot/cli/commands/init_commands/init_workspace_config.py +18 -0
  14. sourcebot/cli/commands/run_commands/__init__.py +3 -0
  15. sourcebot/cli/commands/run_commands/command_line_tool.py +345 -0
  16. sourcebot/cli/commands/run_commands/safe_runner.py +47 -0
  17. sourcebot/cli/main.py +28 -0
  18. sourcebot/config/__init__.py +15 -0
  19. sourcebot/config/base.py +13 -0
  20. sourcebot/config/config_manager.py +367 -0
  21. sourcebot/config/exceptions.py +4 -0
  22. sourcebot/config/global_config.py +55 -0
  23. sourcebot/config/provider_config.py +62 -0
  24. sourcebot/config/workspace_config.py +106 -0
  25. sourcebot/context/__init__.py +5 -0
  26. sourcebot/context/context_builder.py +78 -0
  27. sourcebot/context/identity.py +19 -0
  28. sourcebot/context/message_builder.py +154 -0
  29. sourcebot/context/skill/__init__.py +7 -0
  30. sourcebot/context/skill/skill.py +11 -0
  31. sourcebot/context/skill/skill_context.py +10 -0
  32. sourcebot/context/skill/skill_loader.py +57 -0
  33. sourcebot/context/skill/skill_metadata.py +27 -0
  34. sourcebot/context/skill/skill_requirements.py +25 -0
  35. sourcebot/context/skill/skill_summary.py +31 -0
  36. sourcebot/conversation/__init__.py +2 -0
  37. sourcebot/conversation/service.py +191 -0
  38. sourcebot/docker_sandbox/__init__.py +3 -0
  39. sourcebot/docker_sandbox/docker_sandbox.py +113 -0
  40. sourcebot/llm/__init__.py +3 -0
  41. sourcebot/llm/anthropic/__init__.py +2 -0
  42. sourcebot/llm/anthropic/adapter.py +30 -0
  43. sourcebot/llm/anthropic/anthropic_llm_client.py +38 -0
  44. sourcebot/llm/anthropic/converter.py +59 -0
  45. sourcebot/llm/core/adapter.py +16 -0
  46. sourcebot/llm/core/client.py +16 -0
  47. sourcebot/llm/core/delta.py +12 -0
  48. sourcebot/llm/core/message.py +53 -0
  49. sourcebot/llm/core/message_converter.py +33 -0
  50. sourcebot/llm/core/response.py +30 -0
  51. sourcebot/llm/core/tool.py +7 -0
  52. sourcebot/llm/core/tool_converter.py +30 -0
  53. sourcebot/llm/core/tool_delta_aggregator.py +38 -0
  54. sourcebot/llm/llm_client_factory.py +13 -0
  55. sourcebot/llm/openai/__init__.py +2 -0
  56. sourcebot/llm/openai/adapter.py +27 -0
  57. sourcebot/llm/openai/converter.py +53 -0
  58. sourcebot/llm/openai/openai_llm_client.py +47 -0
  59. sourcebot/logging/__init__.py +3 -0
  60. sourcebot/logging/setup.py +33 -0
  61. sourcebot/memory/__init__.py +5 -0
  62. sourcebot/memory/file_store.py +23 -0
  63. sourcebot/memory/llm_consolidator.py +79 -0
  64. sourcebot/memory/service.py +116 -0
  65. sourcebot/memory/window_policy.py +36 -0
  66. sourcebot/prompt/__init__.py +4 -0
  67. sourcebot/prompt/deeomposer_prompt.py +420 -0
  68. sourcebot/prompt/identity_prompt.py +98 -0
  69. sourcebot/prompt/subagent_prompt.py +25 -0
  70. sourcebot/runtime/__init__.py +3 -0
  71. sourcebot/runtime/agent/__init__.py +3 -0
  72. sourcebot/runtime/agent/agent.py +130 -0
  73. sourcebot/runtime/agent/agent_factory.py +83 -0
  74. sourcebot/runtime/dag/planner/__init__.py +3 -0
  75. sourcebot/runtime/dag/planner/dag_planner.py +26 -0
  76. sourcebot/runtime/dag/planner/execution_scheduler.py +35 -0
  77. sourcebot/runtime/dag/planner/parallelism_optimizer.py +44 -0
  78. sourcebot/runtime/dag/planner/task_decomposer.py +37 -0
  79. sourcebot/runtime/dag/scheduler/__init__.py +3 -0
  80. sourcebot/runtime/dag/scheduler/dag_scheduler.py +319 -0
  81. sourcebot/runtime/dag/scheduler/retry_policy.py +27 -0
  82. sourcebot/runtime/dag/scheduler/run_store.py +58 -0
  83. sourcebot/runtime/dag/scheduler/state_store.py +40 -0
  84. sourcebot/runtime/dag/scheduler/task_graph.py +29 -0
  85. sourcebot/runtime/init_system.py +182 -0
  86. sourcebot/runtime/tool_executor.py +30 -0
  87. sourcebot/security/policy.py +23 -0
  88. sourcebot/session/__init__.py +4 -0
  89. sourcebot/session/jsonl_repository.py +142 -0
  90. sourcebot/session/repository.py +19 -0
  91. sourcebot/session/service.py +44 -0
  92. sourcebot/session/session.py +53 -0
  93. sourcebot/storage/__init__.py +3 -0
  94. sourcebot/storage/rules_loader.py +72 -0
  95. sourcebot/storage/skill_storage.py +51 -0
  96. sourcebot/tools/__init__.py +7 -0
  97. sourcebot/tools/base.py +182 -0
  98. sourcebot/tools/registry.py +81 -0
  99. sourcebot/tools/rule_detail.py +70 -0
  100. sourcebot/tools/rule_list.py +57 -0
  101. sourcebot/tools/shell.py +93 -0
  102. sourcebot/tools/skill_detail.py +61 -0
  103. sourcebot/tools/skill_list.py +68 -0
  104. sourcebot/utils/__init__.py +2 -0
  105. sourcebot/utils/output.py +79 -0
  106. sourcebot-0.1.0.dist-info/METADATA +318 -0
  107. sourcebot-0.1.0.dist-info/RECORD +110 -0
  108. sourcebot-0.1.0.dist-info/WHEEL +5 -0
  109. sourcebot-0.1.0.dist-info/entry_points.txt +2 -0
  110. sourcebot-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,70 @@
1
+ # sourcebot/tools/rule_detail.py
2
+ from sourcebot.tools.base import Tool
3
+ from typing import Any, Optional
4
+ from pathlib import Path
5
+
6
+
7
+ class RuleDetailTool(Tool):
8
+ """Tool for retrieving detailed information about a specific rule."""
9
+
10
+ def __init__(self, rules_loader):
11
+ """
12
+ Initialize the RuleDetailTool.
13
+
14
+ Args:
15
+ rules_loader: Loader instance for accessing rules
16
+ """
17
+ self.rules_loader = rules_loader
18
+
19
+ @property
20
+ def name(self) -> str:
21
+ return "rule_detail"
22
+
23
+ @property
24
+ def description(self) -> str:
25
+ return (
26
+ "Get detailed information about a specific rule. "
27
+ "Provide the rule name to retrieve its full content and requirements."
28
+ )
29
+
30
+ @property
31
+ def parameters(self) -> dict[str, Any]:
32
+ return {
33
+ "type": "object",
34
+ "properties": {
35
+ "name": {
36
+ "type": "string",
37
+ "description": "The name of the rule to get details for",
38
+ },
39
+ },
40
+ "required": ["name"],
41
+ }
42
+
43
+ async def execute(self, name: str, **kwargs: Any) -> str:
44
+ """
45
+ Execute the tool and return detailed information about a specific rule.
46
+
47
+ Args:
48
+ name: The name of the rule to retrieve
49
+
50
+ Returns:
51
+ A string containing the rule details, or an error message if not found
52
+ """
53
+ try:
54
+ # Validate input
55
+ if not name or not isinstance(name, str):
56
+ return "Error: Rule name must be a non-empty string"
57
+
58
+ # Read rule content
59
+ rule_content = self.rules_loader.read_rule(name)
60
+
61
+ if not rule_content:
62
+ return f"No content found for rule: {name}"
63
+
64
+ # Format the output
65
+ return f"=== Rule: {name} ===\n\n{rule_content}\n\n=== End of Rule ==="
66
+
67
+ except FileNotFoundError:
68
+ return f"Error: Rule '{name}' not found. Use the rules_list tool to see available rules."
69
+ except Exception as e:
70
+ return f"Error reading rule details for '{name}': {str(e)}"
@@ -0,0 +1,57 @@
1
+ # sourcebot/tools/rule_list.py
2
+ from sourcebot.tools.base import Tool
3
+ from typing import Any, Optional
4
+
5
+
6
+ class RuleListTool(Tool):
7
+ """Tool for listing available rules that need to be followed."""
8
+
9
+ def __init__(self, rules_loader):
10
+ """
11
+ Initialize the RuleListTool.
12
+
13
+ Args:
14
+ rules_loader: Loader instance for accessing rules
15
+ """
16
+ self.rules_loader = rules_loader
17
+
18
+ @property
19
+ def name(self) -> str:
20
+ return "rule_list"
21
+
22
+ @property
23
+ def description(self) -> str:
24
+ return (
25
+ "Returns the list of available rules. "
26
+ "Call this tool to see all rules that must be followed."
27
+ )
28
+
29
+ @property
30
+ def parameters(self) -> dict[str, Any]:
31
+ return {
32
+ "type": "object",
33
+ "properties": {},
34
+ "required": []
35
+ }
36
+
37
+ async def execute(self, **kwargs: Any) -> str:
38
+ """
39
+ Execute the tool and return the list of available rules.
40
+
41
+ Returns:
42
+ String containing the list of rules
43
+ """
44
+ try:
45
+ rule_list = self.rules_loader.list_rules_dirs()
46
+
47
+ if not rule_list:
48
+ return "No rules found."
49
+
50
+ lines = ["Available rules:"]
51
+ for i, rule in enumerate(rule_list, 1):
52
+ lines.append(f"{i}. {rule}")
53
+
54
+ return "\n".join(lines)
55
+
56
+ except Exception as e:
57
+ return f"Error getting rules list: {str(e)}"
@@ -0,0 +1,93 @@
1
+ # sourcebot/tools/shell.py
2
+ """Shell execution tool (executes commands in /workspace)"""
3
+
4
+ import os
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any, Optional
8
+
9
+ from sourcebot.tools.base import Tool
10
+ from sourcebot.docker_sandbox import DockerSandbox
11
+
12
+
13
+ class ShellTool(Tool):
14
+ """Execute shell commands in /workspace"""
15
+
16
+ _MAX_OUTPUT = 10_000
17
+
18
+ def __init__(
19
+ self,
20
+ sandbox: DockerSandbox,
21
+ timeout: int = 60,
22
+ working_dir: str | None = None,
23
+ deny_patterns: list[str] | None = None,
24
+ allow_patterns: list[str] | None = None,
25
+ ):
26
+ self.sandbox = sandbox
27
+ self.timeout = timeout
28
+ self.working_dir = working_dir
29
+ self.deny_patterns = deny_patterns or []
30
+ self.allow_patterns = allow_patterns or []
31
+
32
+ @property
33
+ def name(self) -> str:
34
+ return "shell"
35
+
36
+ @property
37
+ def description(self) -> str:
38
+ return "Execute shell commands (running in /workspace)"
39
+
40
+ @property
41
+ def parameters(self) -> dict[str, Any]:
42
+ return {
43
+ "type": "object",
44
+ "properties": {
45
+ "command": {
46
+ "type": "string",
47
+ "description": "Shell command to be executed",
48
+ },
49
+ "timeout": {
50
+ "type": "integer",
51
+ "description": "Command timeout (seconds)",
52
+ "minimum": 1,
53
+ "maximum": 60,
54
+ },
55
+ },
56
+ "required": ["command"],
57
+ }
58
+
59
+ async def execute(
60
+ self,
61
+ command: str,
62
+ timeout: Optional[int] = None,
63
+ **kwargs: Any,
64
+ ) -> str:
65
+ """Execute command (running in /workspace)"""
66
+
67
+
68
+ effective_timeout = timeout or self.timeout
69
+
70
+ try:
71
+ # Directly call Docker Sandbox
72
+ result = await self.sandbox.execute(
73
+ command,
74
+ timeout=effective_timeout,
75
+ )
76
+
77
+ if not result:
78
+ result = "(no output)"
79
+
80
+ # Truncate output length
81
+ if len(result) > self._MAX_OUTPUT:
82
+ half = self._MAX_OUTPUT // 2
83
+ result = (
84
+ result[:half]
85
+ + f"\n\n... ({len(result) - self._MAX_OUTPUT:,} chars truncated) ...\n\n"
86
+ + result[-half:]
87
+ )
88
+
89
+ return result
90
+
91
+ except Exception as e:
92
+ return f"Command execution failed: {str(e)}"
93
+
@@ -0,0 +1,61 @@
1
+ # sourcebot/tools/skill_detail.py
2
+ from sourcebot.tools.base import Tool
3
+ from typing import Any, Optional
4
+ from pathlib import Path
5
+ class SkillDetailTool(Tool):
6
+ """Return to skill details"""
7
+
8
+ def __init__(
9
+ self,
10
+ skill_storage,
11
+ host_workspace,
12
+ ):
13
+ self.skill_storage = skill_storage
14
+ self.host_workspace = host_workspace
15
+ @property
16
+ def name(self) -> str:
17
+ return "skill_detail"
18
+
19
+ @property
20
+ def description(self) -> str:
21
+ return (
22
+ "Get the details corresponding to the skill name"
23
+ )
24
+
25
+ @property
26
+ def parameters(self) -> dict[str, Any]:
27
+ return {
28
+ "type": "object",
29
+ "properties": {
30
+ "name": {
31
+ "type": "string",
32
+ "description": "The name of the skill to get details for",
33
+ },
34
+ },
35
+ "required": ["name"],
36
+ }
37
+
38
+
39
+ async def execute(
40
+ self,
41
+ name: str, # Default to skills directory
42
+ **kwargs: Any,
43
+ ) -> str:
44
+ """
45
+ Execute the tool and return the skill details.
46
+
47
+ Args:
48
+ name: Skill name
49
+ Returns:
50
+ A string containing the skill details
51
+ """
52
+ try:
53
+
54
+ self.skill_storage.inject_skill(name, self.host_workspace)
55
+ skill_content = self.skill_storage.read_skill(name)
56
+ guide_message = f"\n\nSkill data has been injected into /workspace/skills/{name} directory for your reference and use."
57
+
58
+ return skill_content + guide_message
59
+ except Exception as e:
60
+ return f"Error reading skill details: {str(e)}"
61
+
@@ -0,0 +1,68 @@
1
+ # sourcebot/tools/skill_list.py
2
+ from sourcebot.tools.base import Tool
3
+ from typing import Any, Optional
4
+
5
+ class SkillListTool(Tool):
6
+ """Return to the list of available skills"""
7
+
8
+ def __init__(
9
+ self,
10
+ skill_storage,
11
+ ):
12
+ self.skill_storage = skill_storage
13
+ @property
14
+ def name(self) -> str:
15
+ return "skill_list"
16
+
17
+ @property
18
+ def description(self) -> str:
19
+ return (
20
+ "Return to the skill list.Directly calling this tool will return to the skill catalog list."
21
+ )
22
+
23
+ @property
24
+ def parameters(self) -> dict[str, Any]:
25
+ return {
26
+ "type": "object",
27
+ "properties": {
28
+ "path": {
29
+ "type": "string",
30
+ "description": "The directory path to list",
31
+ "default": "/skills" # Set default path to skills directory
32
+ },
33
+ "recursive": {
34
+ "type": "boolean",
35
+ "description": "Recursively list all files (default false)",
36
+ },
37
+ "max_entries": {
38
+ "type": "integer",
39
+ "description": "Maximum entries to return (default 200)",
40
+ "minimum": 1,
41
+ },
42
+ },
43
+ # Make path optional since we have a default
44
+ "required": [],
45
+ }
46
+
47
+ async def execute(
48
+ self,
49
+ path: str = "/skills", # Default to skills directory
50
+ recursive: bool = False,
51
+ max_entries: int | None = None,
52
+ **kwargs: Any,
53
+ ) -> str:
54
+ """
55
+ Execute the tool and return directory listing.
56
+
57
+ Args:
58
+ path: Directory path to list (defaults to /skills)
59
+ recursive: Whether to list recursively
60
+ max_entries: Maximum number of entries to return
61
+
62
+ Returns:
63
+ String containing directory listing
64
+ """
65
+ try:
66
+ return self.skill_storage.list_skill_name(source = "all")
67
+ except Exception as e:
68
+ return f"Error geting skill list: {str(e)}"
@@ -0,0 +1,2 @@
1
+ from sourcebot.utils.output import extract_json, strip_think, parse_tool_args, ensure_string
2
+ __all__ = ["extract_json", "strip_think", "parse_tool_args", "ensure_string"]
@@ -0,0 +1,79 @@
1
+ # sourcebot/utils/output.py
2
+ import re
3
+ import json
4
+
5
+ def extract_json(text: str) -> dict:
6
+
7
+ match = re.search(r'```(?:json)?\s*(.*?)```', text, flags=re.DOTALL)
8
+ if match:
9
+ text = match.group(1)
10
+
11
+ text = text.strip()
12
+ return json.loads(text)
13
+
14
+ def strip_think(text: str | None) -> str | None:
15
+ """Remove <think>…</think> blocks that some models embed in content."""
16
+ if not text:
17
+ return None
18
+ return re.sub(r"<think>[\s\S]*?</think>", "", text).strip() or None
19
+
20
+
21
+
22
+ def parse_tool_args(args):
23
+ if isinstance(args, dict):
24
+ return args
25
+ if isinstance(args, str):
26
+ try:
27
+ return json.loads(args)
28
+ except Exception:
29
+ return {"input": args}
30
+ return {}
31
+
32
+ def ensure_string(value: any, pretty: bool = True) -> str:
33
+ """
34
+ Ensure the return value is a string.
35
+ Args:
36
+ value: Input value of any type
37
+
38
+ pretty: If True, the JSON output will be formatted (indented); otherwise, it will be in compact format.
39
+
40
+ Returns:
41
+ String representation
42
+ """
43
+ if value is None:
44
+ return ""
45
+
46
+ if isinstance(value, str):
47
+ return value
48
+
49
+ if isinstance(value, bytes):
50
+ try:
51
+ return value.decode('utf-8')
52
+ except UnicodeDecodeError:
53
+ return str(value)
54
+
55
+ if isinstance(value, (dict, list, tuple, set)):
56
+ try:
57
+ import json
58
+ from datetime import datetime, date
59
+
60
+ def json_serializer(obj):
61
+ if isinstance(obj, (datetime, date)):
62
+ return obj.isoformat()
63
+ if isinstance(obj, set):
64
+ return list(obj)
65
+ if hasattr(obj, '__dict__'):
66
+ return obj.__dict__
67
+ raise TypeError(f"Type {type(obj)} not serializable")
68
+
69
+ if pretty:
70
+ return json.dumps(value, ensure_ascii=False, indent=2, default=json_serializer)
71
+ else:
72
+ return json.dumps(value, ensure_ascii=False, default=json_serializer)
73
+ except Exception as e:
74
+ return f"<Failed to serialize: {e}>\n{str(value)}"
75
+
76
+ if hasattr(value, '__str__'):
77
+ return str(value)
78
+
79
+ return repr(value)