langchain-agentx-python 1.4.0__py3-none-any.whl → 1.4.2__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.
@@ -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.4.0"
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.4.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,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
@@ -1,109 +1,109 @@
1
- """
2
- 主循环上下文治理的 **策略与提示词**(单一配置对象,便于替换与单测)。
3
-
4
- 与 Claude Code 对齐:
5
- - 数值与 `OPENCODE_*` 环境变量覆盖语义、会话摘要模板与 CC `messagesForQuery` 后续各阶段读取的阈值/提示一致方向;
6
- - 各 `CompactionStage` 实现应通过 `get_active_compaction_settings()` 读取,避免散落魔法数。
7
-
8
- 设计要点:
9
- - `CompactionSettings` 为 `frozen` 值对象;可变绑定为模块级 `active_compaction_settings`,单测可 `set_active_compaction_settings(...)` 整体替换。
10
-
11
- 注意:
12
- - 不依赖 LangGraph;与 `tool_runtime` 单条截断分工见设计文档。
13
- - 紧凑化提示词函数(如 `get_compact_prompt`)在 **`loop/prompt/compact.py`**,不在本模块再导出。
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- from dataclasses import dataclass, field
19
- from typing import FrozenSet
20
-
21
- from ..prompt.compact import AutocompactLlmPromptKind
22
- from .query_source import QuerySourcePolicy
23
-
24
-
25
- @dataclass(frozen=True)
26
- class CompactionSettings:
27
- """CC 对齐上下文治理的默认策略(提示词 + 数值阈值)。"""
28
-
29
- num_chars_per_token: int = 4
30
- prune_protect_tokens: int = 40_000
31
- prune_minimum_tokens: int = 20_000
32
- prune_protected_tools: FrozenSet[str] = field(default_factory=lambda: frozenset({"Skill"}))
33
- default_max_context_tokens: int = 120_000
34
- default_compress_trigger_fraction: float = 0.8
35
- default_compress_keep_recent_messages: int = 8
36
- # --- autocompact LLM 提示词(对齐 `compact.ts` + `prompt.ts`)---
37
- # 摘要插在保留 tail 之前 → 默认 `partial_up_to`;全量折叠无尾窗时用 `full`;与 CC partial pivot「from」一致时用 `partial_from`。
38
- autocompact_llm_prompt_kind: AutocompactLlmPromptKind = "partial_up_to"
39
- # 追加到 CC 模板末尾的 Additional Instructions(对齐 hook merge 的 customInstructions)。
40
- compact_custom_instructions: str | None = None
41
- # 非 None 时跳过 builder,整段作为 compact 调用的指令(单测 / 完全自定义)。
42
- autocompact_llm_instructions_override: str | None = None
43
- # 兼容旧字段:未设置 override 时若此字段非空则优先于 builder(避免破坏只传 compaction_system_prompt 的调用方)。
44
- compaction_system_prompt: str | None = None
45
- # --- tool_result_budget(对齐 applyToolResultBudget:单条结果软上限)---
46
- tool_result_max_chars_per_message: int = 48_000
47
- tool_result_budget_suffix: str = field(
48
- default="\n\n[... truncated by tool_result_budget ...]"
49
- )
50
- # --- snip_compact(对齐 snip:尾部保留 + 历史大块截断)---
51
- snip_preserve_tail_messages: int = 10
52
- snip_max_tool_content_chars: int = 12_288
53
- snip_suffix: str = field(default="\n\n[... snipped by snip_compact ...]")
54
- # --- microcompact(轻量规范化;递归压缩时跳过)---
55
- microcompact_skip_query_sources: FrozenSet[str] = field(
56
- default_factory=QuerySourcePolicy.microcompact_skip_sources
57
- )
58
- # --- context_collapse(重复 tool 结果治理)---
59
- collapse_dedupe_consecutive_same_tool_call_id: bool = True
60
- # --- autocompact(阈值触发;产品与 CC 对齐默认走 LLM 摘要,无 model 时回退 prune)---
61
- autocompact_skip_query_sources: FrozenSet[str] = field(
62
- default_factory=QuerySourcePolicy.autocompact_skip_sources
63
- )
64
- autocompact_use_llm_when_available: bool = True
65
- autocompact_llm_excerpt_chars_per_message: int = 20_000
66
- # --- snip carry → autocompact 阈值放宽(对齐 CC snipTokensFreed 计入阈值)---
67
- # True:将本流水线已累计的 `snip_tokens_freed_carry` 加到触发阈值上,避免 snip 已释放后仍立刻 autocompact。
68
- autocompact_snip_carry_threshold_slack: bool = True
69
- # --- blocking / token 告警(对齐 CC calculateTokenWarningState 分档)---
70
- compaction_token_warn_fraction: float = 0.85
71
- compaction_block_compact_llm_fraction: float = 0.98
72
- # 达到或超过视为「主上下文已顶满」,建议由调用方拦主模型(本守卫 `should_block`)。
73
- compaction_block_main_model_fraction: float = 1.0
74
- # True:`model` 节点在 `invoke` 前若 `should_block` 命中则不再调主模型,写入合成 AIMessage 并结束。
75
- enable_main_model_context_token_block: bool = True
76
- # --- tool transcript guard(对齐 CC `ensureToolResultPairing`:发往 LLM 前修复 tool 配对)---
77
- # True:在绑定模型 `invoke/ainvoke` 前对 `messages` 做确定性修复(跨 assistant 去重 tool_call_id、
78
- # 去重连续 ToolMessage、补 synthetic 等),降低提供商 400 与会话卡死风险;不依赖提示词。
79
- enable_tool_transcript_repair_before_llm: bool = True
80
- # --- SystemMessage 单一来源兜底(对齐 CC normalizeMessagesForAPI)---
81
- # True:在 LLM 调用前剥离 state["messages"] 中所有 SystemMessage,避免 OpenAI 兼容 API 400 错误。
82
- enable_system_message_sanitizer: bool = True
83
- # True:剥离时输出内容预览(最多 3 条),便于排查违规来源。
84
- enable_system_message_content_preview: bool = True
85
- # --- 观测:写入 state.compaction_pipeline_meta(需 LoopAgentStateFields 含该键)---
86
- emit_compaction_pipeline_meta: bool = True
87
-
88
-
89
- active_compaction_settings: CompactionSettings = CompactionSettings()
90
-
91
-
92
- def get_active_compaction_settings() -> CompactionSettings:
93
- """返回当前生效的策略(默认即模块级 `active_compaction_settings`)。"""
94
- return active_compaction_settings
95
-
96
-
97
- def set_active_compaction_settings(settings: CompactionSettings) -> None:
98
- """替换全局策略(单测或进程级配置用)。"""
99
- global active_compaction_settings
100
- active_compaction_settings = settings
101
-
102
-
103
- __all__ = [
104
- "AutocompactLlmPromptKind",
105
- "CompactionSettings",
106
- "active_compaction_settings",
107
- "get_active_compaction_settings",
108
- "set_active_compaction_settings",
109
- ]
1
+ """
2
+ 主循环上下文治理的 **策略与提示词**(单一配置对象,便于替换与单测)。
3
+
4
+ 与 Claude Code 对齐:
5
+ - 数值与 `OPENCODE_*` 环境变量覆盖语义、会话摘要模板与 CC `messagesForQuery` 后续各阶段读取的阈值/提示一致方向;
6
+ - 各 `CompactionStage` 实现应通过 `get_active_compaction_settings()` 读取,避免散落魔法数。
7
+
8
+ 设计要点:
9
+ - `CompactionSettings` 为 `frozen` 值对象;可变绑定为模块级 `active_compaction_settings`,单测可 `set_active_compaction_settings(...)` 整体替换。
10
+
11
+ 注意:
12
+ - 不依赖 LangGraph;与 `tool_runtime` 单条截断分工见设计文档。
13
+ - 紧凑化提示词函数(如 `get_compact_prompt`)在 **`loop/prompt/compact.py`**,不在本模块再导出。
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass, field
19
+ from typing import FrozenSet
20
+
21
+ from ..prompt.compact import AutocompactLlmPromptKind
22
+ from .query_source import QuerySourcePolicy
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class CompactionSettings:
27
+ """CC 对齐上下文治理的默认策略(提示词 + 数值阈值)。"""
28
+
29
+ num_chars_per_token: int = 4
30
+ prune_protect_tokens: int = 40_000
31
+ prune_minimum_tokens: int = 20_000
32
+ prune_protected_tools: FrozenSet[str] = field(default_factory=lambda: frozenset({"Skill"}))
33
+ default_max_context_tokens: int = 120_000
34
+ default_compress_trigger_fraction: float = 0.8
35
+ default_compress_keep_recent_messages: int = 8
36
+ # --- autocompact LLM 提示词(对齐 `compact.ts` + `prompt.ts`)---
37
+ # 摘要插在保留 tail 之前 → 默认 `partial_up_to`;全量折叠无尾窗时用 `full`;与 CC partial pivot「from」一致时用 `partial_from`。
38
+ autocompact_llm_prompt_kind: AutocompactLlmPromptKind = "partial_up_to"
39
+ # 追加到 CC 模板末尾的 Additional Instructions(对齐 hook merge 的 customInstructions)。
40
+ compact_custom_instructions: str | None = None
41
+ # 非 None 时跳过 builder,整段作为 compact 调用的指令(单测 / 完全自定义)。
42
+ autocompact_llm_instructions_override: str | None = None
43
+ # 兼容旧字段:未设置 override 时若此字段非空则优先于 builder(避免破坏只传 compaction_system_prompt 的调用方)。
44
+ compaction_system_prompt: str | None = None
45
+ # --- tool_result_budget(对齐 applyToolResultBudget:单条结果软上限)---
46
+ tool_result_max_chars_per_message: int = 48_000
47
+ tool_result_budget_suffix: str = field(
48
+ default="\n\n[... truncated by tool_result_budget ...]"
49
+ )
50
+ # --- snip_compact(对齐 snip:尾部保留 + 历史大块截断)---
51
+ snip_preserve_tail_messages: int = 10
52
+ snip_max_tool_content_chars: int = 12_288
53
+ snip_suffix: str = field(default="\n\n[... snipped by snip_compact ...]")
54
+ # --- microcompact(轻量规范化;递归压缩时跳过)---
55
+ microcompact_skip_query_sources: FrozenSet[str] = field(
56
+ default_factory=QuerySourcePolicy.microcompact_skip_sources
57
+ )
58
+ # --- context_collapse(重复 tool 结果治理)---
59
+ collapse_dedupe_consecutive_same_tool_call_id: bool = True
60
+ # --- autocompact(阈值触发;产品与 CC 对齐默认走 LLM 摘要,无 model 时回退 prune)---
61
+ autocompact_skip_query_sources: FrozenSet[str] = field(
62
+ default_factory=QuerySourcePolicy.autocompact_skip_sources
63
+ )
64
+ autocompact_use_llm_when_available: bool = True
65
+ autocompact_llm_excerpt_chars_per_message: int = 20_000
66
+ # --- snip carry → autocompact 阈值放宽(对齐 CC snipTokensFreed 计入阈值)---
67
+ # True:将本流水线已累计的 `snip_tokens_freed_carry` 加到触发阈值上,避免 snip 已释放后仍立刻 autocompact。
68
+ autocompact_snip_carry_threshold_slack: bool = True
69
+ # --- blocking / token 告警(对齐 CC calculateTokenWarningState 分档)---
70
+ compaction_token_warn_fraction: float = 0.85
71
+ compaction_block_compact_llm_fraction: float = 0.98
72
+ # 达到或超过视为「主上下文已顶满」,建议由调用方拦主模型(本守卫 `should_block`)。
73
+ compaction_block_main_model_fraction: float = 1.0
74
+ # True:`model` 节点在 `invoke` 前若 `should_block` 命中则不再调主模型,写入合成 AIMessage 并结束。
75
+ enable_main_model_context_token_block: bool = True
76
+ # --- tool transcript guard(对齐 CC `ensureToolResultPairing`:发往 LLM 前修复 tool 配对)---
77
+ # True:在绑定模型 `invoke/ainvoke` 前对 `messages` 做确定性修复(跨 assistant 去重 tool_call_id、
78
+ # 去重连续 ToolMessage、补 synthetic 等),降低提供商 400 与会话卡死风险;不依赖提示词。
79
+ enable_tool_transcript_repair_before_llm: bool = True
80
+ # --- SystemMessage 单一来源兜底(对齐 CC normalizeMessagesForAPI)---
81
+ # True:在 LLM 调用前剥离 state["messages"] 中所有 SystemMessage,避免 OpenAI 兼容 API 400 错误。
82
+ enable_system_message_sanitizer: bool = True
83
+ # True:剥离时输出内容预览(最多 3 条),便于排查违规来源。
84
+ enable_system_message_content_preview: bool = True
85
+ # --- 观测:写入 state.compaction_pipeline_meta(需 LoopAgentStateFields 含该键)---
86
+ emit_compaction_pipeline_meta: bool = True
87
+
88
+
89
+ active_compaction_settings: CompactionSettings = CompactionSettings()
90
+
91
+
92
+ def get_active_compaction_settings() -> CompactionSettings:
93
+ """返回当前生效的策略(默认即模块级 `active_compaction_settings`)。"""
94
+ return active_compaction_settings
95
+
96
+
97
+ def set_active_compaction_settings(settings: CompactionSettings) -> None:
98
+ """替换全局策略(单测或进程级配置用)。"""
99
+ global active_compaction_settings
100
+ active_compaction_settings = settings
101
+
102
+
103
+ __all__ = [
104
+ "AutocompactLlmPromptKind",
105
+ "CompactionSettings",
106
+ "active_compaction_settings",
107
+ "get_active_compaction_settings",
108
+ "set_active_compaction_settings",
109
+ ]
@@ -69,6 +69,7 @@ class WorkflowAwareLangGraphEventAdapter:
69
69
  session_id = self._extract_session_id(raw_event)
70
70
  if session_id:
71
71
  self._workflow_adapter.state.session_id = session_id
72
+ self._agent_projector.state.session_id = session_id
72
73
 
73
74
  @staticmethod
74
75
  def _extract_session_id(raw_event: Dict[str, Any]) -> str | None: