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.
- sourcebot/__init__.py +9 -0
- sourcebot/__main__.py +17 -0
- sourcebot/bus/__init__.py +4 -0
- sourcebot/bus/channel_adapter.py +21 -0
- sourcebot/bus/event_bus.py +15 -0
- sourcebot/bus/message_models.py +33 -0
- sourcebot/bus/outbound_dispatcher.py +15 -0
- sourcebot/bus/session_manager.py +20 -0
- sourcebot/cli/commands/core/__init__.py +3 -0
- sourcebot/cli/commands/core/command_line.py +26 -0
- sourcebot/cli/commands/init_commands/__init__.py +3 -0
- sourcebot/cli/commands/init_commands/init_global_config.py +30 -0
- sourcebot/cli/commands/init_commands/init_workspace_config.py +18 -0
- sourcebot/cli/commands/run_commands/__init__.py +3 -0
- sourcebot/cli/commands/run_commands/command_line_tool.py +345 -0
- sourcebot/cli/commands/run_commands/safe_runner.py +47 -0
- sourcebot/cli/main.py +28 -0
- sourcebot/config/__init__.py +15 -0
- sourcebot/config/base.py +13 -0
- sourcebot/config/config_manager.py +367 -0
- sourcebot/config/exceptions.py +4 -0
- sourcebot/config/global_config.py +55 -0
- sourcebot/config/provider_config.py +62 -0
- sourcebot/config/workspace_config.py +106 -0
- sourcebot/context/__init__.py +5 -0
- sourcebot/context/context_builder.py +78 -0
- sourcebot/context/identity.py +19 -0
- sourcebot/context/message_builder.py +154 -0
- sourcebot/context/skill/__init__.py +7 -0
- sourcebot/context/skill/skill.py +11 -0
- sourcebot/context/skill/skill_context.py +10 -0
- sourcebot/context/skill/skill_loader.py +57 -0
- sourcebot/context/skill/skill_metadata.py +27 -0
- sourcebot/context/skill/skill_requirements.py +25 -0
- sourcebot/context/skill/skill_summary.py +31 -0
- sourcebot/conversation/__init__.py +2 -0
- sourcebot/conversation/service.py +191 -0
- sourcebot/docker_sandbox/__init__.py +3 -0
- sourcebot/docker_sandbox/docker_sandbox.py +113 -0
- sourcebot/llm/__init__.py +3 -0
- sourcebot/llm/anthropic/__init__.py +2 -0
- sourcebot/llm/anthropic/adapter.py +30 -0
- sourcebot/llm/anthropic/anthropic_llm_client.py +38 -0
- sourcebot/llm/anthropic/converter.py +59 -0
- sourcebot/llm/core/adapter.py +16 -0
- sourcebot/llm/core/client.py +16 -0
- sourcebot/llm/core/delta.py +12 -0
- sourcebot/llm/core/message.py +53 -0
- sourcebot/llm/core/message_converter.py +33 -0
- sourcebot/llm/core/response.py +30 -0
- sourcebot/llm/core/tool.py +7 -0
- sourcebot/llm/core/tool_converter.py +30 -0
- sourcebot/llm/core/tool_delta_aggregator.py +38 -0
- sourcebot/llm/llm_client_factory.py +13 -0
- sourcebot/llm/openai/__init__.py +2 -0
- sourcebot/llm/openai/adapter.py +27 -0
- sourcebot/llm/openai/converter.py +53 -0
- sourcebot/llm/openai/openai_llm_client.py +47 -0
- sourcebot/logging/__init__.py +3 -0
- sourcebot/logging/setup.py +33 -0
- sourcebot/memory/__init__.py +5 -0
- sourcebot/memory/file_store.py +23 -0
- sourcebot/memory/llm_consolidator.py +79 -0
- sourcebot/memory/service.py +116 -0
- sourcebot/memory/window_policy.py +36 -0
- sourcebot/prompt/__init__.py +4 -0
- sourcebot/prompt/deeomposer_prompt.py +420 -0
- sourcebot/prompt/identity_prompt.py +98 -0
- sourcebot/prompt/subagent_prompt.py +25 -0
- sourcebot/runtime/__init__.py +3 -0
- sourcebot/runtime/agent/__init__.py +3 -0
- sourcebot/runtime/agent/agent.py +130 -0
- sourcebot/runtime/agent/agent_factory.py +83 -0
- sourcebot/runtime/dag/planner/__init__.py +3 -0
- sourcebot/runtime/dag/planner/dag_planner.py +26 -0
- sourcebot/runtime/dag/planner/execution_scheduler.py +35 -0
- sourcebot/runtime/dag/planner/parallelism_optimizer.py +44 -0
- sourcebot/runtime/dag/planner/task_decomposer.py +37 -0
- sourcebot/runtime/dag/scheduler/__init__.py +3 -0
- sourcebot/runtime/dag/scheduler/dag_scheduler.py +319 -0
- sourcebot/runtime/dag/scheduler/retry_policy.py +27 -0
- sourcebot/runtime/dag/scheduler/run_store.py +58 -0
- sourcebot/runtime/dag/scheduler/state_store.py +40 -0
- sourcebot/runtime/dag/scheduler/task_graph.py +29 -0
- sourcebot/runtime/init_system.py +182 -0
- sourcebot/runtime/tool_executor.py +30 -0
- sourcebot/security/policy.py +23 -0
- sourcebot/session/__init__.py +4 -0
- sourcebot/session/jsonl_repository.py +142 -0
- sourcebot/session/repository.py +19 -0
- sourcebot/session/service.py +44 -0
- sourcebot/session/session.py +53 -0
- sourcebot/storage/__init__.py +3 -0
- sourcebot/storage/rules_loader.py +72 -0
- sourcebot/storage/skill_storage.py +51 -0
- sourcebot/tools/__init__.py +7 -0
- sourcebot/tools/base.py +182 -0
- sourcebot/tools/registry.py +81 -0
- sourcebot/tools/rule_detail.py +70 -0
- sourcebot/tools/rule_list.py +57 -0
- sourcebot/tools/shell.py +93 -0
- sourcebot/tools/skill_detail.py +61 -0
- sourcebot/tools/skill_list.py +68 -0
- sourcebot/utils/__init__.py +2 -0
- sourcebot/utils/output.py +79 -0
- sourcebot-0.1.0.dist-info/METADATA +318 -0
- sourcebot-0.1.0.dist-info/RECORD +110 -0
- sourcebot-0.1.0.dist-info/WHEEL +5 -0
- sourcebot-0.1.0.dist-info/entry_points.txt +2 -0
- 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)}"
|
sourcebot/tools/shell.py
ADDED
|
@@ -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,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)
|