klaude-code 2.2.0__tar.gz → 2.3.0__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-2.2.0 → klaude_code-2.3.0}/PKG-INFO +1 -1
- {klaude_code-2.2.0 → klaude_code-2.3.0}/pyproject.toml +11 -1
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/app/runtime.py +2 -15
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/cli/list_model.py +27 -10
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/cli/main.py +25 -9
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/config/assets/builtin_config.yaml +25 -16
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/config/config.py +144 -7
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/config/select_model.py +38 -13
- klaude_code-2.3.0/src/klaude_code/config/sub_agent_model_helper.py +217 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/const.py +1 -1
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/agent_profile.py +43 -5
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/executor.py +75 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/manager/llm_clients_builder.py +17 -11
- klaude_code-2.3.0/src/klaude_code/core/prompts/prompt-nano-banana.md +1 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/sub_agent_tool.py +2 -1
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/anthropic/client.py +7 -4
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/anthropic/input.py +54 -29
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/google/client.py +1 -1
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/google/input.py +23 -2
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/openai_compatible/input.py +22 -13
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/openrouter/input.py +37 -25
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/responses/input.py +96 -57
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/commands.py +1 -2
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/events/system.py +4 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/op.py +17 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/op_handler.py +5 -0
- klaude_code-2.3.0/src/klaude_code/protocol/sub_agent/AGENTS.md +28 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/sub_agent/__init__.py +10 -14
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/sub_agent/image_gen.py +2 -1
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/session/codec.py +2 -6
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/session/session.py +9 -1
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/skill/assets/create-plan/SKILL.md +3 -5
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/__init__.py +3 -6
- klaude_code-2.3.0/src/klaude_code/tui/command/model_cmd.py +57 -0
- klaude_code-2.3.0/src/klaude_code/tui/command/model_select.py +144 -0
- klaude_code-2.3.0/src/klaude_code/tui/command/sub_agent_model_cmd.py +190 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/bash_syntax.py +1 -1
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/common.py +1 -1
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/developer.py +0 -5
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/metadata.py +1 -63
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/rich/cjk_wrap.py +3 -2
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/rich/status.py +49 -3
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/rich/theme.py +2 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/sub_agent.py +25 -46
- klaude_code-2.3.0/src/klaude_code/tui/components/welcome.py +99 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/input/prompt_toolkit.py +14 -1
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/renderer.py +2 -3
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/terminal/selector.py +5 -3
- klaude_code-2.2.0/src/klaude_code/core/prompts/prompt-nano-banana.md +0 -1
- klaude_code-2.2.0/src/klaude_code/tui/command/help_cmd.py +0 -51
- klaude_code-2.2.0/src/klaude_code/tui/command/model_cmd.py +0 -94
- klaude_code-2.2.0/src/klaude_code/tui/command/model_select.py +0 -84
- klaude_code-2.2.0/src/klaude_code/tui/command/release_notes_cmd.py +0 -85
- {klaude_code-2.2.0 → klaude_code-2.3.0}/README.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/app/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/base.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/claude/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/claude/exceptions.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/claude/oauth.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/claude/token_manager.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/auth/codex/token_manager.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/cli/auth_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/cli/config_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/cli/cost_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/cli/debug.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/cli/session_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/config/builtin_config.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/config/thinking.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/manager/llm_clients.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/prompts/prompt-sub-agent-image-gen.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/reminders.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/task.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/context.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/truncation.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/core/turn.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/bedrock/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/bedrock/client.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/claude/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/claude/client.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/codex/client.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/google/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/image.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/registry.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/llm/usage.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/log.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/events/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/events/base.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/events/chat.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/events/lifecycle.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/events/metadata.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/events/streaming.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/events/tools.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/message.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/model.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/sub_agent/task.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/sub_agent/web.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/session/export.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/session/selector.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/session/store.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/session/templates/export_session.html +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/clear_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/command_abc.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/copy_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/debug_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/export_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/export_online_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/fork_session_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/prompt-init.md +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/prompt_command.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/refresh_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/registry.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/resume_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/status_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/terminal_setup_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/command/thinking_cmd.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/commands.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/assistant.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/diffs.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/errors.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/mermaid_viewer.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/rich/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/rich/code_panel.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/rich/live.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/rich/markdown.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/rich/quote.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/rich/searchable_text.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/thinking.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/tools.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/components/user_input.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/display.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/input/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/input/clipboard.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/input/completers.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/input/key_bindings.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/machine.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/runner.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/terminal/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/terminal/color.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/terminal/control.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/terminal/image.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/terminal/notifier.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/tui/terminal/progress_bar.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/ui/common.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/ui/debug_mode.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/ui/terminal/title.py +0 -0
- {klaude_code-2.2.0 → klaude_code-2.3.0}/src/klaude_code/update.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "klaude-code"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.3.0"
|
|
8
8
|
description = "Minimal code agent CLI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -40,6 +40,7 @@ dev = [
|
|
|
40
40
|
"pyright>=1.1.407",
|
|
41
41
|
"pytest>=8.4.1",
|
|
42
42
|
"pytest-cov>=7.0.0",
|
|
43
|
+
"ty>=0.0.8",
|
|
43
44
|
]
|
|
44
45
|
|
|
45
46
|
|
|
@@ -74,6 +75,15 @@ exclude = [".venv/"]
|
|
|
74
75
|
root = "."
|
|
75
76
|
extraPaths = ["src"]
|
|
76
77
|
|
|
78
|
+
[tool.ty.environment]
|
|
79
|
+
python-version = "3.13"
|
|
80
|
+
|
|
81
|
+
[tool.ty.src]
|
|
82
|
+
include = [
|
|
83
|
+
"src",
|
|
84
|
+
"tests",
|
|
85
|
+
]
|
|
86
|
+
|
|
77
87
|
[tool.importlinter]
|
|
78
88
|
root_packages = ["klaude_code"]
|
|
79
89
|
include_external_packages = false
|
|
@@ -55,24 +55,11 @@ async def initialize_app_components(
|
|
|
55
55
|
|
|
56
56
|
config = load_config()
|
|
57
57
|
|
|
58
|
-
if init_config.banana:
|
|
59
|
-
# Banana mode is strict: it requires the built-in Nano Banana image model to be available.
|
|
60
|
-
required_model = "nano-banana-pro@or"
|
|
61
|
-
available = {m.model_name for m in config.iter_model_entries(only_available=True)}
|
|
62
|
-
if required_model not in available:
|
|
63
|
-
log(
|
|
64
|
-
(
|
|
65
|
-
f"Error: --banana requires model '{required_model}', but it is not available in the current environment",
|
|
66
|
-
"red",
|
|
67
|
-
)
|
|
68
|
-
)
|
|
69
|
-
log(("Hint: set OPENROUTER_API_KEY (Nano Banana Pro is configured via OpenRouter by default)", "yellow"))
|
|
70
|
-
raise typer.Exit(2)
|
|
71
|
-
|
|
72
58
|
try:
|
|
73
59
|
llm_clients = build_llm_clients(
|
|
74
60
|
config,
|
|
75
61
|
model_override=init_config.model,
|
|
62
|
+
skip_sub_agents=init_config.vanilla or init_config.banana,
|
|
76
63
|
)
|
|
77
64
|
except ValueError as exc:
|
|
78
65
|
if init_config.model:
|
|
@@ -92,7 +79,7 @@ async def initialize_app_components(
|
|
|
92
79
|
elif init_config.vanilla:
|
|
93
80
|
model_profile_provider = VanillaModelProfileProvider()
|
|
94
81
|
else:
|
|
95
|
-
model_profile_provider = DefaultModelProfileProvider()
|
|
82
|
+
model_profile_provider = DefaultModelProfileProvider(config=config)
|
|
96
83
|
|
|
97
84
|
event_queue: asyncio.Queue[events.Event] = asyncio.Queue()
|
|
98
85
|
|
|
@@ -243,18 +243,34 @@ def _build_provider_info_panel(provider: ProviderConfig, available: bool) -> Quo
|
|
|
243
243
|
|
|
244
244
|
|
|
245
245
|
def _build_models_table(
|
|
246
|
-
provider: ProviderConfig,
|
|
246
|
+
provider: ProviderConfig,
|
|
247
|
+
config: Config,
|
|
247
248
|
) -> Table:
|
|
248
249
|
"""Build a table for models under a provider."""
|
|
249
250
|
provider_available = not provider.is_api_key_missing()
|
|
250
251
|
|
|
252
|
+
def _resolve_selector(value: str | None) -> str | None:
|
|
253
|
+
if not value:
|
|
254
|
+
return None
|
|
255
|
+
try:
|
|
256
|
+
resolved = config.resolve_model_location_prefer_available(value) or config.resolve_model_location(value)
|
|
257
|
+
except ValueError:
|
|
258
|
+
return None
|
|
259
|
+
if resolved is None:
|
|
260
|
+
return None
|
|
261
|
+
return f"{resolved[0]}@{resolved[1]}"
|
|
262
|
+
|
|
263
|
+
default_selector = _resolve_selector(config.main_model)
|
|
264
|
+
|
|
251
265
|
# Build reverse mapping: model_name -> list of agent roles using it
|
|
252
266
|
model_to_agents: dict[str, list[str]] = {}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
267
|
+
for agent_role, model_name in (config.sub_agent_models or {}).items():
|
|
268
|
+
selector = _resolve_selector(model_name)
|
|
269
|
+
if selector is None:
|
|
270
|
+
continue
|
|
271
|
+
if selector not in model_to_agents:
|
|
272
|
+
model_to_agents[selector] = []
|
|
273
|
+
model_to_agents[selector].append(agent_role)
|
|
258
274
|
|
|
259
275
|
models_table = Table.grid(
|
|
260
276
|
padding=(0, 2),
|
|
@@ -275,10 +291,11 @@ def _build_models_table(
|
|
|
275
291
|
else:
|
|
276
292
|
# Build role tags for this model
|
|
277
293
|
roles: list[str] = []
|
|
278
|
-
|
|
294
|
+
selector = f"{model.model_name}@{provider.provider_name}"
|
|
295
|
+
if selector == default_selector:
|
|
279
296
|
roles.append("default")
|
|
280
|
-
if
|
|
281
|
-
roles.extend(role.lower() for role in model_to_agents[
|
|
297
|
+
if selector in model_to_agents:
|
|
298
|
+
roles.extend(role.lower() for role in model_to_agents[selector])
|
|
282
299
|
|
|
283
300
|
if roles:
|
|
284
301
|
name = Text.assemble(
|
|
@@ -350,6 +367,6 @@ def display_models_and_providers(config: Config, *, show_all: bool = False):
|
|
|
350
367
|
console.print()
|
|
351
368
|
|
|
352
369
|
# Models table for this provider
|
|
353
|
-
models_table = _build_models_table(provider, config
|
|
370
|
+
models_table = _build_models_table(provider, config)
|
|
354
371
|
console.print(models_table)
|
|
355
372
|
console.print("\n")
|
|
@@ -124,18 +124,28 @@ def main_callback(
|
|
|
124
124
|
raise typer.Exit(2)
|
|
125
125
|
|
|
126
126
|
from klaude_code.app.runtime import AppInitConfig
|
|
127
|
-
from klaude_code.tui.command.model_select import select_model_interactive
|
|
127
|
+
from klaude_code.tui.command.model_select import ModelSelectStatus, select_model_interactive
|
|
128
128
|
from klaude_code.tui.runner import run_interactive
|
|
129
129
|
|
|
130
130
|
update_terminal_title()
|
|
131
131
|
|
|
132
132
|
chosen_model = model
|
|
133
133
|
if banana:
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
keywords = ["gemini-3-pro-image", "gemini-2.5-flash-image"]
|
|
135
|
+
model_result = select_model_interactive(keywords=keywords)
|
|
136
|
+
if model_result.status == ModelSelectStatus.SELECTED and model_result.model is not None:
|
|
137
|
+
chosen_model = model_result.model
|
|
138
|
+
elif model_result.status == ModelSelectStatus.CANCELLED:
|
|
139
|
+
return
|
|
140
|
+
else:
|
|
141
|
+
log(("Error: no available nano-banana model", "red"))
|
|
142
|
+
log(("Hint: set OPENROUTER_API_KEY or GOOGLE_API_KEY to enable nano-banana models", "yellow"))
|
|
143
|
+
raise typer.Exit(2)
|
|
136
144
|
elif model or select_model:
|
|
137
|
-
|
|
138
|
-
if
|
|
145
|
+
model_result = select_model_interactive(preferred=model)
|
|
146
|
+
if model_result.status == ModelSelectStatus.SELECTED and model_result.model is not None:
|
|
147
|
+
chosen_model = model_result.model
|
|
148
|
+
else:
|
|
139
149
|
return
|
|
140
150
|
|
|
141
151
|
# Resolve session id before entering asyncio loop
|
|
@@ -162,7 +172,12 @@ def main_callback(
|
|
|
162
172
|
cfg = load_config()
|
|
163
173
|
|
|
164
174
|
if session_meta.model_config_name:
|
|
165
|
-
|
|
175
|
+
try:
|
|
176
|
+
model_is_known = cfg.has_model_config_name(session_meta.model_config_name)
|
|
177
|
+
except ValueError:
|
|
178
|
+
model_is_known = False
|
|
179
|
+
|
|
180
|
+
if model_is_known:
|
|
166
181
|
chosen_model = session_meta.model_config_name
|
|
167
182
|
else:
|
|
168
183
|
log(
|
|
@@ -176,7 +191,7 @@ def main_callback(
|
|
|
176
191
|
raw_model = session_meta.model_name.strip()
|
|
177
192
|
if raw_model:
|
|
178
193
|
matches = [
|
|
179
|
-
m.
|
|
194
|
+
m.selector
|
|
180
195
|
for m in cfg.iter_model_entries()
|
|
181
196
|
if (m.model_params.model or "").strip().lower() == raw_model.lower()
|
|
182
197
|
]
|
|
@@ -189,9 +204,10 @@ def main_callback(
|
|
|
189
204
|
|
|
190
205
|
cfg = load_config()
|
|
191
206
|
if cfg.main_model is None:
|
|
192
|
-
|
|
193
|
-
if
|
|
207
|
+
model_result = select_model_interactive()
|
|
208
|
+
if model_result.status != ModelSelectStatus.SELECTED or model_result.model is None:
|
|
194
209
|
raise typer.Exit(1)
|
|
210
|
+
chosen_model = model_result.model
|
|
195
211
|
# Save the selection as default
|
|
196
212
|
cfg.main_model = chosen_model
|
|
197
213
|
from klaude_code.config.config import config_path
|
|
@@ -7,7 +7,7 @@ provider_list:
|
|
|
7
7
|
protocol: anthropic
|
|
8
8
|
api_key: ${ANTHROPIC_API_KEY}
|
|
9
9
|
model_list:
|
|
10
|
-
- model_name: sonnet
|
|
10
|
+
- model_name: sonnet
|
|
11
11
|
model_params:
|
|
12
12
|
model: claude-sonnet-4-5-20250929
|
|
13
13
|
context_limit: 200000
|
|
@@ -18,7 +18,7 @@ provider_list:
|
|
|
18
18
|
output: 15.0
|
|
19
19
|
cache_read: 0.3
|
|
20
20
|
cache_write: 3.75
|
|
21
|
-
- model_name: opus
|
|
21
|
+
- model_name: opus
|
|
22
22
|
model_params:
|
|
23
23
|
model: claude-opus-4-5-20251101
|
|
24
24
|
context_limit: 200000
|
|
@@ -187,10 +187,10 @@ provider_list:
|
|
|
187
187
|
input: 0.5
|
|
188
188
|
output: 3.0
|
|
189
189
|
cache_read: 0.05
|
|
190
|
-
- model_name: nano-banana-pro
|
|
190
|
+
- model_name: nano-banana-pro
|
|
191
191
|
model_params:
|
|
192
192
|
model: google/gemini-3-pro-image-preview
|
|
193
|
-
context_limit:
|
|
193
|
+
context_limit: 66000
|
|
194
194
|
modalities:
|
|
195
195
|
- image
|
|
196
196
|
- text
|
|
@@ -199,6 +199,18 @@ provider_list:
|
|
|
199
199
|
output: 12
|
|
200
200
|
cache_read: 0.2
|
|
201
201
|
image: 120
|
|
202
|
+
- model_name: nano-banana
|
|
203
|
+
model_params:
|
|
204
|
+
model: google/gemini-2.5-flash-image
|
|
205
|
+
context_limit: 33000
|
|
206
|
+
modalities:
|
|
207
|
+
- image
|
|
208
|
+
- text
|
|
209
|
+
cost:
|
|
210
|
+
input: 0.3
|
|
211
|
+
output: 2.5
|
|
212
|
+
cache_read: 0.03
|
|
213
|
+
image: 30
|
|
202
214
|
- model_name: grok
|
|
203
215
|
model_params:
|
|
204
216
|
model: x-ai/grok-4.1-fast
|
|
@@ -234,7 +246,7 @@ provider_list:
|
|
|
234
246
|
protocol: google
|
|
235
247
|
api_key: ${GOOGLE_API_KEY}
|
|
236
248
|
model_list:
|
|
237
|
-
- model_name: gemini-pro
|
|
249
|
+
- model_name: gemini-pro
|
|
238
250
|
model_params:
|
|
239
251
|
model: gemini-3-pro-preview
|
|
240
252
|
context_limit: 1048576
|
|
@@ -242,7 +254,7 @@ provider_list:
|
|
|
242
254
|
input: 2.0
|
|
243
255
|
output: 12.0
|
|
244
256
|
cache_read: 0.2
|
|
245
|
-
- model_name: gemini-flash
|
|
257
|
+
- model_name: gemini-flash
|
|
246
258
|
model_params:
|
|
247
259
|
model: gemini-3-flash-preview
|
|
248
260
|
context_limit: 1048576
|
|
@@ -250,10 +262,10 @@ provider_list:
|
|
|
250
262
|
input: 0.5
|
|
251
263
|
output: 3.0
|
|
252
264
|
cache_read: 0.05
|
|
253
|
-
- model_name: nano-banana-pro
|
|
265
|
+
- model_name: nano-banana-pro
|
|
254
266
|
model_params:
|
|
255
267
|
model: gemini-3-pro-image-preview
|
|
256
|
-
context_limit:
|
|
268
|
+
context_limit: 66000
|
|
257
269
|
modalities:
|
|
258
270
|
- image
|
|
259
271
|
- text
|
|
@@ -269,7 +281,7 @@ provider_list:
|
|
|
269
281
|
aws_secret_key: ${AWS_SECRET_ACCESS_KEY}
|
|
270
282
|
aws_region: ${AWS_REGION}
|
|
271
283
|
model_list:
|
|
272
|
-
- model_name: sonnet
|
|
284
|
+
- model_name: sonnet
|
|
273
285
|
model_params:
|
|
274
286
|
model: us.anthropic.claude-sonnet-4-5-20250929-v1:0
|
|
275
287
|
context_limit: 200000
|
|
@@ -302,7 +314,7 @@ provider_list:
|
|
|
302
314
|
api_key: ${MOONSHOT_API_KEY}
|
|
303
315
|
base_url: https://api.moonshot.cn/anthropic
|
|
304
316
|
model_list:
|
|
305
|
-
- model_name: kimi
|
|
317
|
+
- model_name: kimi
|
|
306
318
|
model_params:
|
|
307
319
|
model: kimi-k2-thinking
|
|
308
320
|
context_limit: 262144
|
|
@@ -318,7 +330,7 @@ provider_list:
|
|
|
318
330
|
- provider_name: claude-max
|
|
319
331
|
protocol: claude_oauth
|
|
320
332
|
model_list:
|
|
321
|
-
- model_name: sonnet
|
|
333
|
+
- model_name: sonnet
|
|
322
334
|
model_params:
|
|
323
335
|
model: claude-sonnet-4-5-20250929
|
|
324
336
|
context_limit: 200000
|
|
@@ -327,7 +339,7 @@ provider_list:
|
|
|
327
339
|
output: 15.0
|
|
328
340
|
cache_read: 0.3
|
|
329
341
|
cache_write: 3.75
|
|
330
|
-
- model_name: opus
|
|
342
|
+
- model_name: opus
|
|
331
343
|
model_params:
|
|
332
344
|
model: claude-opus-4-5-20251101
|
|
333
345
|
context_limit: 200000
|
|
@@ -340,7 +352,7 @@ provider_list:
|
|
|
340
352
|
output: 25.0
|
|
341
353
|
cache_read: 0.5
|
|
342
354
|
cache_write: 6.25
|
|
343
|
-
- model_name: haiku
|
|
355
|
+
- model_name: haiku
|
|
344
356
|
model_params:
|
|
345
357
|
model: claude-haiku-4-5-20251001
|
|
346
358
|
context_limit: 200000
|
|
@@ -364,6 +376,3 @@ provider_list:
|
|
|
364
376
|
input: 1.75
|
|
365
377
|
output: 14.0
|
|
366
378
|
cache_read: 0.17
|
|
367
|
-
|
|
368
|
-
sub_agent_models:
|
|
369
|
-
ImageGen: nano-banana-pro@or
|
|
@@ -140,6 +140,16 @@ class ModelEntry(BaseModel):
|
|
|
140
140
|
provider: str
|
|
141
141
|
model_params: llm_param.LLMConfigModelParameter
|
|
142
142
|
|
|
143
|
+
@property
|
|
144
|
+
def selector(self) -> str:
|
|
145
|
+
"""Return a provider-qualified model selector.
|
|
146
|
+
|
|
147
|
+
This selector can be persisted in user config (e.g. ``sonnet@openrouter``)
|
|
148
|
+
and later resolved via :meth:`Config.get_model_config`.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
return f"{self.model_name}@{self.provider}"
|
|
152
|
+
|
|
143
153
|
|
|
144
154
|
class UserConfig(BaseModel):
|
|
145
155
|
"""User configuration (what gets saved to disk)."""
|
|
@@ -191,8 +201,103 @@ class Config(BaseModel):
|
|
|
191
201
|
"""Set the user config reference for saving."""
|
|
192
202
|
object.__setattr__(self, "_user_config", user_config)
|
|
193
203
|
|
|
204
|
+
@classmethod
|
|
205
|
+
def _split_model_selector(cls, model_selector: str) -> tuple[str, str | None]:
|
|
206
|
+
"""Split a model selector into (model_name, provider_name).
|
|
207
|
+
|
|
208
|
+
Supported forms:
|
|
209
|
+
- ``sonnet``: unqualified; caller should pick the first matching provider.
|
|
210
|
+
- ``sonnet@openrouter``: provider-qualified.
|
|
211
|
+
|
|
212
|
+
Note: the provider segment is normalized for backwards compatibility.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
trimmed = model_selector.strip()
|
|
216
|
+
if "@" not in trimmed:
|
|
217
|
+
return trimmed, None
|
|
218
|
+
|
|
219
|
+
base, provider = trimmed.rsplit("@", 1)
|
|
220
|
+
base = base.strip()
|
|
221
|
+
provider = provider.strip()
|
|
222
|
+
if not base or not provider:
|
|
223
|
+
raise ValueError(f"Invalid model selector: {model_selector!r}")
|
|
224
|
+
return base, provider
|
|
225
|
+
|
|
226
|
+
def has_model_config_name(self, model_selector: str) -> bool:
|
|
227
|
+
"""Return True if the selector points to a configured model.
|
|
228
|
+
|
|
229
|
+
This check is configuration-only: it does not require a valid API key or
|
|
230
|
+
OAuth login.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
model_name, provider_name = self._split_model_selector(model_selector)
|
|
234
|
+
if provider_name is not None:
|
|
235
|
+
for provider in self.provider_list:
|
|
236
|
+
if provider.provider_name.casefold() != provider_name.casefold():
|
|
237
|
+
continue
|
|
238
|
+
return any(m.model_name == model_name for m in provider.model_list)
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
return any(any(m.model_name == model_name for m in provider.model_list) for provider in self.provider_list)
|
|
242
|
+
|
|
243
|
+
def resolve_model_location(self, model_selector: str) -> tuple[str, str] | None:
|
|
244
|
+
"""Resolve a selector to (model_name, provider_name), without auth checks.
|
|
245
|
+
|
|
246
|
+
- If the selector is provider-qualified, returns that provider.
|
|
247
|
+
- If unqualified, returns the first provider that defines the model.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
model_name, provider_name = self._split_model_selector(model_selector)
|
|
251
|
+
if provider_name is not None:
|
|
252
|
+
for provider in self.provider_list:
|
|
253
|
+
if provider.provider_name.casefold() != provider_name.casefold():
|
|
254
|
+
continue
|
|
255
|
+
if any(m.model_name == model_name for m in provider.model_list):
|
|
256
|
+
return model_name, provider.provider_name
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
for provider in self.provider_list:
|
|
260
|
+
if any(m.model_name == model_name for m in provider.model_list):
|
|
261
|
+
return model_name, provider.provider_name
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
def resolve_model_location_prefer_available(self, model_selector: str) -> tuple[str, str] | None:
|
|
265
|
+
"""Resolve a selector to (model_name, provider_name), preferring usable providers.
|
|
266
|
+
|
|
267
|
+
This uses the same availability logic as :meth:`get_model_config` (API-key
|
|
268
|
+
presence for non-OAuth protocols).
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
requested_model, requested_provider = self._split_model_selector(model_selector)
|
|
272
|
+
|
|
273
|
+
for provider in self.provider_list:
|
|
274
|
+
if requested_provider is not None and provider.provider_name.casefold() != requested_provider.casefold():
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
api_key = provider.get_resolved_api_key()
|
|
278
|
+
if (
|
|
279
|
+
provider.protocol
|
|
280
|
+
not in {
|
|
281
|
+
llm_param.LLMClientProtocol.CODEX_OAUTH,
|
|
282
|
+
llm_param.LLMClientProtocol.CLAUDE_OAUTH,
|
|
283
|
+
llm_param.LLMClientProtocol.BEDROCK,
|
|
284
|
+
}
|
|
285
|
+
and not api_key
|
|
286
|
+
):
|
|
287
|
+
continue
|
|
288
|
+
|
|
289
|
+
if any(m.model_name == requested_model for m in provider.model_list):
|
|
290
|
+
return requested_model, provider.provider_name
|
|
291
|
+
|
|
292
|
+
return None
|
|
293
|
+
|
|
194
294
|
def get_model_config(self, model_name: str) -> llm_param.LLMConfigParameter:
|
|
295
|
+
requested_model, requested_provider = self._split_model_selector(model_name)
|
|
296
|
+
|
|
195
297
|
for provider in self.provider_list:
|
|
298
|
+
if requested_provider is not None and provider.provider_name.casefold() != requested_provider.casefold():
|
|
299
|
+
continue
|
|
300
|
+
|
|
196
301
|
# Resolve ${ENV_VAR} syntax for api_key
|
|
197
302
|
api_key = provider.get_resolved_api_key()
|
|
198
303
|
|
|
@@ -206,15 +311,22 @@ class Config(BaseModel):
|
|
|
206
311
|
}
|
|
207
312
|
and not api_key
|
|
208
313
|
):
|
|
314
|
+
# When provider is explicitly requested, fail fast with a clearer error.
|
|
315
|
+
if requested_provider is not None:
|
|
316
|
+
raise ValueError(
|
|
317
|
+
f"Provider '{provider.provider_name}' is not available (missing API key) for: {model_name}"
|
|
318
|
+
)
|
|
209
319
|
continue
|
|
320
|
+
|
|
210
321
|
for model in provider.model_list:
|
|
211
|
-
if model.model_name
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
)
|
|
322
|
+
if model.model_name != requested_model:
|
|
323
|
+
continue
|
|
324
|
+
provider_dump = provider.model_dump(exclude={"model_list"})
|
|
325
|
+
provider_dump["api_key"] = api_key
|
|
326
|
+
return llm_param.LLMConfigParameter(
|
|
327
|
+
**provider_dump,
|
|
328
|
+
**model.model_params.model_dump(),
|
|
329
|
+
)
|
|
218
330
|
|
|
219
331
|
raise ValueError(f"Unknown model: {model_name}")
|
|
220
332
|
|
|
@@ -235,6 +347,27 @@ class Config(BaseModel):
|
|
|
235
347
|
for model in provider.model_list
|
|
236
348
|
]
|
|
237
349
|
|
|
350
|
+
def has_available_image_model(self) -> bool:
|
|
351
|
+
"""Check if any image generation model is available."""
|
|
352
|
+
for entry in self.iter_model_entries(only_available=True):
|
|
353
|
+
if entry.model_params.modalities and "image" in entry.model_params.modalities:
|
|
354
|
+
return True
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
def get_first_available_nano_banana_model(self) -> str | None:
|
|
358
|
+
"""Get the first available nano-banana model, or None."""
|
|
359
|
+
for entry in self.iter_model_entries(only_available=True):
|
|
360
|
+
if "nano-banana" in entry.model_name:
|
|
361
|
+
return entry.model_name
|
|
362
|
+
return None
|
|
363
|
+
|
|
364
|
+
def get_first_available_image_model(self) -> str | None:
|
|
365
|
+
"""Get the first available image generation model, or None."""
|
|
366
|
+
for entry in self.iter_model_entries(only_available=True):
|
|
367
|
+
if entry.model_params.modalities and "image" in entry.model_params.modalities:
|
|
368
|
+
return entry.model_name
|
|
369
|
+
return None
|
|
370
|
+
|
|
238
371
|
async def save(self) -> None:
|
|
239
372
|
"""Save user config to file (excludes builtin providers).
|
|
240
373
|
|
|
@@ -418,6 +551,10 @@ def create_example_config() -> bool:
|
|
|
418
551
|
header = "# Example configuration for klaude-code\n"
|
|
419
552
|
header += "# Copy this file to klaude-config.yaml and modify as needed.\n"
|
|
420
553
|
header += "# Run `klaude list` to see available models.\n"
|
|
554
|
+
header += "# Tip: you can pick a provider explicitly with `model@provider` (e.g. `sonnet@openrouter`).\n"
|
|
555
|
+
header += (
|
|
556
|
+
"# If you omit `@provider` (e.g. `sonnet`), klaude picks the first configured provider with credentials.\n"
|
|
557
|
+
)
|
|
421
558
|
header += "#\n"
|
|
422
559
|
header += "# Built-in providers (anthropic, openai, openrouter, deepseek) are available automatically.\n"
|
|
423
560
|
header += "# Just set the corresponding API key environment variable to use them.\n\n"
|
|
@@ -50,7 +50,8 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
50
50
|
|
|
51
51
|
# Only show models from providers with valid API keys
|
|
52
52
|
models: list[ModelEntry] = sorted(
|
|
53
|
-
config.iter_model_entries(only_available=True),
|
|
53
|
+
config.iter_model_entries(only_available=True),
|
|
54
|
+
key=lambda m: (m.model_name.lower(), m.provider.lower()),
|
|
54
55
|
)
|
|
55
56
|
|
|
56
57
|
if not models:
|
|
@@ -62,26 +63,42 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
62
63
|
error_message="No models available",
|
|
63
64
|
)
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
selectors: list[str] = [m.selector for m in models]
|
|
66
67
|
|
|
67
68
|
# Try to match preferred model name
|
|
68
69
|
filter_hint = preferred
|
|
69
70
|
if preferred and preferred.strip():
|
|
70
71
|
preferred = preferred.strip()
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
|
|
73
|
+
# Exact match on selector (e.g. sonnet@openrouter)
|
|
74
|
+
if preferred in selectors:
|
|
73
75
|
return ModelMatchResult(matched_model=preferred, filtered_models=models, filter_hint=None)
|
|
74
76
|
|
|
77
|
+
# Exact match on base model name (e.g. sonnet)
|
|
78
|
+
exact_base_matches = [m for m in models if m.model_name == preferred]
|
|
79
|
+
if len(exact_base_matches) == 1:
|
|
80
|
+
return ModelMatchResult(
|
|
81
|
+
matched_model=exact_base_matches[0].selector,
|
|
82
|
+
filtered_models=models,
|
|
83
|
+
filter_hint=None,
|
|
84
|
+
)
|
|
85
|
+
if len(exact_base_matches) > 1:
|
|
86
|
+
return ModelMatchResult(matched_model=None, filtered_models=exact_base_matches, filter_hint=filter_hint)
|
|
87
|
+
|
|
75
88
|
preferred_lower = preferred.lower()
|
|
76
|
-
# Case-insensitive exact match (model_name
|
|
89
|
+
# Case-insensitive exact match (selector/model_name/model_params.model)
|
|
77
90
|
exact_ci_matches = [
|
|
78
91
|
m
|
|
79
92
|
for m in models
|
|
80
|
-
if preferred_lower == m.
|
|
93
|
+
if preferred_lower == m.selector.lower()
|
|
94
|
+
or preferred_lower == m.model_name.lower()
|
|
95
|
+
or preferred_lower == (m.model_params.model or "").lower()
|
|
81
96
|
]
|
|
82
97
|
if len(exact_ci_matches) == 1:
|
|
83
98
|
return ModelMatchResult(
|
|
84
|
-
matched_model=exact_ci_matches[0].
|
|
99
|
+
matched_model=exact_ci_matches[0].selector,
|
|
100
|
+
filtered_models=models,
|
|
101
|
+
filter_hint=None,
|
|
85
102
|
)
|
|
86
103
|
|
|
87
104
|
# Normalized matching (e.g. gpt52 == gpt-5.2, gpt52 in gpt-5.2-2025-...)
|
|
@@ -91,24 +108,30 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
91
108
|
normalized_matches = [
|
|
92
109
|
m
|
|
93
110
|
for m in models
|
|
94
|
-
if preferred_norm == _normalize_model_key(m.
|
|
111
|
+
if preferred_norm == _normalize_model_key(m.selector)
|
|
112
|
+
or preferred_norm == _normalize_model_key(m.model_name)
|
|
95
113
|
or preferred_norm == _normalize_model_key(m.model_params.model or "")
|
|
96
114
|
]
|
|
97
115
|
if len(normalized_matches) == 1:
|
|
98
116
|
return ModelMatchResult(
|
|
99
|
-
matched_model=normalized_matches[0].
|
|
117
|
+
matched_model=normalized_matches[0].selector,
|
|
118
|
+
filtered_models=models,
|
|
119
|
+
filter_hint=None,
|
|
100
120
|
)
|
|
101
121
|
|
|
102
122
|
if not normalized_matches and len(preferred_norm) >= 4:
|
|
103
123
|
normalized_matches = [
|
|
104
124
|
m
|
|
105
125
|
for m in models
|
|
106
|
-
if preferred_norm in _normalize_model_key(m.
|
|
126
|
+
if preferred_norm in _normalize_model_key(m.selector)
|
|
127
|
+
or preferred_norm in _normalize_model_key(m.model_name)
|
|
107
128
|
or preferred_norm in _normalize_model_key(m.model_params.model or "")
|
|
108
129
|
]
|
|
109
130
|
if len(normalized_matches) == 1:
|
|
110
131
|
return ModelMatchResult(
|
|
111
|
-
matched_model=normalized_matches[0].
|
|
132
|
+
matched_model=normalized_matches[0].selector,
|
|
133
|
+
filtered_models=models,
|
|
134
|
+
filter_hint=None,
|
|
112
135
|
)
|
|
113
136
|
|
|
114
137
|
# Partial match (case-insensitive) on model_name or model_params.model.
|
|
@@ -116,10 +139,12 @@ def match_model_from_config(preferred: str | None = None) -> ModelMatchResult:
|
|
|
116
139
|
matches = normalized_matches or [
|
|
117
140
|
m
|
|
118
141
|
for m in models
|
|
119
|
-
if preferred_lower in m.
|
|
142
|
+
if preferred_lower in m.selector.lower()
|
|
143
|
+
or preferred_lower in m.model_name.lower()
|
|
144
|
+
or preferred_lower in (m.model_params.model or "").lower()
|
|
120
145
|
]
|
|
121
146
|
if len(matches) == 1:
|
|
122
|
-
return ModelMatchResult(matched_model=matches[0].
|
|
147
|
+
return ModelMatchResult(matched_model=matches[0].selector, filtered_models=models, filter_hint=None)
|
|
123
148
|
if matches:
|
|
124
149
|
# Multiple matches: filter the list for interactive selection
|
|
125
150
|
return ModelMatchResult(matched_model=None, filtered_models=matches, filter_hint=filter_hint)
|