klaude-code 1.2.4__tar.gz → 1.2.5__tar.gz
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.
- {klaude_code-1.2.4 → klaude_code-1.2.5}/PKG-INFO +1 -1
- {klaude_code-1.2.4 → klaude_code-1.2.5}/pyproject.toml +1 -1
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/cli/runtime.py +5 -4
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/executor.py +100 -232
- klaude_code-1.2.5/src/klaude_code/core/manager/__init__.py +19 -0
- klaude_code-1.2.5/src/klaude_code/core/manager/agent_manager.py +127 -0
- klaude_code-1.2.5/src/klaude_code/core/manager/llm_clients.py +42 -0
- klaude_code-1.2.5/src/klaude_code/core/manager/llm_clients_builder.py +49 -0
- klaude_code-1.2.5/src/klaude_code/core/manager/sub_agent_manager.py +86 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/shell/bash_tool.py +4 -6
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/shell/command_safety.py +0 -267
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/tool_registry.py +2 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/anthropic/input.py +4 -1
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/openai_compatible/input.py +4 -3
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/openrouter/input.py +4 -3
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/responses/input.py +1 -1
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/session/export.py +16 -18
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/session/templates/export_session.html +14 -9
- {klaude_code-1.2.4 → klaude_code-1.2.5}/README.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/cli/main.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/cli/session_cmd.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/clear_cmd.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/command_abc.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/diff_cmd.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/export_cmd.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/help_cmd.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/model_cmd.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/prompt-dev-docs-update.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/prompt-dev-docs.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/prompt_command.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/refresh_cmd.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/registry.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/status_cmd.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/config/config.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/config/list_model.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/config/select_model.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/const/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/prompt.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/prompts/prompt-subagent-explore.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/prompts/prompt-subagent-oracle.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/prompts/prompt-subagent.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/reminders.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/task.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/multi_edit_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/multi_edit_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/memory/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/memory/memory_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/memory/memory_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/memory/skill_loader.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/memory/skill_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/memory/skill_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/tool_context.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/truncation.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/core/turn.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/anthropic/client.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/openrouter/reasoning_handler.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/registry.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/llm/usage.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/protocol/commands.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/protocol/events.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/protocol/model.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/protocol/op.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/protocol/op_handler.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/protocol/sub_agent.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/session/selector.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/session/session.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/trace/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/trace/log.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/core/stage_manager.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/debug/display.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/repl/completers.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/repl/display.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/repl/event_handler.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/modes/repl/renderer.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/assistant.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/common.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/developer.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/diffs.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/errors.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/metadata.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/thinking.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/tools.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/rich/live.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/rich/markdown.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/rich/quote.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/rich/searchable_text.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/rich/status.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/rich/theme.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/terminal/control.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/terminal/notifier.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/utils/__init__.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/utils/common.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/ui/utils/debouncer.py +0 -0
- {klaude_code-1.2.4 → klaude_code-1.2.5}/src/klaude_code/version.py +0 -0
|
@@ -10,8 +10,9 @@ from rich.text import Text
|
|
|
10
10
|
from klaude_code import ui
|
|
11
11
|
from klaude_code.command import has_interactive_command
|
|
12
12
|
from klaude_code.config import Config, load_config
|
|
13
|
-
from klaude_code.core.agent import DefaultModelProfileProvider, VanillaModelProfileProvider
|
|
14
|
-
from klaude_code.core.executor import Executor
|
|
13
|
+
from klaude_code.core.agent import Agent, DefaultModelProfileProvider, VanillaModelProfileProvider
|
|
14
|
+
from klaude_code.core.executor import Executor
|
|
15
|
+
from klaude_code.core.manager import build_llm_clients
|
|
15
16
|
from klaude_code.core.tool import SkillLoader, SkillTool
|
|
16
17
|
from klaude_code.protocol import events, op
|
|
17
18
|
from klaude_code.protocol.model import UserInputPayload
|
|
@@ -103,7 +104,7 @@ async def initialize_app_components(init_config: AppInitConfig) -> AppComponents
|
|
|
103
104
|
# Initialize LLM clients
|
|
104
105
|
try:
|
|
105
106
|
enabled_sub_agents = [p.name for p in iter_sub_agent_profiles()]
|
|
106
|
-
llm_clients =
|
|
107
|
+
llm_clients = build_llm_clients(
|
|
107
108
|
config,
|
|
108
109
|
model_override=init_config.model,
|
|
109
110
|
enabled_sub_agents=enabled_sub_agents,
|
|
@@ -239,7 +240,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
239
240
|
|
|
240
241
|
# Create status provider for bottom toolbar
|
|
241
242
|
def _status_provider() -> REPLStatusSnapshot:
|
|
242
|
-
agent = None
|
|
243
|
+
agent: Agent | None = None
|
|
243
244
|
if session_id and session_id in components.executor.context.active_agents:
|
|
244
245
|
agent = components.executor.context.active_agents[session_id]
|
|
245
246
|
|
|
@@ -9,88 +9,67 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
from dataclasses import dataclass
|
|
12
|
-
from dataclasses import field as dataclass_field
|
|
13
12
|
|
|
14
13
|
from klaude_code.command import InputAction, InputActionType, dispatch_command
|
|
15
|
-
from klaude_code.config import Config, load_config
|
|
16
14
|
from klaude_code.core.agent import Agent, DefaultModelProfileProvider, ModelProfileProvider
|
|
15
|
+
from klaude_code.core.manager import AgentManager, LLMClients, SubAgentManager
|
|
17
16
|
from klaude_code.core.tool import current_run_subtask_callback
|
|
18
|
-
from klaude_code.
|
|
19
|
-
from klaude_code.llm.registry import create_llm_client
|
|
20
|
-
from klaude_code.protocol import commands, events, model, op
|
|
17
|
+
from klaude_code.protocol import events, model, op
|
|
21
18
|
from klaude_code.protocol.op_handler import OperationHandler
|
|
22
|
-
from klaude_code.protocol.sub_agent import SubAgentResult
|
|
23
|
-
from klaude_code.protocol.tools import SubAgentType
|
|
24
|
-
from klaude_code.session.session import Session
|
|
19
|
+
from klaude_code.protocol.sub_agent import SubAgentResult
|
|
25
20
|
from klaude_code.trace import DebugType, log_debug
|
|
26
21
|
|
|
27
22
|
|
|
28
23
|
@dataclass
|
|
29
|
-
class
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
main: LLMClientABC
|
|
33
|
-
sub_clients: dict[SubAgentType, LLMClientABC] = dataclass_field(default_factory=lambda: {})
|
|
34
|
-
|
|
35
|
-
def get_client(self, sub_agent_type: SubAgentType | None = None) -> LLMClientABC:
|
|
36
|
-
"""Get client for given sub-agent type, or main client if None."""
|
|
37
|
-
if sub_agent_type is None:
|
|
38
|
-
return self.main
|
|
39
|
-
return self.sub_clients.get(sub_agent_type) or self.main
|
|
40
|
-
|
|
41
|
-
@classmethod
|
|
42
|
-
def from_config(
|
|
43
|
-
cls,
|
|
44
|
-
config: Config,
|
|
45
|
-
model_override: str | None = None,
|
|
46
|
-
enabled_sub_agents: list[SubAgentType] | None = None,
|
|
47
|
-
) -> LLMClients:
|
|
48
|
-
"""Create LLMClients from application config.
|
|
24
|
+
class ActiveTask:
|
|
25
|
+
"""Track an in-flight task and its owning session."""
|
|
49
26
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
model_override: Optional model name to override the main model
|
|
53
|
-
enabled_sub_agents: List of sub-agent types to initialize clients for
|
|
27
|
+
task: asyncio.Task[None]
|
|
28
|
+
session_id: str
|
|
54
29
|
|
|
55
|
-
Returns:
|
|
56
|
-
LLMClients instance
|
|
57
|
-
"""
|
|
58
|
-
# Resolve main agent LLM config
|
|
59
|
-
if model_override:
|
|
60
|
-
llm_config = config.get_model_config(model_override)
|
|
61
|
-
else:
|
|
62
|
-
llm_config = config.get_main_model_config()
|
|
63
30
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
llm_config.model_dump_json(exclude_none=True),
|
|
67
|
-
style="yellow",
|
|
68
|
-
debug_type=DebugType.LLM_CONFIG,
|
|
69
|
-
)
|
|
31
|
+
class TaskManager:
|
|
32
|
+
"""Manager that tracks active tasks keyed by submission id."""
|
|
70
33
|
|
|
71
|
-
|
|
72
|
-
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
self._tasks: dict[str, ActiveTask] = {}
|
|
73
36
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
model_name = config.subagent_models.get(sub_agent_type)
|
|
77
|
-
if not model_name:
|
|
78
|
-
continue
|
|
79
|
-
profile = get_sub_agent_profile(sub_agent_type)
|
|
80
|
-
if not profile.enabled_for_model(main_client.model_name):
|
|
81
|
-
continue
|
|
82
|
-
sub_llm_config = config.get_model_config(model_name)
|
|
83
|
-
sub_clients[sub_agent_type] = create_llm_client(sub_llm_config)
|
|
37
|
+
def register(self, submission_id: str, task: asyncio.Task[None], session_id: str) -> None:
|
|
38
|
+
"""Register a new active task for a submission id."""
|
|
84
39
|
|
|
85
|
-
|
|
40
|
+
self._tasks[submission_id] = ActiveTask(task=task, session_id=session_id)
|
|
86
41
|
|
|
42
|
+
def get(self, submission_id: str) -> ActiveTask | None:
|
|
43
|
+
"""Return the active task for a submission id if present."""
|
|
87
44
|
|
|
88
|
-
|
|
89
|
-
class ActiveTask:
|
|
90
|
-
"""Track an in-flight task and its owning session."""
|
|
45
|
+
return self._tasks.get(submission_id)
|
|
91
46
|
|
|
92
|
-
|
|
93
|
-
|
|
47
|
+
def remove(self, submission_id: str) -> None:
|
|
48
|
+
"""Remove the active task associated with a submission id if present."""
|
|
49
|
+
|
|
50
|
+
self._tasks.pop(submission_id, None)
|
|
51
|
+
|
|
52
|
+
def values(self) -> list[ActiveTask]:
|
|
53
|
+
"""Return a snapshot list of all active tasks."""
|
|
54
|
+
|
|
55
|
+
return list(self._tasks.values())
|
|
56
|
+
|
|
57
|
+
def cancel_tasks_for_sessions(self, session_ids: set[str] | None = None) -> list[tuple[str, asyncio.Task[None]]]:
|
|
58
|
+
"""Collect tasks that should be cancelled for given sessions."""
|
|
59
|
+
|
|
60
|
+
tasks_to_cancel: list[tuple[str, asyncio.Task[None]]] = []
|
|
61
|
+
for task_id, active in list(self._tasks.items()):
|
|
62
|
+
task = active.task
|
|
63
|
+
if task.done():
|
|
64
|
+
continue
|
|
65
|
+
if session_ids is None or active.session_id in session_ids:
|
|
66
|
+
tasks_to_cancel.append((task_id, task))
|
|
67
|
+
return tasks_to_cancel
|
|
68
|
+
|
|
69
|
+
def clear(self) -> None:
|
|
70
|
+
"""Remove all tracked tasks from the manager."""
|
|
71
|
+
|
|
72
|
+
self._tasks.clear()
|
|
94
73
|
|
|
95
74
|
|
|
96
75
|
class ExecutorContext:
|
|
@@ -110,56 +89,35 @@ class ExecutorContext:
|
|
|
110
89
|
model_profile_provider: ModelProfileProvider | None = None,
|
|
111
90
|
):
|
|
112
91
|
self.event_queue: asyncio.Queue[events.Event] = event_queue
|
|
113
|
-
self.llm_clients: LLMClients = llm_clients
|
|
114
|
-
self.model_profile_provider: ModelProfileProvider = model_profile_provider or DefaultModelProfileProvider()
|
|
115
92
|
|
|
116
|
-
|
|
117
|
-
self.
|
|
118
|
-
|
|
119
|
-
|
|
93
|
+
resolved_profile_provider = model_profile_provider or DefaultModelProfileProvider()
|
|
94
|
+
self.model_profile_provider: ModelProfileProvider = resolved_profile_provider
|
|
95
|
+
|
|
96
|
+
# Delegate responsibilities to helper components
|
|
97
|
+
self.agent_manager = AgentManager(event_queue, llm_clients, resolved_profile_provider)
|
|
98
|
+
self.task_manager = TaskManager()
|
|
99
|
+
self.subagent_manager = SubAgentManager(event_queue, llm_clients, resolved_profile_provider)
|
|
120
100
|
|
|
121
101
|
async def emit_event(self, event: events.Event) -> None:
|
|
122
102
|
"""Emit an event to the UI display system."""
|
|
123
103
|
await self.event_queue.put(event)
|
|
124
104
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
agent = self.active_agents.get(session_id)
|
|
129
|
-
if agent is not None:
|
|
130
|
-
return agent
|
|
105
|
+
@property
|
|
106
|
+
def active_agents(self) -> dict[str, Agent]:
|
|
107
|
+
"""Expose currently active agents keyed by session id.
|
|
131
108
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
session=session,
|
|
136
|
-
profile=profile,
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
async for evt in agent.replay_history():
|
|
140
|
-
await self.emit_event(evt)
|
|
141
|
-
|
|
142
|
-
await self.emit_event(
|
|
143
|
-
events.WelcomeEvent(
|
|
144
|
-
work_dir=str(session.work_dir),
|
|
145
|
-
llm_config=self.llm_clients.main.get_llm_config(),
|
|
146
|
-
)
|
|
147
|
-
)
|
|
109
|
+
This property preserves the previous public attribute used by the
|
|
110
|
+
CLI status provider while delegating storage to :class:`AgentManager`.
|
|
111
|
+
"""
|
|
148
112
|
|
|
149
|
-
self.
|
|
150
|
-
log_debug(
|
|
151
|
-
f"Initialized agent for session: {session_id}",
|
|
152
|
-
style="cyan",
|
|
153
|
-
debug_type=DebugType.EXECUTION,
|
|
154
|
-
)
|
|
155
|
-
return agent
|
|
113
|
+
return self.agent_manager.all_active_agents()
|
|
156
114
|
|
|
157
115
|
async def handle_init_agent(self, operation: op.InitAgentOperation) -> None:
|
|
158
116
|
"""Initialize an agent for a session and replay history to UI."""
|
|
159
117
|
if operation.session_id is None:
|
|
160
118
|
raise ValueError("session_id cannot be None")
|
|
161
119
|
|
|
162
|
-
await self.
|
|
120
|
+
await self.agent_manager.ensure_agent(operation.session_id)
|
|
163
121
|
|
|
164
122
|
async def handle_user_input(self, operation: op.UserInputOperation) -> None:
|
|
165
123
|
"""Handle a user input operation by running it through an agent."""
|
|
@@ -168,7 +126,7 @@ class ExecutorContext:
|
|
|
168
126
|
raise ValueError("session_id cannot be None")
|
|
169
127
|
|
|
170
128
|
session_id = operation.session_id
|
|
171
|
-
agent = await self.
|
|
129
|
+
agent = await self.agent_manager.ensure_agent(session_id)
|
|
172
130
|
user_input = operation.input
|
|
173
131
|
|
|
174
132
|
# emit user input event
|
|
@@ -204,69 +162,29 @@ class ExecutorContext:
|
|
|
204
162
|
if action.type == InputActionType.RUN_AGENT:
|
|
205
163
|
task_input = model.UserInputPayload(text=action.text, images=operation.input.images)
|
|
206
164
|
|
|
207
|
-
existing_active = self.
|
|
165
|
+
existing_active = self.task_manager.get(operation.id)
|
|
208
166
|
if existing_active is not None and not existing_active.task.done():
|
|
209
167
|
raise RuntimeError(f"Active task already registered for operation {operation.id}")
|
|
210
168
|
|
|
211
169
|
task: asyncio.Task[None] = asyncio.create_task(
|
|
212
170
|
self._run_agent_task(agent, task_input, operation.id, session_id)
|
|
213
171
|
)
|
|
214
|
-
self.
|
|
172
|
+
self.task_manager.register(operation.id, task, session_id)
|
|
215
173
|
return
|
|
216
174
|
|
|
217
175
|
if action.type == InputActionType.CHANGE_MODEL:
|
|
218
176
|
if not action.model_name:
|
|
219
177
|
raise ValueError("ChangeModel action requires model_name")
|
|
220
178
|
|
|
221
|
-
await self.
|
|
179
|
+
await self.agent_manager.apply_model_change(agent, action.model_name)
|
|
222
180
|
return
|
|
223
181
|
|
|
224
182
|
if action.type == InputActionType.CLEAR:
|
|
225
|
-
await self.
|
|
183
|
+
await self.agent_manager.apply_clear(agent)
|
|
226
184
|
return
|
|
227
185
|
|
|
228
186
|
raise ValueError(f"Unsupported input action type: {action.type}")
|
|
229
187
|
|
|
230
|
-
async def _apply_model_change(self, agent: Agent, model_name: str) -> None:
|
|
231
|
-
config = load_config()
|
|
232
|
-
if config is None:
|
|
233
|
-
raise ValueError("Configuration must be initialized before changing model")
|
|
234
|
-
|
|
235
|
-
llm_config = config.get_model_config(model_name)
|
|
236
|
-
llm_client = create_llm_client(llm_config)
|
|
237
|
-
agent.set_model_profile(self.model_profile_provider.build_profile(llm_client))
|
|
238
|
-
|
|
239
|
-
developer_item = model.DeveloperMessageItem(
|
|
240
|
-
content=f"switched to model: {model_name}",
|
|
241
|
-
command_output=model.CommandOutput(command_name=commands.CommandName.MODEL),
|
|
242
|
-
)
|
|
243
|
-
agent.session.append_history([developer_item])
|
|
244
|
-
|
|
245
|
-
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
246
|
-
await self.emit_event(events.WelcomeEvent(llm_config=llm_config, work_dir=str(agent.session.work_dir)))
|
|
247
|
-
|
|
248
|
-
async def _apply_clear(self, agent: Agent) -> None:
|
|
249
|
-
old_session_id = agent.session.id
|
|
250
|
-
|
|
251
|
-
# Create a new session instance to replace the current one
|
|
252
|
-
new_session = Session(work_dir=agent.session.work_dir)
|
|
253
|
-
new_session.model_name = agent.session.model_name
|
|
254
|
-
|
|
255
|
-
# Replace the agent's session with the new one
|
|
256
|
-
agent.session = new_session
|
|
257
|
-
agent.session.save()
|
|
258
|
-
|
|
259
|
-
# Update the active_agents mapping
|
|
260
|
-
self.active_agents.pop(old_session_id, None)
|
|
261
|
-
self.active_agents[new_session.id] = agent
|
|
262
|
-
|
|
263
|
-
developer_item = model.DeveloperMessageItem(
|
|
264
|
-
content="started new conversation",
|
|
265
|
-
command_output=model.CommandOutput(command_name=commands.CommandName.CLEAR),
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
269
|
-
|
|
270
188
|
async def handle_interrupt(self, operation: op.InterruptOperation) -> None:
|
|
271
189
|
"""Handle an interrupt by invoking agent.cancel() and cancelling tasks."""
|
|
272
190
|
|
|
@@ -274,11 +192,11 @@ class ExecutorContext:
|
|
|
274
192
|
if operation.target_session_id is not None:
|
|
275
193
|
session_ids: list[str] = [operation.target_session_id]
|
|
276
194
|
else:
|
|
277
|
-
session_ids =
|
|
195
|
+
session_ids = self.agent_manager.active_session_ids()
|
|
278
196
|
|
|
279
197
|
# Call cancel() on each affected agent to persist an interrupt marker
|
|
280
198
|
for sid in session_ids:
|
|
281
|
-
agent = self.
|
|
199
|
+
agent = self.agent_manager.get_active_agent(sid)
|
|
282
200
|
if agent is not None:
|
|
283
201
|
for evt in agent.cancel():
|
|
284
202
|
await self.emit_event(evt)
|
|
@@ -287,16 +205,12 @@ class ExecutorContext:
|
|
|
287
205
|
await self.emit_event(events.InterruptEvent(session_id=operation.target_session_id or "all"))
|
|
288
206
|
|
|
289
207
|
# Find tasks to cancel (filter by target sessions if provided)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
tasks_to_cancel.append((task_id, task))
|
|
297
|
-
else:
|
|
298
|
-
if active.session_id == operation.target_session_id:
|
|
299
|
-
tasks_to_cancel.append((task_id, task))
|
|
208
|
+
if operation.target_session_id is None:
|
|
209
|
+
session_filter: set[str] | None = None
|
|
210
|
+
else:
|
|
211
|
+
session_filter = {operation.target_session_id}
|
|
212
|
+
|
|
213
|
+
tasks_to_cancel = self.task_manager.cancel_tasks_for_sessions(session_filter)
|
|
300
214
|
|
|
301
215
|
scope = operation.target_session_id or "all"
|
|
302
216
|
log_debug(
|
|
@@ -309,7 +223,7 @@ class ExecutorContext:
|
|
|
309
223
|
for task_id, task in tasks_to_cancel:
|
|
310
224
|
task.cancel()
|
|
311
225
|
# Remove from active tasks immediately
|
|
312
|
-
self.
|
|
226
|
+
self.task_manager.remove(task_id)
|
|
313
227
|
|
|
314
228
|
async def _run_agent_task(
|
|
315
229
|
self, agent: Agent, user_input: model.UserInputPayload, task_id: str, session_id: str
|
|
@@ -329,7 +243,7 @@ class ExecutorContext:
|
|
|
329
243
|
|
|
330
244
|
# Inject subtask runner into tool context for nested Task tool usage
|
|
331
245
|
async def _runner(state: model.SubAgentState) -> SubAgentResult:
|
|
332
|
-
return await self.
|
|
246
|
+
return await self.subagent_manager.run_subagent(agent, state)
|
|
333
247
|
|
|
334
248
|
token = current_run_subtask_callback.set(_runner)
|
|
335
249
|
try:
|
|
@@ -367,69 +281,25 @@ class ExecutorContext:
|
|
|
367
281
|
|
|
368
282
|
finally:
|
|
369
283
|
# Clean up the task from active tasks
|
|
370
|
-
self.
|
|
284
|
+
self.task_manager.remove(task_id)
|
|
371
285
|
log_debug(
|
|
372
286
|
f"Cleaned up agent task {task_id}",
|
|
373
287
|
style="cyan",
|
|
374
288
|
debug_type=DebugType.EXECUTION,
|
|
375
289
|
)
|
|
376
290
|
|
|
377
|
-
|
|
378
|
-
"""
|
|
291
|
+
def get_active_task(self, submission_id: str) -> asyncio.Task[None] | None:
|
|
292
|
+
"""Return the asyncio.Task for a submission id if one is registered."""
|
|
379
293
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
# Create a child session under the same workdir
|
|
385
|
-
parent_session = parent_agent.session
|
|
386
|
-
child_session = Session(work_dir=parent_session.work_dir)
|
|
387
|
-
child_session.sub_agent_state = state
|
|
388
|
-
|
|
389
|
-
child_profile = self.model_profile_provider.build_profile(
|
|
390
|
-
self.llm_clients.get_client(state.sub_agent_type),
|
|
391
|
-
state.sub_agent_type,
|
|
392
|
-
)
|
|
393
|
-
child_agent = Agent(
|
|
394
|
-
session=child_session,
|
|
395
|
-
profile=child_profile,
|
|
396
|
-
)
|
|
294
|
+
active = self.task_manager.get(submission_id)
|
|
295
|
+
if active is None:
|
|
296
|
+
return None
|
|
297
|
+
return active.task
|
|
397
298
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
style="cyan",
|
|
401
|
-
debug_type=DebugType.EXECUTION,
|
|
402
|
-
)
|
|
299
|
+
def has_active_task(self, submission_id: str) -> bool:
|
|
300
|
+
"""Return True if a task is registered for the submission id."""
|
|
403
301
|
|
|
404
|
-
|
|
405
|
-
# Not emit the subtask's user input since task tool call is already rendered
|
|
406
|
-
result: str = ""
|
|
407
|
-
sub_agent_input = model.UserInputPayload(text=state.sub_agent_prompt, images=None)
|
|
408
|
-
async for event in child_agent.run_task(sub_agent_input):
|
|
409
|
-
# Capture TaskFinishEvent content for return
|
|
410
|
-
if isinstance(event, events.TaskFinishEvent):
|
|
411
|
-
result = event.task_result
|
|
412
|
-
await self.emit_event(event)
|
|
413
|
-
return SubAgentResult(task_result=result, session_id=child_session.id)
|
|
414
|
-
except asyncio.CancelledError:
|
|
415
|
-
# Propagate cancellation so tooling can treat it as user interrupt
|
|
416
|
-
log_debug(
|
|
417
|
-
f"Subagent task for {state.sub_agent_type} was cancelled",
|
|
418
|
-
style="yellow",
|
|
419
|
-
debug_type=DebugType.EXECUTION,
|
|
420
|
-
)
|
|
421
|
-
raise
|
|
422
|
-
except Exception as e:
|
|
423
|
-
log_debug(
|
|
424
|
-
f"Subagent task failed: [{e.__class__.__name__}] {str(e)}",
|
|
425
|
-
style="red",
|
|
426
|
-
debug_type=DebugType.EXECUTION,
|
|
427
|
-
)
|
|
428
|
-
return SubAgentResult(
|
|
429
|
-
task_result=f"Subagent task failed: [{e.__class__.__name__}] {str(e)}",
|
|
430
|
-
session_id="",
|
|
431
|
-
error=True,
|
|
432
|
-
)
|
|
302
|
+
return self.task_manager.get(submission_id) is not None
|
|
433
303
|
|
|
434
304
|
|
|
435
305
|
class Executor:
|
|
@@ -533,7 +403,7 @@ class Executor:
|
|
|
533
403
|
"""Stop the executor and clean up resources."""
|
|
534
404
|
# Cancel all active tasks and collect them for awaiting
|
|
535
405
|
tasks_to_await: list[asyncio.Task[None]] = []
|
|
536
|
-
for active in self.context.
|
|
406
|
+
for active in self.context.task_manager.values():
|
|
537
407
|
task = active.task
|
|
538
408
|
if not task.done():
|
|
539
409
|
task.cancel()
|
|
@@ -543,8 +413,8 @@ class Executor:
|
|
|
543
413
|
if tasks_to_await:
|
|
544
414
|
await asyncio.gather(*tasks_to_await, return_exceptions=True)
|
|
545
415
|
|
|
546
|
-
# Clear the
|
|
547
|
-
self.context.
|
|
416
|
+
# Clear the active task manager
|
|
417
|
+
self.context.task_manager.clear()
|
|
548
418
|
|
|
549
419
|
# Send EndOperation to wake up the start() loop
|
|
550
420
|
try:
|
|
@@ -577,25 +447,23 @@ class Executor:
|
|
|
577
447
|
# Execute to spawn the agent task in context
|
|
578
448
|
await submission.operation.execute(handler=self.context)
|
|
579
449
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
# Run in background so the submission loop can continue (e.g., to handle interrupts)
|
|
592
|
-
asyncio.create_task(_await_agent_and_complete())
|
|
593
|
-
|
|
594
|
-
# For operations without ActiveTask (e.g., InitAgentOperation), signal completion immediately
|
|
595
|
-
if submission.id not in self.context.active_tasks:
|
|
450
|
+
task = self.context.get_active_task(submission.id)
|
|
451
|
+
|
|
452
|
+
async def _await_agent_and_complete(captured_task: asyncio.Task[None]) -> None:
|
|
453
|
+
try:
|
|
454
|
+
await captured_task
|
|
455
|
+
finally:
|
|
456
|
+
event = self._completion_events.get(submission.id)
|
|
457
|
+
if event is not None:
|
|
458
|
+
event.set()
|
|
459
|
+
|
|
460
|
+
if task is None:
|
|
596
461
|
event = self._completion_events.get(submission.id)
|
|
597
462
|
if event is not None:
|
|
598
463
|
event.set()
|
|
464
|
+
else:
|
|
465
|
+
# Run in background so the submission loop can continue (e.g., to handle interrupts)
|
|
466
|
+
asyncio.create_task(_await_agent_and_complete(task))
|
|
599
467
|
|
|
600
468
|
except Exception as e:
|
|
601
469
|
log_debug(
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Core runtime and state management components.
|
|
2
|
+
|
|
3
|
+
Expose the manager layer via package imports to reduce module churn in
|
|
4
|
+
callers. This keeps long-lived runtime state helpers (agents, tasks,
|
|
5
|
+
LLM clients, sub-agents) distinct from per-session execution logic in
|
|
6
|
+
``klaude_code.core``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from klaude_code.core.manager.agent_manager import AgentManager
|
|
10
|
+
from klaude_code.core.manager.llm_clients import LLMClients
|
|
11
|
+
from klaude_code.core.manager.llm_clients_builder import build_llm_clients
|
|
12
|
+
from klaude_code.core.manager.sub_agent_manager import SubAgentManager
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AgentManager",
|
|
16
|
+
"LLMClients",
|
|
17
|
+
"SubAgentManager",
|
|
18
|
+
"build_llm_clients",
|
|
19
|
+
]
|