klaude-code 2.2.0__py3-none-any.whl → 2.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. klaude_code/app/runtime.py +2 -15
  2. klaude_code/cli/list_model.py +30 -13
  3. klaude_code/cli/main.py +26 -10
  4. klaude_code/config/assets/builtin_config.yaml +177 -310
  5. klaude_code/config/config.py +158 -21
  6. klaude_code/config/{select_model.py → model_matcher.py} +41 -16
  7. klaude_code/config/sub_agent_model_helper.py +217 -0
  8. klaude_code/config/thinking.py +2 -2
  9. klaude_code/const.py +1 -1
  10. klaude_code/core/agent_profile.py +43 -5
  11. klaude_code/core/executor.py +129 -47
  12. klaude_code/core/manager/llm_clients_builder.py +17 -11
  13. klaude_code/core/prompts/prompt-nano-banana.md +1 -1
  14. klaude_code/core/tool/file/diff_builder.py +25 -18
  15. klaude_code/core/tool/sub_agent_tool.py +2 -1
  16. klaude_code/llm/anthropic/client.py +12 -9
  17. klaude_code/llm/anthropic/input.py +54 -29
  18. klaude_code/llm/client.py +1 -1
  19. klaude_code/llm/codex/client.py +2 -2
  20. klaude_code/llm/google/client.py +7 -7
  21. klaude_code/llm/google/input.py +23 -2
  22. klaude_code/llm/input_common.py +2 -2
  23. klaude_code/llm/openai_compatible/client.py +3 -3
  24. klaude_code/llm/openai_compatible/input.py +22 -13
  25. klaude_code/llm/openai_compatible/stream.py +1 -1
  26. klaude_code/llm/openrouter/client.py +4 -4
  27. klaude_code/llm/openrouter/input.py +35 -25
  28. klaude_code/llm/responses/client.py +5 -5
  29. klaude_code/llm/responses/input.py +96 -57
  30. klaude_code/protocol/commands.py +1 -2
  31. klaude_code/protocol/events/__init__.py +7 -1
  32. klaude_code/protocol/events/chat.py +10 -0
  33. klaude_code/protocol/events/system.py +4 -0
  34. klaude_code/protocol/llm_param.py +1 -1
  35. klaude_code/protocol/model.py +0 -26
  36. klaude_code/protocol/op.py +17 -5
  37. klaude_code/protocol/op_handler.py +5 -0
  38. klaude_code/protocol/sub_agent/AGENTS.md +28 -0
  39. klaude_code/protocol/sub_agent/__init__.py +10 -14
  40. klaude_code/protocol/sub_agent/image_gen.py +2 -1
  41. klaude_code/session/codec.py +2 -6
  42. klaude_code/session/session.py +13 -3
  43. klaude_code/skill/assets/create-plan/SKILL.md +3 -5
  44. klaude_code/tui/command/__init__.py +3 -6
  45. klaude_code/tui/command/clear_cmd.py +0 -1
  46. klaude_code/tui/command/command_abc.py +6 -4
  47. klaude_code/tui/command/copy_cmd.py +10 -10
  48. klaude_code/tui/command/debug_cmd.py +11 -10
  49. klaude_code/tui/command/export_online_cmd.py +18 -23
  50. klaude_code/tui/command/fork_session_cmd.py +39 -43
  51. klaude_code/tui/command/model_cmd.py +10 -49
  52. klaude_code/tui/command/model_picker.py +142 -0
  53. klaude_code/tui/command/refresh_cmd.py +0 -1
  54. klaude_code/tui/command/registry.py +15 -21
  55. klaude_code/tui/command/resume_cmd.py +10 -16
  56. klaude_code/tui/command/status_cmd.py +8 -12
  57. klaude_code/tui/command/sub_agent_model_cmd.py +185 -0
  58. klaude_code/tui/command/terminal_setup_cmd.py +8 -11
  59. klaude_code/tui/command/thinking_cmd.py +4 -6
  60. klaude_code/tui/commands.py +5 -0
  61. klaude_code/tui/components/bash_syntax.py +1 -1
  62. klaude_code/tui/components/command_output.py +96 -0
  63. klaude_code/tui/components/common.py +1 -1
  64. klaude_code/tui/components/developer.py +3 -115
  65. klaude_code/tui/components/metadata.py +1 -63
  66. klaude_code/tui/components/rich/cjk_wrap.py +3 -2
  67. klaude_code/tui/components/rich/status.py +49 -3
  68. klaude_code/tui/components/rich/theme.py +2 -0
  69. klaude_code/tui/components/sub_agent.py +25 -46
  70. klaude_code/tui/components/welcome.py +99 -0
  71. klaude_code/tui/input/prompt_toolkit.py +19 -8
  72. klaude_code/tui/machine.py +5 -0
  73. klaude_code/tui/renderer.py +7 -8
  74. klaude_code/tui/runner.py +0 -6
  75. klaude_code/tui/terminal/selector.py +8 -6
  76. {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/METADATA +21 -74
  77. {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/RECORD +79 -76
  78. klaude_code/tui/command/help_cmd.py +0 -51
  79. klaude_code/tui/command/model_select.py +0 -84
  80. klaude_code/tui/command/release_notes_cmd.py +0 -85
  81. {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/WHEEL +0 -0
  82. {klaude_code-2.2.0.dist-info → klaude_code-2.4.0.dist-info}/entry_points.txt +0 -0
@@ -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
 
@@ -181,7 +181,7 @@ def format_env_var_display(value: str | None) -> Text:
181
181
 
182
182
  def _get_model_params_display(model: ModelConfig) -> list[Text]:
183
183
  """Get display elements for model parameters."""
184
- param_strings = format_model_params(model.model_params)
184
+ param_strings = format_model_params(model)
185
185
  if param_strings:
186
186
  return [Text(s) for s in param_strings]
187
187
  return [Text("")]
@@ -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, main_model: str | None, sub_agent_models: dict[str, str] | None = None
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
- if sub_agent_models:
254
- for agent_role, model_name in sub_agent_models.items():
255
- if model_name not in model_to_agents:
256
- model_to_agents[model_name] = []
257
- model_to_agents[model_name].append(agent_role)
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),
@@ -270,15 +286,16 @@ def _build_models_table(
270
286
 
271
287
  if not provider_available:
272
288
  name = Text.assemble((prefix, ThemeKey.LINES), (model.model_name, "dim"))
273
- model_id = Text(model.model_params.model or "", style="dim")
289
+ model_id = Text(model.model_id or "", style="dim")
274
290
  params = Text("(unavailable)", style="dim")
275
291
  else:
276
292
  # Build role tags for this model
277
293
  roles: list[str] = []
278
- if model.model_name == main_model:
294
+ selector = f"{model.model_name}@{provider.provider_name}"
295
+ if selector == default_selector:
279
296
  roles.append("default")
280
- if model.model_name in model_to_agents:
281
- roles.extend(role.lower() for role in model_to_agents[model.model_name])
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(
@@ -288,7 +305,7 @@ def _build_models_table(
288
305
  )
289
306
  else:
290
307
  name = Text.assemble((prefix, ThemeKey.LINES), (model.model_name, ThemeKey.CONFIG_ITEM_NAME))
291
- model_id = Text(model.model_params.model or "")
308
+ model_id = Text(model.model_id or "")
292
309
  params = Text(" · ").join(_get_model_params_display(model))
293
310
 
294
311
  models_table.add_row(name, model_id, params)
@@ -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.main_model, config.sub_agent_models)
370
+ models_table = _build_models_table(provider, config)
354
371
  console.print(models_table)
355
372
  console.print("\n")
klaude_code/cli/main.py CHANGED
@@ -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_picker 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
- # Banana mode always uses the built-in Nano Banana Pro image model.
135
- chosen_model = "nano-banana-pro@or"
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
- chosen_model = select_model_interactive(preferred=model)
138
- if chosen_model is None:
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
- if any(m.model_name == session_meta.model_config_name for m in cfg.iter_model_entries()):
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,9 +191,9 @@ def main_callback(
176
191
  raw_model = session_meta.model_name.strip()
177
192
  if raw_model:
178
193
  matches = [
179
- m.model_name
194
+ m.selector
180
195
  for m in cfg.iter_model_entries()
181
- if (m.model_params.model or "").strip().lower() == raw_model.lower()
196
+ if (m.model_id or "").strip().lower() == raw_model.lower()
182
197
  ]
183
198
  if len(matches) == 1:
184
199
  chosen_model = matches[0]
@@ -189,9 +204,10 @@ def main_callback(
189
204
 
190
205
  cfg = load_config()
191
206
  if cfg.main_model is None:
192
- chosen_model = select_model_interactive()
193
- if chosen_model is None:
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