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,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
4
|
+
|
|
5
|
+
from dolphin.core.common.constants import PIN_MARKER
|
|
6
|
+
|
|
7
|
+
# Default prefix for downgraded tool messages
|
|
8
|
+
DOWNGRADED_TOOL_PREFIX = "[Tool Output]: "
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def sanitize_openai_messages(
|
|
12
|
+
messages: List[Dict[str, Any]],
|
|
13
|
+
*,
|
|
14
|
+
downgrade_role: str = "user",
|
|
15
|
+
pinned_downgrade_role: str = "assistant",
|
|
16
|
+
downgraded_prefix: str = DOWNGRADED_TOOL_PREFIX,
|
|
17
|
+
) -> Tuple[List[Dict[str, Any]], int]:
|
|
18
|
+
"""Best-effort sanitizer for OpenAI-compatible tool message constraints.
|
|
19
|
+
|
|
20
|
+
OpenAI-compatible APIs require:
|
|
21
|
+
- role="tool" messages must be responses to a preceding assistant message with tool_calls
|
|
22
|
+
- tool messages must carry tool_call_id and it must match a previously declared tool_calls[].id
|
|
23
|
+
|
|
24
|
+
In real systems, context restore/compression/persistence may produce orphan tool messages, e.g.:
|
|
25
|
+
- tool appears before any assistant(tool_calls) (system -> tool -> user)
|
|
26
|
+
- tool_call_id missing or mismatched
|
|
27
|
+
|
|
28
|
+
This function downgrades orphan tool messages into normal text messages to avoid hard API failures.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
- sanitized_messages: list[dict] safe to send to OpenAI-compatible APIs
|
|
32
|
+
- downgraded_count: number of tool messages downgraded
|
|
33
|
+
"""
|
|
34
|
+
if not messages:
|
|
35
|
+
return messages, 0
|
|
36
|
+
|
|
37
|
+
declared_tool_call_ids: set[str] = set()
|
|
38
|
+
seen_any_tool_calls = False
|
|
39
|
+
downgraded = 0
|
|
40
|
+
sanitized: List[Dict[str, Any]] = []
|
|
41
|
+
|
|
42
|
+
for msg in messages:
|
|
43
|
+
role = msg.get("role")
|
|
44
|
+
|
|
45
|
+
if role == "assistant":
|
|
46
|
+
tool_calls = msg.get("tool_calls") or []
|
|
47
|
+
if isinstance(tool_calls, list) and tool_calls:
|
|
48
|
+
seen_any_tool_calls = True
|
|
49
|
+
for tc in tool_calls:
|
|
50
|
+
if isinstance(tc, dict) and tc.get("id"):
|
|
51
|
+
declared_tool_call_ids.add(tc["id"])
|
|
52
|
+
sanitized.append(msg)
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
if role != "tool":
|
|
56
|
+
sanitized.append(msg)
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
# NOTE: Some workflows inject pinned tool outputs (e.g. skill docs) even when
|
|
60
|
+
# the model didn't initiate a tool call. OpenAI-compatible providers (incl. DashScope)
|
|
61
|
+
# may reject *any* role="tool" message unless it is part of a valid tool_calls sequence.
|
|
62
|
+
# Pinned outputs are best represented as normal assistant text to avoid hard API failures.
|
|
63
|
+
content = msg.get("content") or ""
|
|
64
|
+
if not isinstance(content, str):
|
|
65
|
+
content = str(content)
|
|
66
|
+
|
|
67
|
+
tool_call_id = msg.get("tool_call_id")
|
|
68
|
+
is_orphan = False
|
|
69
|
+
if not seen_any_tool_calls:
|
|
70
|
+
is_orphan = True
|
|
71
|
+
elif not tool_call_id or not isinstance(tool_call_id, str):
|
|
72
|
+
is_orphan = True
|
|
73
|
+
elif tool_call_id not in declared_tool_call_ids:
|
|
74
|
+
is_orphan = True
|
|
75
|
+
|
|
76
|
+
# If a pinned tool message is a valid response to a preceding tool_calls entry,
|
|
77
|
+
# keep it as role="tool" so OpenAI-compatible providers won't reject the sequence.
|
|
78
|
+
if content.startswith(PIN_MARKER):
|
|
79
|
+
if is_orphan:
|
|
80
|
+
downgraded += 1
|
|
81
|
+
sanitized.append({"role": pinned_downgrade_role, "content": content})
|
|
82
|
+
else:
|
|
83
|
+
sanitized.append(msg)
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
if not is_orphan:
|
|
87
|
+
sanitized.append(msg)
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
downgraded += 1
|
|
91
|
+
downgraded_msg = {"role": downgrade_role, "content": f"{downgraded_prefix}{content}"}
|
|
92
|
+
sanitized.append(downgraded_msg)
|
|
93
|
+
|
|
94
|
+
return sanitized, downgraded
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def sanitize_and_log(
|
|
98
|
+
messages: List[Dict[str, Any]],
|
|
99
|
+
warning_fn: Optional[Callable[[str], None]] = None,
|
|
100
|
+
**sanitize_kwargs,
|
|
101
|
+
) -> List[Dict[str, Any]]:
|
|
102
|
+
"""Sanitize messages and optionally log warnings about downgraded tool messages.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
messages: List of message dictionaries to sanitize
|
|
106
|
+
warning_fn: Optional callable to log warnings (e.g., logger.warning or context.warn)
|
|
107
|
+
**sanitize_kwargs: Additional arguments to pass to sanitize_openai_messages
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Sanitized message list safe for OpenAI-compatible APIs
|
|
111
|
+
"""
|
|
112
|
+
sanitized_messages, downgraded_count = sanitize_openai_messages(
|
|
113
|
+
messages, **sanitize_kwargs
|
|
114
|
+
)
|
|
115
|
+
if downgraded_count and warning_fn:
|
|
116
|
+
warning_fn(
|
|
117
|
+
f"Detected {downgraded_count} orphan tool message(s); "
|
|
118
|
+
"downgraded for OpenAI compatibility"
|
|
119
|
+
)
|
|
120
|
+
return sanitized_messages
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Logging ๆจกๅ - ๆฅๅฟ็ณป็ป"""
|
|
3
|
+
|
|
4
|
+
from dolphin.core.logging.logger import (
|
|
5
|
+
get_logger,
|
|
6
|
+
console,
|
|
7
|
+
setup_logger,
|
|
8
|
+
set_log_level,
|
|
9
|
+
Colors,
|
|
10
|
+
colorize,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"get_logger",
|
|
15
|
+
"console",
|
|
16
|
+
"setup_logger",
|
|
17
|
+
"set_log_level",
|
|
18
|
+
"Colors",
|
|
19
|
+
"colorize",
|
|
20
|
+
]
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import contextvars
|
|
5
|
+
import re
|
|
6
|
+
from typing import Optional, List
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
|
|
9
|
+
"""_dolphin_logger is the global logger for Dolphin SDK.
|
|
10
|
+
|
|
11
|
+
Developers can customize the logger configuration using the setup_logger() method.
|
|
12
|
+
Internal modules of Dolphin SDK can obtain the global logger of Dolphin SDK using the get_logger() method.
|
|
13
|
+
By default, the global logger of Dolphin SDK is created using the setup_default_logger() method.
|
|
14
|
+
"""
|
|
15
|
+
_dolphin_logger = None
|
|
16
|
+
|
|
17
|
+
MaxLenLog = 4096
|
|
18
|
+
|
|
19
|
+
# Context variable storage, used to support independent log files during asyncio coroutine parallelization
|
|
20
|
+
# Fix: Changed from threading.local() to contextvars.ContextVar to support asynchronous coroutine isolation
|
|
21
|
+
_extra_log_file_var = contextvars.ContextVar("extra_log_file", default=None)
|
|
22
|
+
|
|
23
|
+
# Default logger name for Dolphin SDK
|
|
24
|
+
SDK_LOGGER_NAME = "dolphin_language"
|
|
25
|
+
|
|
26
|
+
# Pre-compiled ANSI escape sequences for removing console color codes
|
|
27
|
+
_ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ANSI color codes
|
|
31
|
+
class Colors:
|
|
32
|
+
# Basic Colors
|
|
33
|
+
RESET = "\033[0m"
|
|
34
|
+
BOLD = "\033[1m"
|
|
35
|
+
DIM = "\033[2m"
|
|
36
|
+
ITALIC = "\033[3m"
|
|
37
|
+
UNDERLINE = "\033[4m"
|
|
38
|
+
|
|
39
|
+
# Foreground color
|
|
40
|
+
BLACK = "\033[30m"
|
|
41
|
+
RED = "\033[31m"
|
|
42
|
+
GREEN = "\033[32m"
|
|
43
|
+
YELLOW = "\033[33m"
|
|
44
|
+
BLUE = "\033[34m"
|
|
45
|
+
MAGENTA = "\033[35m"
|
|
46
|
+
CYAN = "\033[36m"
|
|
47
|
+
WHITE = "\033[37m"
|
|
48
|
+
|
|
49
|
+
# Light color
|
|
50
|
+
BRIGHT_BLACK = "\033[90m"
|
|
51
|
+
BRIGHT_RED = "\033[91m"
|
|
52
|
+
BRIGHT_GREEN = "\033[92m"
|
|
53
|
+
BRIGHT_YELLOW = "\033[93m"
|
|
54
|
+
BRIGHT_BLUE = "\033[94m"
|
|
55
|
+
BRIGHT_MAGENTA = "\033[95m"
|
|
56
|
+
BRIGHT_CYAN = "\033[96m"
|
|
57
|
+
BRIGHT_WHITE = "\033[97m"
|
|
58
|
+
|
|
59
|
+
# Background color
|
|
60
|
+
BG_BLACK = "\033[40m"
|
|
61
|
+
BG_RED = "\033[41m"
|
|
62
|
+
BG_GREEN = "\033[42m"
|
|
63
|
+
BG_YELLOW = "\033[43m"
|
|
64
|
+
BG_BLUE = "\033[44m"
|
|
65
|
+
BG_MAGENTA = "\033[45m"
|
|
66
|
+
BG_CYAN = "\033[46m"
|
|
67
|
+
BG_WHITE = "\033[47m"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def colorize(text, color):
|
|
71
|
+
"""Add color to text"""
|
|
72
|
+
return f"{color}{text}{Colors.RESET}"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _strip_ansi(s: str) -> str:
|
|
76
|
+
"""Remove ANSI color control codes for writing plain text logs"""
|
|
77
|
+
return _ANSI_RE.sub("", s)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@contextmanager
|
|
81
|
+
def extra_log_context(file_path: Optional[str]):
|
|
82
|
+
"""Set additional log output context (based on contextvars, supports multi-coroutine isolation)
|
|
83
|
+
|
|
84
|
+
Usage example:
|
|
85
|
+
with extra_log_context('path/to/detail.log'):
|
|
86
|
+
... # Calls to _write_to_extra_log/console within this context will also write to detail.log
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
file_path: Path to the additional log file, None means disable additional logging
|
|
90
|
+
"""
|
|
91
|
+
# Ensure the parent directory exists (if a path is provided)
|
|
92
|
+
if file_path:
|
|
93
|
+
try:
|
|
94
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
95
|
+
except Exception:
|
|
96
|
+
# Directory creation failure does not affect the main process
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
token = _extra_log_file_var.set(file_path)
|
|
100
|
+
try:
|
|
101
|
+
yield
|
|
102
|
+
finally:
|
|
103
|
+
# Restore previous context value
|
|
104
|
+
try:
|
|
105
|
+
_extra_log_file_var.reset(token)
|
|
106
|
+
except Exception:
|
|
107
|
+
# If the context has expired, silently ignore it.
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def write_to_log_file(text: str, *, ensure_newline: bool = True):
|
|
112
|
+
"""Write a line to an additional log file (if additional log context exists).
|
|
113
|
+
|
|
114
|
+
- Automatically remove ANSI color codes and write plain text
|
|
115
|
+
- Open in append mode to avoid persistently holding file handles
|
|
116
|
+
- Thread-safe and coroutine-safe (write granularity is single write)
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
text: The text to write.
|
|
120
|
+
ensure_newline: Whether to ensure the text ends with a newline.
|
|
121
|
+
"""
|
|
122
|
+
file_path = _extra_log_file_var.get()
|
|
123
|
+
if not file_path:
|
|
124
|
+
return
|
|
125
|
+
try:
|
|
126
|
+
s = str(text)
|
|
127
|
+
if ensure_newline and not s.endswith("\n"):
|
|
128
|
+
s += "\n"
|
|
129
|
+
s = _strip_ansi(s)
|
|
130
|
+
with open(file_path, "a", encoding="utf-8") as f:
|
|
131
|
+
f.write(s)
|
|
132
|
+
except Exception:
|
|
133
|
+
# Any log writing errors should not affect the main process
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
# Alias for internal backward compatibility
|
|
137
|
+
_write_to_extra_log = write_to_log_file
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def setup_logger(
|
|
141
|
+
level: int = logging.INFO,
|
|
142
|
+
format: str = "%(asctime)s - %(levelname)s - %(message)s",
|
|
143
|
+
datefmt: str = "%Y-%m-%d %H:%M:%S",
|
|
144
|
+
handlers: Optional[List[logging.Handler]] = None,
|
|
145
|
+
propagate: bool = False,
|
|
146
|
+
force: bool = False,
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Set up the logger.
|
|
149
|
+
|
|
150
|
+
:param level: Log level, default is logging.INFO
|
|
151
|
+
:param format: Log format, default is logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
|
152
|
+
:param datefmt: Date format, default is "%Y-%m-%d %H:%M:%S"
|
|
153
|
+
:param handlers: List of log handlers, default is None
|
|
154
|
+
:param propagate: Whether to propagate logs, default is False
|
|
155
|
+
:param fore: Whether to clear existing handlers
|
|
156
|
+
"""
|
|
157
|
+
_dolphin_logger = logging.getLogger(SDK_LOGGER_NAME)
|
|
158
|
+
|
|
159
|
+
# Avoid duplicate configuration
|
|
160
|
+
if not force and _dolphin_logger.handlers:
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
# Set log level
|
|
164
|
+
_dolphin_logger.setLevel(level)
|
|
165
|
+
|
|
166
|
+
# Set whether to pass to the parent logger
|
|
167
|
+
_dolphin_logger.propagate = propagate
|
|
168
|
+
|
|
169
|
+
formatter = logging.Formatter(format, datefmt)
|
|
170
|
+
|
|
171
|
+
if handlers:
|
|
172
|
+
for handler in handlers:
|
|
173
|
+
handler.setFormatter(formatter)
|
|
174
|
+
_dolphin_logger.addHandler(handler)
|
|
175
|
+
else:
|
|
176
|
+
console_handler = logging.StreamHandler()
|
|
177
|
+
console_handler.setFormatter(formatter)
|
|
178
|
+
_dolphin_logger.addHandler(console_handler)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_logger(sub_name: str = "") -> logging.Logger:
|
|
182
|
+
"""Get the logger with the specified name.
|
|
183
|
+
|
|
184
|
+
:param name: The name of the logger
|
|
185
|
+
:return: The logger object
|
|
186
|
+
"""
|
|
187
|
+
logger_name = f"{SDK_LOGGER_NAME}.{sub_name}" if sub_name else SDK_LOGGER_NAME
|
|
188
|
+
return logging.getLogger(logger_name)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Set the default logger
|
|
192
|
+
def setup_default_logger(log_suffix: Optional[str] = None):
|
|
193
|
+
global _dolphin_logger
|
|
194
|
+
|
|
195
|
+
# If a logger already exists and no suffix is specified, return directly.
|
|
196
|
+
if _dolphin_logger is not None and log_suffix is None:
|
|
197
|
+
return _dolphin_logger
|
|
198
|
+
|
|
199
|
+
# If a suffix is specified, reconfiguration is required.
|
|
200
|
+
if log_suffix is not None and _dolphin_logger is not None:
|
|
201
|
+
# Clear existing processors
|
|
202
|
+
_dolphin_logger.handlers.clear()
|
|
203
|
+
|
|
204
|
+
_dolphin_logger = logging.getLogger(SDK_LOGGER_NAME)
|
|
205
|
+
_dolphin_logger.setLevel(logging.INFO)
|
|
206
|
+
_dolphin_logger.propagate = False
|
|
207
|
+
|
|
208
|
+
# Avoid duplicate processor addition
|
|
209
|
+
if not _dolphin_logger.handlers:
|
|
210
|
+
# Allow customizing log directory and file suffix via environment variables
|
|
211
|
+
# DOLPHIN_LOG_DIR: log directory (default "log")
|
|
212
|
+
# DOLPHIN_LOG_SUFFIX: File name suffix (default no suffix)
|
|
213
|
+
env_log_dir = os.getenv("DOLPHIN_LOG_DIR", "log")
|
|
214
|
+
env_suffix = os.getenv("DOLPHIN_LOG_SUFFIX")
|
|
215
|
+
# If log_suffix is not explicitly passed in, respect the environment variable
|
|
216
|
+
if log_suffix is None and env_suffix:
|
|
217
|
+
log_suffix = env_suffix
|
|
218
|
+
|
|
219
|
+
# Ensure the directory exists
|
|
220
|
+
try:
|
|
221
|
+
os.makedirs(env_log_dir, exist_ok=True)
|
|
222
|
+
except Exception:
|
|
223
|
+
# Failed to create directory, revert to default directory
|
|
224
|
+
env_log_dir = "log"
|
|
225
|
+
os.makedirs(env_log_dir, exist_ok=True)
|
|
226
|
+
|
|
227
|
+
# Build log filename
|
|
228
|
+
if log_suffix:
|
|
229
|
+
log_file = os.path.join(env_log_dir, f"dolphin_{log_suffix}.log")
|
|
230
|
+
else:
|
|
231
|
+
log_file = os.path.join(env_log_dir, "dolphin.log")
|
|
232
|
+
|
|
233
|
+
file_handler = logging.FileHandler(log_file)
|
|
234
|
+
file_handler.setFormatter(
|
|
235
|
+
logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
236
|
+
)
|
|
237
|
+
_dolphin_logger.addHandler(file_handler)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# Initialize the default logger
|
|
241
|
+
setup_default_logger()
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _format_json_compact(data, max_width=80):
|
|
245
|
+
"""Format JSON data, keeping it compact within a reasonable width"""
|
|
246
|
+
if isinstance(data, dict):
|
|
247
|
+
json_str = json.dumps(data, ensure_ascii=False, separators=(",", ":"))
|
|
248
|
+
if len(json_str) <= max_width:
|
|
249
|
+
return json_str
|
|
250
|
+
else:
|
|
251
|
+
# If too long, try formatting
|
|
252
|
+
formatted = json.dumps(data, ensure_ascii=False, indent=2)
|
|
253
|
+
lines = formatted.split("\n")
|
|
254
|
+
if len(lines) <= 5: # If the number of lines is not large, return the formatted version
|
|
255
|
+
return formatted
|
|
256
|
+
else: # Otherwise, return the truncated compact version
|
|
257
|
+
if len(json_str) > max_width:
|
|
258
|
+
return json_str[: max_width - 3] + "..."
|
|
259
|
+
return json_str
|
|
260
|
+
return str(data)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _stdout(value: str, info: bool = False, **kwargs):
|
|
264
|
+
if info or ("verbose" in kwargs and kwargs["verbose"]):
|
|
265
|
+
# Console Output
|
|
266
|
+
print(value, **{k: v for k, v in kwargs.items() if k in ["end", "flush"]})
|
|
267
|
+
# Synchronously write additional logs (if context is set)
|
|
268
|
+
end = kwargs.get("end", "\n")
|
|
269
|
+
if end:
|
|
270
|
+
_write_to_extra_log(
|
|
271
|
+
value + ("" if end is None else end), ensure_newline=False
|
|
272
|
+
)
|
|
273
|
+
else:
|
|
274
|
+
_write_to_extra_log(value, ensure_newline=False)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def console_skill_call(skill_name, params, max_length=200, verbose=None, skill=None):
|
|
278
|
+
"""Display skill/tool call with modern styling.
|
|
279
|
+
|
|
280
|
+
This function uses the new console_ui module for enhanced visual display
|
|
281
|
+
inspired by Codex CLI and Claude Code's terminal interfaces.
|
|
282
|
+
|
|
283
|
+
Supports custom UI rendering via Skillkit.has_custom_ui() protocol.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
skill_name: Name of the skill being called
|
|
287
|
+
params: Parameters passed to the skill
|
|
288
|
+
max_length: Maximum length for parameter display
|
|
289
|
+
verbose: Whether to display (None uses default, False suppresses)
|
|
290
|
+
skill: Optional SkillFunction object (for custom UI lookup)
|
|
291
|
+
"""
|
|
292
|
+
if verbose is False:
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
# Check if skill has custom UI rendering
|
|
296
|
+
if skill and hasattr(skill, 'owner_skillkit') and skill.owner_skillkit:
|
|
297
|
+
skillkit = skill.owner_skillkit
|
|
298
|
+
if hasattr(skillkit, 'has_custom_ui') and skillkit.has_custom_ui(skill_name):
|
|
299
|
+
# Use custom rendering (start phase)
|
|
300
|
+
if hasattr(skillkit, 'render_skill_start'):
|
|
301
|
+
skillkit.render_skill_start(skill_name, params, verbose=verbose if verbose is not None else True)
|
|
302
|
+
return # Skip default rendering
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
from dolphin.cli.ui.console import get_console_ui
|
|
306
|
+
ui = get_console_ui()
|
|
307
|
+
ui.skill_call_start(skill_name, params, max_length, verbose=True)
|
|
308
|
+
except ImportError:
|
|
309
|
+
# Fallback to legacy display if console_ui not available
|
|
310
|
+
header = f"{Colors.BOLD}{Colors.BRIGHT_CYAN}๐ง CALL SKILL{Colors.RESET} {Colors.BOLD}{Colors.BRIGHT_YELLOW}<{skill_name}>{Colors.RESET}"
|
|
311
|
+
separator = f"{Colors.DIM}{Colors.BRIGHT_BLACK}{'โ' * 60}{Colors.RESET}"
|
|
312
|
+
formatted_params = _format_json_compact(params, max_width=100)
|
|
313
|
+
if len(formatted_params) > max_length:
|
|
314
|
+
formatted_params = formatted_params[:max_length] + "..."
|
|
315
|
+
input_line = f"{Colors.BRIGHT_GREEN}>>> {Colors.RESET}{Colors.CYAN}{formatted_params}{Colors.RESET}"
|
|
316
|
+
print(separator)
|
|
317
|
+
print(header)
|
|
318
|
+
print(input_line)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def console_skill_response(skill_name, response, max_length=200, verbose=None, skill=None, params=None, duration_ms=0):
|
|
322
|
+
"""Display skill/tool response with modern styling.
|
|
323
|
+
|
|
324
|
+
This function uses the new console_ui module for enhanced visual display
|
|
325
|
+
inspired by Codex CLI and Claude Code's terminal interfaces.
|
|
326
|
+
|
|
327
|
+
Supports custom UI rendering via Skillkit.has_custom_ui() protocol.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
skill_name: Name of the skill that completed
|
|
331
|
+
response: Response from the skill
|
|
332
|
+
max_length: Maximum length for response display
|
|
333
|
+
verbose: Whether to display (None uses default, False suppresses)
|
|
334
|
+
skill: Optional SkillFunction object (for custom UI lookup)
|
|
335
|
+
params: Optional parameters that were passed to the skill
|
|
336
|
+
duration_ms: Execution duration in milliseconds
|
|
337
|
+
"""
|
|
338
|
+
from dolphin.lib.skillkits.cognitive_skillkit import CognitiveSkillkit
|
|
339
|
+
|
|
340
|
+
if CognitiveSkillkit.is_cognitive_skill(skill_name):
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
if verbose is False:
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
# Check if skill has custom UI rendering
|
|
347
|
+
if skill and hasattr(skill, 'owner_skillkit') and skill.owner_skillkit:
|
|
348
|
+
skillkit = skill.owner_skillkit
|
|
349
|
+
if hasattr(skillkit, 'has_custom_ui') and skillkit.has_custom_ui(skill_name):
|
|
350
|
+
# Use custom rendering (end phase)
|
|
351
|
+
if hasattr(skillkit, 'render_skill_end'):
|
|
352
|
+
skillkit.render_skill_end(
|
|
353
|
+
skill_name,
|
|
354
|
+
params or {},
|
|
355
|
+
response,
|
|
356
|
+
success=True,
|
|
357
|
+
duration_ms=duration_ms,
|
|
358
|
+
verbose=verbose if verbose is not None else True
|
|
359
|
+
)
|
|
360
|
+
return # Skip default rendering
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
from dolphin.cli.ui.console import get_console_ui
|
|
364
|
+
ui = get_console_ui()
|
|
365
|
+
ui.skill_call_end(skill_name, response, max_length, success=True, verbose=True)
|
|
366
|
+
except ImportError:
|
|
367
|
+
# Fallback to legacy display if console_ui not available
|
|
368
|
+
if isinstance(response, dict):
|
|
369
|
+
display_response = _format_json_compact(response, max_width=100)
|
|
370
|
+
elif not isinstance(response, str):
|
|
371
|
+
display_response = str(response)
|
|
372
|
+
else:
|
|
373
|
+
display_response = response
|
|
374
|
+
|
|
375
|
+
if len(display_response) > max_length:
|
|
376
|
+
display_response = display_response[:max_length] + "..."
|
|
377
|
+
|
|
378
|
+
output_line = f"{Colors.BRIGHT_BLUE}<<< {Colors.RESET}{Colors.BRIGHT_WHITE}{display_response}{Colors.RESET}"
|
|
379
|
+
separator = f"{Colors.DIM}{Colors.BRIGHT_BLACK}{'โ' * 60}{Colors.RESET}"
|
|
380
|
+
print(f"\n{output_line}")
|
|
381
|
+
print(f"{separator}\n")
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def console_agent_skill_exit(skill_name: str, message: str = None, verbose=None):
|
|
385
|
+
"""Display a delimiter indicating an agent-as-skill has exited.
|
|
386
|
+
|
|
387
|
+
This keeps terminal formatting concerns in the UI layer.
|
|
388
|
+
"""
|
|
389
|
+
if verbose is False:
|
|
390
|
+
return
|
|
391
|
+
|
|
392
|
+
try:
|
|
393
|
+
from dolphin.cli.ui.console import get_console_ui
|
|
394
|
+
|
|
395
|
+
ui = get_console_ui()
|
|
396
|
+
ui.agent_skill_exit(skill_name, message=message, verbose=True)
|
|
397
|
+
except ImportError:
|
|
398
|
+
# Fallback: plain separator
|
|
399
|
+
label = message or f" AGENT EXITED: {skill_name}"
|
|
400
|
+
print("\n" + ("โ" * 10) + label + ("โ" * 10) + "\n")
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def console_agent_skill_enter(skill_name: str, message: str = None, verbose=None):
|
|
404
|
+
"""Display a delimiter indicating an agent-as-skill has started.
|
|
405
|
+
|
|
406
|
+
This keeps terminal formatting concerns in the UI layer.
|
|
407
|
+
"""
|
|
408
|
+
if verbose is False:
|
|
409
|
+
return
|
|
410
|
+
|
|
411
|
+
try:
|
|
412
|
+
from dolphin.cli.ui.console import get_console_ui
|
|
413
|
+
ui = get_console_ui()
|
|
414
|
+
ui.agent_skill_enter(skill_name, message=message, verbose=True)
|
|
415
|
+
except ImportError:
|
|
416
|
+
label = message or f"๐ AGENT ACTIVATE: {skill_name}"
|
|
417
|
+
print(f"\n===== {label} =====\n")
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def console_block_start(
|
|
421
|
+
block_name: str, output_var: str, content: Optional[str] = None, verbose=None
|
|
422
|
+
):
|
|
423
|
+
"""Display code block start with modern styling.
|
|
424
|
+
|
|
425
|
+
This function uses the new console_ui module for enhanced visual display
|
|
426
|
+
inspired by Codex CLI and Claude Code's terminal interfaces.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
block_name: Type of block (explore, prompt, judge, assign, tool)
|
|
430
|
+
output_var: Variable name to store the output
|
|
431
|
+
content: Optional content preview
|
|
432
|
+
verbose: Whether to display (None uses default, False suppresses)
|
|
433
|
+
"""
|
|
434
|
+
if verbose is False:
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
try:
|
|
438
|
+
from dolphin.cli.ui.console import get_console_ui
|
|
439
|
+
ui = get_console_ui()
|
|
440
|
+
ui.block_start(block_name, output_var, content, verbose=True)
|
|
441
|
+
except ImportError:
|
|
442
|
+
# Fallback to legacy display if console_ui not available
|
|
443
|
+
block_icons = {
|
|
444
|
+
"explore": "๐",
|
|
445
|
+
"prompt": "๐ฌ",
|
|
446
|
+
"judge": "โ๏ธ",
|
|
447
|
+
"assign": "๐",
|
|
448
|
+
"tool": "๐ง",
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
block_colors = {
|
|
452
|
+
"explore": Colors.BRIGHT_MAGENTA,
|
|
453
|
+
"prompt": Colors.BRIGHT_GREEN,
|
|
454
|
+
"judge": Colors.BRIGHT_YELLOW,
|
|
455
|
+
"assign": Colors.BRIGHT_BLUE,
|
|
456
|
+
"tool": Colors.BRIGHT_CYAN,
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
icon = block_icons.get(block_name, "๐ฆ")
|
|
460
|
+
color = block_colors.get(block_name, Colors.BRIGHT_WHITE)
|
|
461
|
+
|
|
462
|
+
header = f"{color}{Colors.BOLD}{icon} {block_name.upper()}{Colors.RESET}"
|
|
463
|
+
var_name = f"{Colors.BRIGHT_CYAN}{output_var}{Colors.RESET}"
|
|
464
|
+
arrow = f"{Colors.DIM}โ{Colors.RESET}"
|
|
465
|
+
|
|
466
|
+
if content:
|
|
467
|
+
content = str(content)
|
|
468
|
+
if content.strip():
|
|
469
|
+
content_preview = (
|
|
470
|
+
content.strip()[:50] + "..."
|
|
471
|
+
if len(content.strip()) > 50
|
|
472
|
+
else content.strip()
|
|
473
|
+
)
|
|
474
|
+
content_part = f" {Colors.DIM}{content_preview}{Colors.RESET}"
|
|
475
|
+
else:
|
|
476
|
+
content_part = ""
|
|
477
|
+
else:
|
|
478
|
+
content_part = ""
|
|
479
|
+
|
|
480
|
+
output_line = f"{header} {var_name} {arrow}{content_part}"
|
|
481
|
+
print(output_line)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def set_log_level(level: int = logging.INFO, force: bool = False):
|
|
485
|
+
"""Dynamically set the log level of the global logger
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
level: Log level, such as logging.DEBUG, logging.INFO, logging.WARNING, etc.
|
|
489
|
+
force: Whether to forcibly set, even if the logger already has handlers
|
|
490
|
+
"""
|
|
491
|
+
global _dolphin_logger
|
|
492
|
+
|
|
493
|
+
if _dolphin_logger is None:
|
|
494
|
+
# If the logger has not been initialized yet, initialize it first.
|
|
495
|
+
setup_default_logger()
|
|
496
|
+
_dolphin_logger.setLevel(level)
|
|
497
|
+
|
|
498
|
+
# If forcibly set, clear existing handlers and reset them.
|
|
499
|
+
if force:
|
|
500
|
+
_dolphin_logger.handlers.clear()
|
|
501
|
+
setup_default_logger()
|
|
502
|
+
_dolphin_logger.setLevel(level)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def console(info, verbose=None, **kwargs):
|
|
506
|
+
"""Console output function, supports verbose parameter control
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
info: Information to be output
|
|
510
|
+
verbose: Whether to display detailed information (if None, determine based on the logic in _stdout)
|
|
511
|
+
**kwargs: Other parameters, such as end, flush, etc.
|
|
512
|
+
"""
|
|
513
|
+
if verbose is not None:
|
|
514
|
+
# If the verbose parameter is explicitly specified, use that value.
|
|
515
|
+
if "end" in kwargs:
|
|
516
|
+
_stdout(str(info), info=verbose, flush=True, end=kwargs["end"])
|
|
517
|
+
else:
|
|
518
|
+
_stdout(str(info), info=verbose, flush=True)
|
|
519
|
+
else:
|
|
520
|
+
# Keep the original logic and get verbose from kwargs
|
|
521
|
+
if "end" in kwargs:
|
|
522
|
+
_stdout(str(info), info=True, flush=True, end=kwargs["end"])
|
|
523
|
+
else:
|
|
524
|
+
_stdout(str(info), info=True, flush=True)
|
|
525
|
+
|
|
526
|
+
|