klaude-code 1.2.25__py3-none-any.whl → 1.2.27__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 (42) hide show
  1. klaude_code/cli/config_cmd.py +1 -5
  2. klaude_code/cli/list_model.py +170 -129
  3. klaude_code/cli/main.py +37 -5
  4. klaude_code/cli/runtime.py +4 -6
  5. klaude_code/cli/self_update.py +2 -1
  6. klaude_code/cli/session_cmd.py +1 -1
  7. klaude_code/config/__init__.py +3 -1
  8. klaude_code/config/assets/__init__.py +1 -0
  9. klaude_code/config/assets/builtin_config.yaml +233 -0
  10. klaude_code/config/builtin_config.py +37 -0
  11. klaude_code/config/config.py +332 -112
  12. klaude_code/config/select_model.py +45 -8
  13. klaude_code/const.py +5 -1
  14. klaude_code/core/executor.py +4 -2
  15. klaude_code/core/manager/llm_clients_builder.py +4 -1
  16. klaude_code/core/tool/file/apply_patch_tool.py +26 -3
  17. klaude_code/core/tool/file/edit_tool.py +4 -4
  18. klaude_code/core/tool/file/write_tool.py +4 -4
  19. klaude_code/core/tool/shell/bash_tool.py +2 -2
  20. klaude_code/llm/openai_compatible/stream.py +2 -1
  21. klaude_code/protocol/model.py +24 -1
  22. klaude_code/session/export.py +1 -1
  23. klaude_code/session/selector.py +2 -2
  24. klaude_code/session/session.py +4 -4
  25. klaude_code/ui/modes/repl/completers.py +4 -4
  26. klaude_code/ui/modes/repl/event_handler.py +23 -4
  27. klaude_code/ui/modes/repl/input_prompt_toolkit.py +4 -4
  28. klaude_code/ui/modes/repl/key_bindings.py +4 -4
  29. klaude_code/ui/modes/repl/renderer.py +22 -17
  30. klaude_code/ui/renderers/diffs.py +1 -1
  31. klaude_code/ui/renderers/metadata.py +2 -2
  32. klaude_code/ui/renderers/sub_agent.py +14 -12
  33. klaude_code/ui/renderers/thinking.py +1 -1
  34. klaude_code/ui/renderers/tools.py +27 -3
  35. klaude_code/ui/rich/markdown.py +35 -15
  36. klaude_code/ui/rich/theme.py +2 -5
  37. klaude_code/ui/terminal/color.py +1 -1
  38. klaude_code/ui/terminal/control.py +4 -4
  39. {klaude_code-1.2.25.dist-info → klaude_code-1.2.27.dist-info}/METADATA +121 -127
  40. {klaude_code-1.2.25.dist-info → klaude_code-1.2.27.dist-info}/RECORD +42 -39
  41. {klaude_code-1.2.25.dist-info → klaude_code-1.2.27.dist-info}/WHEEL +0 -0
  42. {klaude_code-1.2.25.dist-info → klaude_code-1.2.27.dist-info}/entry_points.txt +0 -0
@@ -18,8 +18,8 @@ from klaude_code.ui.rich.theme import ThemeKey
18
18
 
19
19
  # Tool markers (Unicode symbols for UI display)
20
20
  MARK_GENERIC = "⚒"
21
- MARK_BASH = ""
22
- MARK_PLAN = ""
21
+ MARK_BASH = "$"
22
+ MARK_PLAN = "Ξ"
23
23
  MARK_READ = "←"
24
24
  MARK_EDIT = "±"
25
25
  MARK_WRITE = "+"
@@ -528,19 +528,28 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
528
528
  def _extract_diff(ui_extra: model.ToolResultUIExtra | None) -> model.DiffUIExtra | None:
529
529
  if isinstance(ui_extra, model.DiffUIExtra):
530
530
  return ui_extra
531
+ if isinstance(ui_extra, model.MultiUIExtra):
532
+ for item in ui_extra.items:
533
+ if isinstance(item, model.DiffUIExtra):
534
+ return item
531
535
  return None
532
536
 
533
537
 
534
538
  def _extract_markdown_doc(ui_extra: model.ToolResultUIExtra | None) -> model.MarkdownDocUIExtra | None:
535
539
  if isinstance(ui_extra, model.MarkdownDocUIExtra):
536
540
  return ui_extra
541
+ if isinstance(ui_extra, model.MultiUIExtra):
542
+ for item in ui_extra.items:
543
+ if isinstance(item, model.MarkdownDocUIExtra):
544
+ return item
537
545
  return None
538
546
 
539
547
 
540
548
  def render_markdown_doc(md_ui: model.MarkdownDocUIExtra, *, code_theme: str) -> RenderableType:
541
549
  """Render markdown document content in a panel."""
550
+ header = render_path(md_ui.file_path, ThemeKey.TOOL_PARAM_FILE_PATH)
542
551
  return Panel.fit(
543
- NoInsetMarkdown(md_ui.content, code_theme=code_theme),
552
+ Group(header, Text(""), NoInsetMarkdown(md_ui.content, code_theme=code_theme)),
544
553
  box=box.SIMPLE,
545
554
  border_style=ThemeKey.LINES,
546
555
  style=ThemeKey.WRITE_MARKDOWN_PANEL,
@@ -562,6 +571,19 @@ def render_tool_result(e: events.ToolResultEvent, *, code_theme: str = "monokai"
562
571
  error_msg = truncate_display(e.result)
563
572
  return r_errors.render_error(error_msg)
564
573
 
574
+ # Render multiple ui blocks if present
575
+ if isinstance(e.ui_extra, model.MultiUIExtra) and e.ui_extra.items:
576
+ rendered: list[RenderableType] = []
577
+ for item in e.ui_extra.items:
578
+ if isinstance(item, model.MarkdownDocUIExtra):
579
+ rendered.append(Padding.indent(render_markdown_doc(item, code_theme=code_theme), level=2))
580
+ elif isinstance(item, model.DiffUIExtra):
581
+ show_file_name = e.tool_name == tools.APPLY_PATCH
582
+ rendered.append(
583
+ Padding.indent(r_diffs.render_structured_diff(item, show_file_name=show_file_name), level=2)
584
+ )
585
+ return Group(*rendered) if rendered else None
586
+
565
587
  # Show truncation info if output was truncated and saved to file
566
588
  truncation_info = get_truncation_info(e)
567
589
  if truncation_info:
@@ -580,6 +602,8 @@ def render_tool_result(e: events.ToolResultEvent, *, code_theme: str = "monokai"
580
602
  return Padding.indent(render_markdown_doc(md_ui, code_theme=code_theme), level=2)
581
603
  return Padding.indent(r_diffs.render_structured_diff(diff_ui) if diff_ui else Text(""), level=2)
582
604
  case tools.APPLY_PATCH:
605
+ if md_ui:
606
+ return Padding.indent(render_markdown_doc(md_ui, code_theme=code_theme), level=2)
583
607
  if diff_ui:
584
608
  return Padding.indent(r_diffs.render_structured_diff(diff_ui, show_file_name=True), level=2)
585
609
  if len(e.result.strip()) == 0:
@@ -1,4 +1,3 @@
1
- # copy from https://github.com/Aider-AI/aider/blob/main/aider/mdstream.py
2
1
  from __future__ import annotations
3
2
 
4
3
  import contextlib
@@ -9,11 +8,13 @@ from typing import Any, ClassVar
9
8
 
10
9
  from markdown_it import MarkdownIt
11
10
  from markdown_it.token import Token
11
+ from rich import box
12
12
  from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
13
- from rich.markdown import CodeBlock, Heading, Markdown, MarkdownElement
13
+ from rich.markdown import CodeBlock, Heading, Markdown, MarkdownElement, TableElement
14
14
  from rich.rule import Rule
15
15
  from rich.style import Style, StyleType
16
16
  from rich.syntax import Syntax
17
+ from rich.table import Table
17
18
  from rich.text import Text
18
19
  from rich.theme import Theme
19
20
 
@@ -53,6 +54,24 @@ class Divider(MarkdownElement):
53
54
  yield Rule(style=style, characters="-")
54
55
 
55
56
 
57
+ class MinimalHeavyHeadTable(TableElement):
58
+ """A table element with MINIMAL_HEAVY_HEAD box style."""
59
+
60
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
61
+ table = Table(box=box.MARKDOWN)
62
+
63
+ if self.header is not None and self.header.row is not None:
64
+ for column in self.header.row.cells:
65
+ table.add_column(column.content)
66
+
67
+ if self.body is not None:
68
+ for row in self.body.rows:
69
+ row_content = [element.content for element in row.cells]
70
+ table.add_row(*row_content)
71
+
72
+ yield table
73
+
74
+
56
75
  class LeftHeading(Heading):
57
76
  """A heading class that renders left-justified."""
58
77
 
@@ -64,7 +83,7 @@ class LeftHeading(Heading):
64
83
  yield h1_text
65
84
  elif self.tag == "h2":
66
85
  text.stylize(Style(bold=True, underline=False))
67
- yield Rule(title=text, characters="·", style="markdown.h2.border", align="left")
86
+ yield text
68
87
  else:
69
88
  yield text
70
89
 
@@ -78,6 +97,7 @@ class NoInsetMarkdown(Markdown):
78
97
  "code_block": NoInsetCodeBlock,
79
98
  "heading_open": LeftHeading,
80
99
  "hr": Divider,
100
+ "table_open": MinimalHeavyHeadTable,
81
101
  }
82
102
 
83
103
 
@@ -90,6 +110,7 @@ class ThinkingMarkdown(Markdown):
90
110
  "code_block": ThinkingCodeBlock,
91
111
  "heading_open": LeftHeading,
92
112
  "hr": Divider,
113
+ "table_open": MinimalHeavyHeadTable,
93
114
  }
94
115
 
95
116
 
@@ -171,7 +192,7 @@ class MarkdownStream:
171
192
 
172
193
  try:
173
194
  tokens = self._parser.parse(text)
174
- except Exception:
195
+ except Exception: # markdown-it-py may raise various internal errors during parsing
175
196
  return 0
176
197
 
177
198
  top_level: list[Token] = [token for token in tokens if token.level == 0 and token.map is not None]
@@ -341,9 +362,6 @@ class MarkdownStream:
341
362
  def update(self, text: str, final: bool = False) -> None:
342
363
  """Update the display with the latest full markdown buffer."""
343
364
 
344
- if self._live_sink is None:
345
- return
346
-
347
365
  now = time.time()
348
366
  if not final and now - self.when < self.min_delay:
349
367
  return
@@ -374,18 +392,20 @@ class MarkdownStream:
374
392
  self._stable_source_line_count = stable_line
375
393
 
376
394
  if final:
377
- self._live_sink(None)
395
+ if self._live_sink is not None:
396
+ self._live_sink(None)
378
397
  return
379
398
 
380
- apply_mark_live = self._stable_source_line_count == 0
381
- live_lines = self._render_markdown_to_lines(live_source, apply_mark=apply_mark_live)
399
+ if const.MARKDOWN_STREAM_LIVE_REPAINT_ENABLED and self._live_sink is not None:
400
+ apply_mark_live = self._stable_source_line_count == 0
401
+ live_lines = self._render_markdown_to_lines(live_source, apply_mark=apply_mark_live)
382
402
 
383
- if self._stable_rendered_lines and not self._stable_rendered_lines[-1].strip():
384
- while live_lines and not live_lines[0].strip():
385
- live_lines.pop(0)
403
+ if self._stable_rendered_lines and not self._stable_rendered_lines[-1].strip():
404
+ while live_lines and not live_lines[0].strip():
405
+ live_lines.pop(0)
386
406
 
387
- live_text = Text.from_ansi("".join(live_lines))
388
- self._live_sink(live_text)
407
+ live_text = Text.from_ansi("".join(live_lines))
408
+ self._live_sink(live_text)
389
409
 
390
410
  elapsed = time.time() - start
391
411
  self.min_delay = min(max(elapsed * 6, 1.0 / 30), 0.5)
@@ -276,7 +276,7 @@ def get_theme(theme: str | None = None) -> Themes:
276
276
  ThemeKey.RESUME_FLAG.value: "bold reverse " + palette.green,
277
277
  ThemeKey.RESUME_INFO.value: palette.green,
278
278
  # CONFIGURATION DISPLAY
279
- ThemeKey.CONFIG_TABLE_HEADER.value: palette.green,
279
+ ThemeKey.CONFIG_TABLE_HEADER.value: palette.grey1,
280
280
  ThemeKey.CONFIG_STATUS_OK.value: palette.green,
281
281
  ThemeKey.CONFIG_STATUS_PRIMARY.value: palette.yellow,
282
282
  ThemeKey.CONFIG_STATUS_ERROR.value: palette.red,
@@ -291,7 +291,7 @@ def get_theme(theme: str | None = None) -> Themes:
291
291
  "markdown.code.border": palette.grey3,
292
292
  "markdown.h1": "bold reverse",
293
293
  "markdown.h1.border": palette.grey3,
294
- "markdown.h2.border": palette.grey3,
294
+ "markdown.h2": "bold underline",
295
295
  "markdown.h3": "bold " + palette.grey1,
296
296
  "markdown.h4": "bold " + palette.grey2,
297
297
  "markdown.hr": palette.grey3,
@@ -311,7 +311,6 @@ def get_theme(theme: str | None = None) -> Themes:
311
311
  "markdown.code.border": palette.grey3,
312
312
  "markdown.h1": "bold reverse",
313
313
  "markdown.h1.border": palette.grey3,
314
- "markdown.h2.border": palette.grey3,
315
314
  "markdown.h3": "bold " + palette.grey1,
316
315
  "markdown.h4": "bold " + palette.grey2,
317
316
  "markdown.hr": palette.grey3,
@@ -329,7 +328,6 @@ def get_theme(theme: str | None = None) -> Themes:
329
328
  Style(color=palette.blue),
330
329
  Style(color=palette.purple),
331
330
  Style(color=palette.orange),
332
- Style(color=palette.red),
333
331
  Style(color=palette.grey1),
334
332
  Style(color=palette.yellow),
335
333
  ],
@@ -339,7 +337,6 @@ def get_theme(theme: str | None = None) -> Themes:
339
337
  Style(bgcolor=palette.blue_sub_background),
340
338
  Style(bgcolor=palette.purple_background),
341
339
  Style(bgcolor=palette.orange_background),
342
- Style(bgcolor=palette.red_background),
343
340
  Style(bgcolor=palette.grey_background),
344
341
  Style(bgcolor=palette.yellow_background),
345
342
  ],
@@ -182,7 +182,7 @@ def _parse_osc_color_response(data: bytes) -> tuple[int, int, int] | None:
182
182
 
183
183
  try:
184
184
  text = data.decode("ascii", errors="ignore")
185
- except Exception:
185
+ except LookupError: # encoding lookup failure (should not happen with "ascii")
186
186
  return None
187
187
 
188
188
  match = _OSC_BG_REGEX.search(text)
@@ -48,7 +48,7 @@ def start_esc_interrupt_monitor(
48
48
  try:
49
49
  fd = sys.stdin.fileno()
50
50
  old = termios.tcgetattr(fd)
51
- except Exception as exc: # pragma: no cover - environment dependent
51
+ except OSError as exc: # pragma: no cover - environment dependent
52
52
  log((f"esc monitor init error: {exc}", "r red"))
53
53
  return
54
54
 
@@ -60,7 +60,7 @@ def start_esc_interrupt_monitor(
60
60
  continue
61
61
  try:
62
62
  ch = os.read(fd, 1).decode(errors="ignore")
63
- except Exception:
63
+ except OSError:
64
64
  continue
65
65
  if ch != "\x1b":
66
66
  continue
@@ -71,7 +71,7 @@ def start_esc_interrupt_monitor(
71
71
  while r2:
72
72
  try:
73
73
  seq += os.read(fd, 1).decode(errors="ignore")
74
- except Exception:
74
+ except OSError:
75
75
  break
76
76
  r2, _, _ = select.select([sys.stdin], [], [], 0.0)
77
77
 
@@ -127,7 +127,7 @@ def install_sigint_double_press_exit(
127
127
 
128
128
  try:
129
129
  signal.signal(signal.SIGINT, _handler)
130
- except Exception: # pragma: no cover - platform dependent
130
+ except (OSError, ValueError): # pragma: no cover - platform dependent
131
131
  # If installing the handler fails, restore() will be a no-op.
132
132
  return lambda: None
133
133
 
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: klaude-code
3
- Version: 1.2.25
4
- Summary: Add your description here
3
+ Version: 1.2.27
4
+ Summary: Minimal code agent CLI
5
5
  Requires-Dist: anthropic>=0.66.0
6
6
  Requires-Dist: chardet>=5.2.0
7
7
  Requires-Dist: ddgs>=9.9.3
@@ -21,7 +21,7 @@ Description-Content-Type: text/markdown
21
21
 
22
22
  # Klaude Code
23
23
 
24
- Multi-model code agent CLI.
24
+ Minimal code agent CLI.
25
25
 
26
26
  ## Features
27
27
  - **Multi-provider**: Anthropic, OpenAI Responses API, OpenRouter
@@ -92,147 +92,141 @@ klaude [--model <name>] [--select-model]
92
92
 
93
93
  ### Configuration
94
94
 
95
- An example config will be created in `~/.klaude/klaude-config.yaml` when first run.
95
+ #### Quick Start (Zero Config)
96
96
 
97
- Open the configuration file in editor:
97
+ Klaude comes with built-in provider configurations. Just set an API key environment variable and start using it:
98
+
99
+ ```bash
100
+ # Pick one (or more) of these:
101
+ export ANTHROPIC_API_KEY=sk-ant-xxx # Claude models
102
+ export OPENAI_API_KEY=sk-xxx # GPT models
103
+ export OPENROUTER_API_KEY=sk-or-xxx # OpenRouter (multi-provider)
104
+ export DEEPSEEK_API_KEY=sk-xxx # DeepSeek models
105
+ export MOONSHOT_API_KEY=sk-xxx # Moonshot/Kimi models
106
+
107
+ # Then just run:
108
+ klaude
109
+ ```
110
+
111
+ On first run, you'll be prompted to select a model. Your choice is saved as `main_model`.
112
+
113
+ #### Built-in Providers
114
+
115
+ | Provider | Env Variable | Models |
116
+ |-------------|-----------------------|-------------------------------------------------------------------------------|
117
+ | anthropic | `ANTHROPIC_API_KEY` | sonnet, opus |
118
+ | openai | `OPENAI_API_KEY` | gpt-5.2 |
119
+ | openrouter | `OPENROUTER_API_KEY` | gpt-5.2, gpt-5.2-fast, gpt-5.1-codex-max, sonnet, opus, haiku, kimi, gemini-* |
120
+ | deepseek | `DEEPSEEK_API_KEY` | deepseek |
121
+ | moonshot | `MOONSHOT_API_KEY` | kimi@moonshot |
122
+ | codex | N/A (OAuth) | gpt-5.2-codex |
123
+
124
+ List all configured providers and models:
125
+
126
+ ```bash
127
+ klaude list
128
+ ```
129
+
130
+ Models from providers without a valid API key are shown as dimmed/unavailable.
131
+
132
+ #### Custom Configuration
133
+
134
+ User config file: `~/.klaude/klaude-config.yaml`
135
+
136
+ Open in editor:
98
137
 
99
138
  ```bash
100
139
  klaude config
101
140
  ```
102
141
 
103
- An example config yaml:
142
+ ##### Adding Models to Built-in Providers
143
+
144
+ You can add custom models to existing providers without redefining the entire provider:
104
145
 
105
146
  ```yaml
147
+ # Just specify provider_name and your new models - no need for protocol/api_key
106
148
  provider_list:
107
- - provider_name: openrouter
108
- protocol: openrouter # support <responses|openrouter|anthropic|openai>
109
- api_key: <your-openrouter-api-key>
110
-
111
- - provider_name: openai-responses
112
- protocol: responses
113
- api_key: <your-openai-api-key>
114
-
115
- - provider_name: anthropic
116
- protocol: anthropic
117
- api_key: <your-anthropic-api-key>
118
-
119
- - provider_name: moonshot
120
- protocol: anthropic
121
- base_url: https://api.moonshot.cn/anthropic
122
- api_key: <your-api-key>
123
-
124
- - provider_name: deepseek
125
- protocol: anthropic
126
- base_url: https://api.deepseek.com/anthropic
127
- api_key: <your-api-key>
128
-
129
- model_list:
130
-
131
- - model_name: deepseek
132
- provider: deepseek
133
- model_params:
134
- model: deepseek-reasoner
135
- context_limit: 128000
136
- thinking:
137
- type: enabled
138
- budget_tokens: 8192
139
- cost:
140
- currency: CNY
141
- input: 2
142
- output: 3
143
- cache_read: 0.2
144
-
145
- - model_name: codex-max
146
- provider: openai-responses
147
- model_params:
148
- model: gpt-5.1-codex-max
149
- thinking:
150
- reasoning_effort: medium
151
- context_limit: 400000
152
- max_tokens: 128000
153
- cost:
154
- input: 1.25
155
- output: 10
156
- cache_read: 0.13
157
-
158
- - model_name: gpt-5.1
159
- provider: openrouter
160
- model_params:
161
- model: openai/gpt-5.1
162
- context_limit: 400000
163
- max_tokens: 128000
164
- verbosity: high
165
- thinking:
166
- reasoning_effort: high
167
- cost:
168
- input: 1.25
169
- output: 10
170
- cache_read: 0.13
171
-
172
- - model_name: kimi@moonshot
173
- provider: moonshot
174
- model_params:
175
- model: kimi-k2-thinking
176
- context_limit: 262144
177
- thinking:
178
- type: enabled
179
- budget_tokens: 8192
180
- cost:
181
- currency: CNY
182
- input: 4
183
- output: 16
184
- cache_read: 1
185
-
186
- - model_name: opus
187
- provider: openrouter
188
- model_params:
189
- model: anthropic/claude-4.5-opus
190
- context_limit: 200000
191
- provider_routing:
192
- only: [ google-vertex ]
193
- verbosity: high
194
- thinking:
195
- type: enabled
196
- budget_tokens: 31999
197
- cost:
198
- input: 5
199
- output: 25
200
- cache_read: 0.5
201
- cache_write: 6.25
202
-
203
- - model_name: gemini
204
- provider: openrouter
205
- model_params:
206
- model: google/gemini-3-pro-preview
207
- context_limit: 1048576
208
- thinking:
209
- reasoning_effort: medium
210
- cost:
211
- input: 2
212
- output: 12
213
- cache_read: 0.2
214
-
215
- - model_name: haiku
216
- provider: anthropic
217
- model_params:
218
- model: claude-haiku-4-5-20251001
219
- context_limit: 200000
220
- cost:
221
- input: 1
222
- output: 5
223
- cache_read: 0.1
224
- cache_write: 1.25
149
+ - provider_name: openrouter
150
+ model_list:
151
+ - model_name: my-custom-model
152
+ model_params:
153
+ model: some-provider/some-model-id
154
+ context_limit: 200000
155
+ ```
156
+
157
+ Your models are merged with built-in models. To override a built-in model, use the same `model_name`.
225
158
 
159
+ ##### Overriding Provider Settings
160
+
161
+ Override provider-level settings (like api_key) while keeping built-in models:
162
+
163
+ ```yaml
164
+ provider_list:
165
+ - provider_name: anthropic
166
+ api_key: sk-my-custom-key # Override the default ${ANTHROPIC_API_KEY}
167
+ # Built-in models (sonnet, opus) are still available
168
+ ```
169
+
170
+ ##### Adding New Providers
171
+
172
+ For providers not in the built-in list, you must specify `protocol`:
173
+
174
+ ```yaml
175
+ provider_list:
176
+ - provider_name: my-azure-openai
177
+ protocol: openai
178
+ api_key: ${AZURE_OPENAI_KEY}
179
+ base_url: https://my-instance.openai.azure.com/
180
+ is_azure: true
181
+ azure_api_version: "2024-02-15-preview"
182
+ model_list:
183
+ - model_name: gpt-4-azure
184
+ model_params:
185
+ model: gpt-4
186
+ context_limit: 128000
187
+ ```
188
+
189
+ ##### Full Example
190
+
191
+ ```yaml
192
+ # User configuration - merged with built-in config
226
193
  main_model: opus
227
194
 
228
195
  sub_agent_models:
229
- oracle: gpt-5.1
230
- explore: haiku
196
+ oracle: gpt-4.1
197
+ explore: sonnet
231
198
  task: opus
232
- webagent: haiku
199
+ webagent: sonnet
233
200
 
201
+ provider_list:
202
+ # Add models to built-in openrouter
203
+ - provider_name: openrouter
204
+ model_list:
205
+ - model_name: qwen-coder
206
+ model_params:
207
+ model: qwen/qwen-2.5-coder-32b-instruct
208
+ context_limit: 131072
209
+
210
+ # Add a completely new provider
211
+ - provider_name: local-ollama
212
+ protocol: openai
213
+ base_url: http://localhost:11434/v1
214
+ api_key: ollama
215
+ model_list:
216
+ - model_name: local-llama
217
+ model_params:
218
+ model: llama3.2
219
+ context_limit: 8192
234
220
  ```
235
221
 
222
+ ##### Supported Protocols
223
+
224
+ - `anthropic` - Anthropic Claude API
225
+ - `openai` - OpenAI-compatible API
226
+ - `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
227
+ - `openrouter` - OpenRouter API
228
+ - `codex` - OpenAI Codex CLI (OAuth-based)
229
+
236
230
  List configured providers and models:
237
231
 
238
232
  ```bash