langchain-agentx-python 1.2.2__py3-none-any.whl → 1.2.5__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.
- langchain_agentx/__init__.py +64 -64
- langchain_agentx/command/prompt_shell.py +123 -123
- langchain_agentx/loop/config/loop_config.py +2 -0
- langchain_agentx/loop/config/thinking_config.py +105 -0
- langchain_agentx/loop/config/ui_display_context.py +58 -0
- langchain_agentx/loop/context/settings.py +109 -109
- langchain_agentx/loop/exit/__init__.py +2 -0
- langchain_agentx/loop/graph/factory.py +1 -0
- langchain_agentx/loop/model/model_node.py +45 -2
- langchain_agentx/loop/subagent/cache_safe_params.py +119 -0
- langchain_agentx/loop/subagent/context.py +16 -2
- langchain_agentx/loop/subagent/display_projector.py +36 -0
- langchain_agentx/loop/subagent/graph.py +4 -1
- langchain_agentx/loop/subagent/orchestrator.py +33 -1
- langchain_agentx/loop/subagent/progress.py +47 -2
- langchain_agentx/loop/subagent/reasoning_extractor.py +42 -0
- langchain_agentx/loop/subagent/runner.py +42 -12
- langchain_agentx/observability/events/langchain_agentx_event_adapter.py +1 -0
- langchain_agentx/observability/events/subagent_event_mapper.py +5 -0
- langchain_agentx/provider/compatible_chat_openai.py +4 -0
- langchain_agentx/provider/semantics/__init__.py +28 -0
- langchain_agentx/provider/semantics/contracts.py +7 -0
- langchain_agentx/provider/semantics/profile.py +77 -6
- langchain_agentx/provider/semantics/thinking_capability.py +244 -0
- langchain_agentx/tool_runtime/adapter.py +26 -0
- langchain_agentx/tool_runtime/models.py +9 -0
- langchain_agentx/tool_runtime/policy_decorator.py +70 -70
- langchain_agentx/tools/agent/tool.py +11 -4
- langchain_agentx/tools/bash/backend.py +17 -1
- langchain_agentx/tools/bash/boundary_check.py +236 -236
- langchain_agentx/tools/bash/tool.py +7 -0
- {langchain_agentx_python-1.2.2.dist-info → langchain_agentx_python-1.2.5.dist-info}/METADATA +594 -594
- {langchain_agentx_python-1.2.2.dist-info → langchain_agentx_python-1.2.5.dist-info}/RECORD +36 -30
- {langchain_agentx_python-1.2.2.dist-info → langchain_agentx_python-1.2.5.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-1.2.2.dist-info → langchain_agentx_python-1.2.5.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-1.2.2.dist-info → langchain_agentx_python-1.2.5.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
"""
|
|
2
|
-
LangChain-based agent utilities for CodeBaseX.
|
|
3
|
-
|
|
4
|
-
This package provides:
|
|
5
|
-
- LangChain-AgentX LangGraph agent factory (`loop`)
|
|
6
|
-
|
|
7
|
-
Typical usage:
|
|
8
|
-
|
|
9
|
-
```python
|
|
10
|
-
from langchain_agentx import create_loop_agent
|
|
11
|
-
```
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
__version__ = "1.2.
|
|
15
|
-
|
|
16
|
-
from .loop import ( # noqa: F401
|
|
17
|
-
create_loop_agent,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
from .loop.hook import ( # noqa: F401
|
|
21
|
-
ATTACHMENT_HOOK_BLOCKING_ERROR,
|
|
22
|
-
ATTACHMENT_HOOK_CANCELLED,
|
|
23
|
-
ATTACHMENT_HOOK_NON_BLOCKING_ERROR,
|
|
24
|
-
ATTACHMENT_HOOK_STOPPED_CONTINUATION,
|
|
25
|
-
ATTACHMENT_HOOK_SUCCESS,
|
|
26
|
-
)
|
|
27
|
-
from .observability import ( # noqa: F401
|
|
28
|
-
EventAdapterState,
|
|
29
|
-
LangchainAgentEvent,
|
|
30
|
-
LangchainAgentEventType,
|
|
31
|
-
LangGraphToLangchainAgentEventAdapter,
|
|
32
|
-
TraceCollector,
|
|
33
|
-
TraceSession,
|
|
34
|
-
TraceSpan,
|
|
35
|
-
)
|
|
36
|
-
from .provider import CompatibleChatOpenAI # noqa: F401
|
|
37
|
-
from .session import AgentSession, create_agent_session # noqa: F401
|
|
38
|
-
from .health import HealthCheck, HealthReport, PreflightResult, preflight, sdk_health # noqa: F401
|
|
39
|
-
from .workflow import BaseWorkflow # noqa: F401
|
|
40
|
-
|
|
41
|
-
__all__ = [
|
|
42
|
-
"ATTACHMENT_HOOK_BLOCKING_ERROR",
|
|
43
|
-
"ATTACHMENT_HOOK_CANCELLED",
|
|
44
|
-
"ATTACHMENT_HOOK_NON_BLOCKING_ERROR",
|
|
45
|
-
"ATTACHMENT_HOOK_STOPPED_CONTINUATION",
|
|
46
|
-
"ATTACHMENT_HOOK_SUCCESS",
|
|
47
|
-
"create_loop_agent",
|
|
48
|
-
"create_agent_session",
|
|
49
|
-
"AgentSession",
|
|
50
|
-
"BaseWorkflow",
|
|
51
|
-
"EventAdapterState",
|
|
52
|
-
"LangchainAgentEvent",
|
|
53
|
-
"LangchainAgentEventType",
|
|
54
|
-
"LangGraphToLangchainAgentEventAdapter",
|
|
55
|
-
"TraceCollector",
|
|
56
|
-
"TraceSession",
|
|
57
|
-
"TraceSpan",
|
|
58
|
-
"CompatibleChatOpenAI",
|
|
59
|
-
"HealthCheck",
|
|
60
|
-
"HealthReport",
|
|
61
|
-
"PreflightResult",
|
|
62
|
-
"preflight",
|
|
63
|
-
"sdk_health",
|
|
64
|
-
]
|
|
1
|
+
"""
|
|
2
|
+
LangChain-based agent utilities for CodeBaseX.
|
|
3
|
+
|
|
4
|
+
This package provides:
|
|
5
|
+
- LangChain-AgentX LangGraph agent factory (`loop`)
|
|
6
|
+
|
|
7
|
+
Typical usage:
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from langchain_agentx import create_loop_agent
|
|
11
|
+
```
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__version__ = "1.2.5"
|
|
15
|
+
|
|
16
|
+
from .loop import ( # noqa: F401
|
|
17
|
+
create_loop_agent,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .loop.hook import ( # noqa: F401
|
|
21
|
+
ATTACHMENT_HOOK_BLOCKING_ERROR,
|
|
22
|
+
ATTACHMENT_HOOK_CANCELLED,
|
|
23
|
+
ATTACHMENT_HOOK_NON_BLOCKING_ERROR,
|
|
24
|
+
ATTACHMENT_HOOK_STOPPED_CONTINUATION,
|
|
25
|
+
ATTACHMENT_HOOK_SUCCESS,
|
|
26
|
+
)
|
|
27
|
+
from .observability import ( # noqa: F401
|
|
28
|
+
EventAdapterState,
|
|
29
|
+
LangchainAgentEvent,
|
|
30
|
+
LangchainAgentEventType,
|
|
31
|
+
LangGraphToLangchainAgentEventAdapter,
|
|
32
|
+
TraceCollector,
|
|
33
|
+
TraceSession,
|
|
34
|
+
TraceSpan,
|
|
35
|
+
)
|
|
36
|
+
from .provider import CompatibleChatOpenAI # noqa: F401
|
|
37
|
+
from .session import AgentSession, create_agent_session # noqa: F401
|
|
38
|
+
from .health import HealthCheck, HealthReport, PreflightResult, preflight, sdk_health # noqa: F401
|
|
39
|
+
from .workflow import BaseWorkflow # noqa: F401
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"ATTACHMENT_HOOK_BLOCKING_ERROR",
|
|
43
|
+
"ATTACHMENT_HOOK_CANCELLED",
|
|
44
|
+
"ATTACHMENT_HOOK_NON_BLOCKING_ERROR",
|
|
45
|
+
"ATTACHMENT_HOOK_STOPPED_CONTINUATION",
|
|
46
|
+
"ATTACHMENT_HOOK_SUCCESS",
|
|
47
|
+
"create_loop_agent",
|
|
48
|
+
"create_agent_session",
|
|
49
|
+
"AgentSession",
|
|
50
|
+
"BaseWorkflow",
|
|
51
|
+
"EventAdapterState",
|
|
52
|
+
"LangchainAgentEvent",
|
|
53
|
+
"LangchainAgentEventType",
|
|
54
|
+
"LangGraphToLangchainAgentEventAdapter",
|
|
55
|
+
"TraceCollector",
|
|
56
|
+
"TraceSession",
|
|
57
|
+
"TraceSpan",
|
|
58
|
+
"CompatibleChatOpenAI",
|
|
59
|
+
"HealthCheck",
|
|
60
|
+
"HealthReport",
|
|
61
|
+
"PreflightResult",
|
|
62
|
+
"preflight",
|
|
63
|
+
"sdk_health",
|
|
64
|
+
]
|
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
"""
|
|
2
|
-
command/prompt_shell.py — Prompt 内嵌 shell 命令执行(CC executeShellCommandsInPrompt 对齐)。
|
|
3
|
-
|
|
4
|
-
职责:
|
|
5
|
-
解析并执行 prompt 中的 !`command` 占位,将输出内联替换;执行前走路由权限检查。
|
|
6
|
-
|
|
7
|
-
链路位置:
|
|
8
|
-
由 command/builtin/commit* 在展开 prompt 前调用。
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import re
|
|
14
|
-
from collections.abc import Callable, Sequence
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Protocol
|
|
17
|
-
|
|
18
|
-
from langchain_agentx.tool_runtime.models import ToolExecutionContext
|
|
19
|
-
from langchain_agentx.tool_runtime.permission_context import set_command_bash_allow_rules
|
|
20
|
-
from langchain_agentx.tool_runtime.session_store import AgentSessionStore
|
|
21
|
-
from langchain_agentx.utils.subprocess_text import TextSubprocessRunner, default_text_subprocess_runner
|
|
22
|
-
|
|
23
|
-
_INLINE_PATTERN = re.compile(r"!`([^`]+)`")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class PromptShellPermissionError(RuntimeError):
|
|
27
|
-
"""prompt 内嵌 shell 命令未通过 Bash 权限检查。"""
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class _ShellRunner(Protocol):
|
|
31
|
-
def run_text(
|
|
32
|
-
self,
|
|
33
|
-
argv: list[str],
|
|
34
|
-
*,
|
|
35
|
-
cwd: str | Path | None = None,
|
|
36
|
-
timeout_sec: float | None = None,
|
|
37
|
-
) -> tuple[int, str, str]: ...
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _default_runner_call(
|
|
41
|
-
runner: TextSubprocessRunner,
|
|
42
|
-
command: str,
|
|
43
|
-
*,
|
|
44
|
-
cwd: str | Path | None,
|
|
45
|
-
) -> tuple[int, str, str]:
|
|
46
|
-
completed = runner.run(
|
|
47
|
-
["bash", "-lc", command],
|
|
48
|
-
cwd=cwd,
|
|
49
|
-
timeout=30.0,
|
|
50
|
-
capture_output=True,
|
|
51
|
-
)
|
|
52
|
-
stdout = completed.stdout or ""
|
|
53
|
-
stderr = completed.stderr or ""
|
|
54
|
-
if completed.returncode != 0 and not stdout.strip():
|
|
55
|
-
return completed.returncode, "", stderr.strip() or f"exit {completed.returncode}"
|
|
56
|
-
return completed.returncode, stdout, stderr
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _assert_prompt_shell_permission(
|
|
60
|
-
command: str,
|
|
61
|
-
*,
|
|
62
|
-
bash_allow_rules: Sequence[str] | None,
|
|
63
|
-
cwd: str | Path | None,
|
|
64
|
-
) -> None:
|
|
65
|
-
from langchain_agentx.tools.bash.tool import BashRuntimeTool
|
|
66
|
-
|
|
67
|
-
store = AgentSessionStore("prompt-shell")
|
|
68
|
-
if bash_allow_rules:
|
|
69
|
-
set_command_bash_allow_rules(store, bash_allow_rules)
|
|
70
|
-
workspace_root = str(cwd) if cwd is not None else None
|
|
71
|
-
ctx = ToolExecutionContext(
|
|
72
|
-
tool_name="Bash",
|
|
73
|
-
tool_call_id="prompt-shell",
|
|
74
|
-
input_args={"command": command},
|
|
75
|
-
state={},
|
|
76
|
-
session_store=store,
|
|
77
|
-
workspace_root=workspace_root,
|
|
78
|
-
cwd=workspace_root,
|
|
79
|
-
)
|
|
80
|
-
l1 = BashRuntimeTool().run_l1_permission_check({"command": command}, ctx)
|
|
81
|
-
if l1.outcome == "deny":
|
|
82
|
-
message = (l1.decision.message if l1.decision else None) or "Permission denied"
|
|
83
|
-
raise PromptShellPermissionError(
|
|
84
|
-
f'Shell command permission check failed for "!`{command}`": {message}',
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
async def execute_shell_commands_in_prompt(
|
|
89
|
-
text: str,
|
|
90
|
-
*,
|
|
91
|
-
cwd: str | Path | None = None,
|
|
92
|
-
runner: TextSubprocessRunner | None = None,
|
|
93
|
-
shell_runner: Callable[[str], tuple[int, str, str]] | None = None,
|
|
94
|
-
bash_allow_rules: Sequence[str] | None = None,
|
|
95
|
-
check_permissions: bool = True,
|
|
96
|
-
) -> str:
|
|
97
|
-
"""将 text 中所有 !`command` 替换为命令 stdout(失败时内联 stderr/exit)。"""
|
|
98
|
-
if "!`" not in text:
|
|
99
|
-
return text
|
|
100
|
-
|
|
101
|
-
text_runner = runner or default_text_subprocess_runner()
|
|
102
|
-
result = text
|
|
103
|
-
|
|
104
|
-
for match in list(_INLINE_PATTERN.finditer(text)):
|
|
105
|
-
command = match.group(1).strip()
|
|
106
|
-
if not command:
|
|
107
|
-
continue
|
|
108
|
-
if check_permissions and shell_runner is None:
|
|
109
|
-
_assert_prompt_shell_permission(
|
|
110
|
-
command,
|
|
111
|
-
bash_allow_rules=bash_allow_rules,
|
|
112
|
-
cwd=cwd,
|
|
113
|
-
)
|
|
114
|
-
if shell_runner is not None:
|
|
115
|
-
_code, stdout, stderr = shell_runner(command)
|
|
116
|
-
else:
|
|
117
|
-
_code, stdout, stderr = _default_runner_call(text_runner, command, cwd=cwd)
|
|
118
|
-
replacement = stdout.strip() if stdout.strip() else stderr.strip()
|
|
119
|
-
if not replacement:
|
|
120
|
-
replacement = "(no output)"
|
|
121
|
-
result = result.replace(match.group(0), replacement, 1)
|
|
122
|
-
|
|
123
|
-
return result
|
|
1
|
+
"""
|
|
2
|
+
command/prompt_shell.py — Prompt 内嵌 shell 命令执行(CC executeShellCommandsInPrompt 对齐)。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
解析并执行 prompt 中的 !`command` 占位,将输出内联替换;执行前走路由权限检查。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
由 command/builtin/commit* 在展开 prompt 前调用。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from collections.abc import Callable, Sequence
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Protocol
|
|
17
|
+
|
|
18
|
+
from langchain_agentx.tool_runtime.models import ToolExecutionContext
|
|
19
|
+
from langchain_agentx.tool_runtime.permission_context import set_command_bash_allow_rules
|
|
20
|
+
from langchain_agentx.tool_runtime.session_store import AgentSessionStore
|
|
21
|
+
from langchain_agentx.utils.subprocess_text import TextSubprocessRunner, default_text_subprocess_runner
|
|
22
|
+
|
|
23
|
+
_INLINE_PATTERN = re.compile(r"!`([^`]+)`")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PromptShellPermissionError(RuntimeError):
|
|
27
|
+
"""prompt 内嵌 shell 命令未通过 Bash 权限检查。"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _ShellRunner(Protocol):
|
|
31
|
+
def run_text(
|
|
32
|
+
self,
|
|
33
|
+
argv: list[str],
|
|
34
|
+
*,
|
|
35
|
+
cwd: str | Path | None = None,
|
|
36
|
+
timeout_sec: float | None = None,
|
|
37
|
+
) -> tuple[int, str, str]: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _default_runner_call(
|
|
41
|
+
runner: TextSubprocessRunner,
|
|
42
|
+
command: str,
|
|
43
|
+
*,
|
|
44
|
+
cwd: str | Path | None,
|
|
45
|
+
) -> tuple[int, str, str]:
|
|
46
|
+
completed = runner.run(
|
|
47
|
+
["bash", "-lc", command],
|
|
48
|
+
cwd=cwd,
|
|
49
|
+
timeout=30.0,
|
|
50
|
+
capture_output=True,
|
|
51
|
+
)
|
|
52
|
+
stdout = completed.stdout or ""
|
|
53
|
+
stderr = completed.stderr or ""
|
|
54
|
+
if completed.returncode != 0 and not stdout.strip():
|
|
55
|
+
return completed.returncode, "", stderr.strip() or f"exit {completed.returncode}"
|
|
56
|
+
return completed.returncode, stdout, stderr
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _assert_prompt_shell_permission(
|
|
60
|
+
command: str,
|
|
61
|
+
*,
|
|
62
|
+
bash_allow_rules: Sequence[str] | None,
|
|
63
|
+
cwd: str | Path | None,
|
|
64
|
+
) -> None:
|
|
65
|
+
from langchain_agentx.tools.bash.tool import BashRuntimeTool
|
|
66
|
+
|
|
67
|
+
store = AgentSessionStore("prompt-shell")
|
|
68
|
+
if bash_allow_rules:
|
|
69
|
+
set_command_bash_allow_rules(store, bash_allow_rules)
|
|
70
|
+
workspace_root = str(cwd) if cwd is not None else None
|
|
71
|
+
ctx = ToolExecutionContext(
|
|
72
|
+
tool_name="Bash",
|
|
73
|
+
tool_call_id="prompt-shell",
|
|
74
|
+
input_args={"command": command},
|
|
75
|
+
state={},
|
|
76
|
+
session_store=store,
|
|
77
|
+
workspace_root=workspace_root,
|
|
78
|
+
cwd=workspace_root,
|
|
79
|
+
)
|
|
80
|
+
l1 = BashRuntimeTool().run_l1_permission_check({"command": command}, ctx)
|
|
81
|
+
if l1.outcome == "deny":
|
|
82
|
+
message = (l1.decision.message if l1.decision else None) or "Permission denied"
|
|
83
|
+
raise PromptShellPermissionError(
|
|
84
|
+
f'Shell command permission check failed for "!`{command}`": {message}',
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def execute_shell_commands_in_prompt(
|
|
89
|
+
text: str,
|
|
90
|
+
*,
|
|
91
|
+
cwd: str | Path | None = None,
|
|
92
|
+
runner: TextSubprocessRunner | None = None,
|
|
93
|
+
shell_runner: Callable[[str], tuple[int, str, str]] | None = None,
|
|
94
|
+
bash_allow_rules: Sequence[str] | None = None,
|
|
95
|
+
check_permissions: bool = True,
|
|
96
|
+
) -> str:
|
|
97
|
+
"""将 text 中所有 !`command` 替换为命令 stdout(失败时内联 stderr/exit)。"""
|
|
98
|
+
if "!`" not in text:
|
|
99
|
+
return text
|
|
100
|
+
|
|
101
|
+
text_runner = runner or default_text_subprocess_runner()
|
|
102
|
+
result = text
|
|
103
|
+
|
|
104
|
+
for match in list(_INLINE_PATTERN.finditer(text)):
|
|
105
|
+
command = match.group(1).strip()
|
|
106
|
+
if not command:
|
|
107
|
+
continue
|
|
108
|
+
if check_permissions and shell_runner is None:
|
|
109
|
+
_assert_prompt_shell_permission(
|
|
110
|
+
command,
|
|
111
|
+
bash_allow_rules=bash_allow_rules,
|
|
112
|
+
cwd=cwd,
|
|
113
|
+
)
|
|
114
|
+
if shell_runner is not None:
|
|
115
|
+
_code, stdout, stderr = shell_runner(command)
|
|
116
|
+
else:
|
|
117
|
+
_code, stdout, stderr = _default_runner_call(text_runner, command, cwd=cwd)
|
|
118
|
+
replacement = stdout.strip() if stdout.strip() else stderr.strip()
|
|
119
|
+
if not replacement:
|
|
120
|
+
replacement = "(no output)"
|
|
121
|
+
result = result.replace(match.group(0), replacement, 1)
|
|
122
|
+
|
|
123
|
+
return result
|
|
@@ -43,11 +43,13 @@ class PromptConfig:
|
|
|
43
43
|
- QueryParams.systemPrompt → system
|
|
44
44
|
- options.customSystemPrompt → override_system
|
|
45
45
|
- options.appendSystemPrompt → append_system
|
|
46
|
+
- options.thinkingConfig → thinking_generation_enabled
|
|
46
47
|
"""
|
|
47
48
|
system: str | SystemMessage | None = None
|
|
48
49
|
override_system: str | None = None
|
|
49
50
|
append_system: str | None = None
|
|
50
51
|
dynamic_sections: tuple[SystemPromptSection, ...] = ()
|
|
52
|
+
thinking_generation_enabled: bool = True # 是否启用 request-side thinking 生成(对齐 CC thinkingConfig)
|
|
51
53
|
|
|
52
54
|
|
|
53
55
|
@dataclass(frozen=True)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""loop/config/thinking_config.py — Thinking 生成配置
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
定义 ThinkingConfig 数据类,根据 subagent mode (plain/fork/coordinate)
|
|
5
|
+
决定是否启用 thinking 功能。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
SubagentOrchestrator → ThinkingConfig.from_subagent_mode() →
|
|
9
|
+
SubagentContext.thinking_generation_enabled → AgentLoopConfig
|
|
10
|
+
|
|
11
|
+
当前裁剪范围:
|
|
12
|
+
v1 基础实现:mode 驱动启用/禁用;预留父配置覆盖路径。
|
|
13
|
+
|
|
14
|
+
CC 对齐:
|
|
15
|
+
- runAgent.ts:682-683: plain mode 默认 { type: 'disabled' }
|
|
16
|
+
- fork mode 继承父配置或默认启用
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Any, Literal
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class ThinkingConfig:
|
|
27
|
+
"""Thinking 生成配置(对齐 CC thinkingConfig)。
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
enabled: 是否启用 thinking
|
|
31
|
+
type: Thinking 类型(预留,对齐 CC { type: 'enabled' | 'disabled' })
|
|
32
|
+
budget_tokens: Thinking token 预算(预留)
|
|
33
|
+
"""
|
|
34
|
+
enabled: bool = True
|
|
35
|
+
type: Literal["enabled", "disabled", "auto"] = "auto"
|
|
36
|
+
budget_tokens: int | None = None
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def disabled(cls) -> "ThinkingConfig":
|
|
40
|
+
"""返回禁用配置(对齐 CC { type: 'disabled' })。"""
|
|
41
|
+
return cls(enabled=False, type="disabled")
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def enabled_config(cls) -> "ThinkingConfig":
|
|
45
|
+
"""返回启用配置(对齐 CC { type: 'enabled' })。"""
|
|
46
|
+
return cls(enabled=True, type="enabled")
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_subagent_mode(
|
|
50
|
+
cls,
|
|
51
|
+
mode: str,
|
|
52
|
+
parent_thinking_config: Any | None = None,
|
|
53
|
+
*,
|
|
54
|
+
parent_thinking_enabled: bool | None = None,
|
|
55
|
+
) -> "ThinkingConfig":
|
|
56
|
+
"""根据 subagent mode 决定是否启用 thinking。
|
|
57
|
+
|
|
58
|
+
CC 对齐规则:
|
|
59
|
+
- plain mode: 禁用 thinking(除非父配置显式启用)
|
|
60
|
+
- fork mode: 继承父 thinking_enabled / thinkingConfig(对齐 runAgent useExactTools)
|
|
61
|
+
- coordinate mode: 同 fork,继承父配置
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
mode: Subagent 模式 ("plain" | "fork" | "coordinate")
|
|
65
|
+
parent_thinking_config: 父 ThinkingConfig 或 dict(优先于 mode 规则)
|
|
66
|
+
parent_thinking_enabled: 父 ToolExecutionContext.thinking_generation_enabled(fork/coordinate 继承)
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
ThinkingConfig 实例
|
|
70
|
+
"""
|
|
71
|
+
# 优先级 1:父显式 thinkingConfig
|
|
72
|
+
if parent_thinking_config is not None:
|
|
73
|
+
if isinstance(parent_thinking_config, dict):
|
|
74
|
+
parent_enabled = parent_thinking_config.get("enabled", True)
|
|
75
|
+
parent_type = parent_thinking_config.get("type", "auto")
|
|
76
|
+
return cls(
|
|
77
|
+
enabled=parent_enabled,
|
|
78
|
+
type=parent_type if parent_type in ("enabled", "disabled", "auto") else "auto",
|
|
79
|
+
)
|
|
80
|
+
elif isinstance(parent_thinking_config, ThinkingConfig):
|
|
81
|
+
return parent_thinking_config
|
|
82
|
+
|
|
83
|
+
# 优先级 2:mode 规则
|
|
84
|
+
if mode == "plain":
|
|
85
|
+
return cls.disabled()
|
|
86
|
+
|
|
87
|
+
# fork / coordinate:继承父 loop thinking_enabled(CC fork inherit)
|
|
88
|
+
if parent_thinking_enabled is not None:
|
|
89
|
+
enabled = bool(parent_thinking_enabled)
|
|
90
|
+
return cls(
|
|
91
|
+
enabled=enabled,
|
|
92
|
+
type="enabled" if enabled else "disabled",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return cls.enabled_config()
|
|
96
|
+
|
|
97
|
+
def to_dict(self) -> dict[str, Any]:
|
|
98
|
+
"""转换为字典(传递给 Provider)。"""
|
|
99
|
+
return {
|
|
100
|
+
"enabled": self.enabled,
|
|
101
|
+
"type": self.type,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__all__ = ["ThinkingConfig"]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""loop/config/ui_display_context.py — UI 展示意图(CLI → SDK seam)
|
|
2
|
+
|
|
3
|
+
职责:
|
|
4
|
+
定义宿主(CLI)注入的 transcript / verbose / thinking 展示意图;
|
|
5
|
+
供 ToolExecutionContext 与 SubagentOrchestrator 读取。
|
|
6
|
+
|
|
7
|
+
链路位置:
|
|
8
|
+
RunnableConfig.configurable["ui_display_context"] → LangChainAdapter → ToolExecutionContext
|
|
9
|
+
|
|
10
|
+
当前裁剪范围:
|
|
11
|
+
仅数据契约与解析;不做 TUI 渲染(langchain_agentx_cli 职责)。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import Any, Mapping
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class UiDisplayContext:
|
|
22
|
+
"""宿主 UI 展示意图(对齐 CC verbose + isTranscriptMode)。"""
|
|
23
|
+
|
|
24
|
+
is_transcript_mode: bool = False
|
|
25
|
+
verbose: bool = False
|
|
26
|
+
show_thinking: bool | None = None
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_mapping(cls, raw: Mapping[str, Any] | None) -> UiDisplayContext | None:
|
|
30
|
+
if not raw or not isinstance(raw, Mapping):
|
|
31
|
+
return None
|
|
32
|
+
is_transcript = bool(raw.get("is_transcript_mode", False))
|
|
33
|
+
verbose = bool(raw.get("verbose", False))
|
|
34
|
+
show_thinking_raw = raw.get("show_thinking")
|
|
35
|
+
show_thinking = None if show_thinking_raw is None else bool(show_thinking_raw)
|
|
36
|
+
return cls(
|
|
37
|
+
is_transcript_mode=is_transcript,
|
|
38
|
+
verbose=verbose,
|
|
39
|
+
show_thinking=show_thinking,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_configurable(cls, configurable: Mapping[str, Any] | None) -> UiDisplayContext | None:
|
|
44
|
+
if not configurable:
|
|
45
|
+
return None
|
|
46
|
+
raw = configurable.get("ui_display_context")
|
|
47
|
+
if isinstance(raw, UiDisplayContext):
|
|
48
|
+
return raw
|
|
49
|
+
if isinstance(raw, Mapping):
|
|
50
|
+
return cls.from_mapping(raw)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def should_show_thinking_full(self) -> bool:
|
|
54
|
+
"""对齐 CC Message.tsx:verbose || isTranscriptMode 时展示完整 thinking。"""
|
|
55
|
+
return self.verbose or self.is_transcript_mode
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
__all__ = ["UiDisplayContext"]
|