deepy-cli 0.1.5__tar.gz → 0.1.7__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.
Files changed (72) hide show
  1. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/PKG-INFO +16 -5
  2. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/README.md +15 -4
  3. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/pyproject.toml +1 -1
  4. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/__init__.py +1 -1
  5. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/cli.py +7 -1
  6. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/config/__init__.py +18 -0
  7. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/config/settings.py +107 -19
  8. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/data/tools/AskUserQuestion.md +3 -1
  9. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/data/tools/WebFetch.md +2 -1
  10. deepy_cli-0.1.7/src/deepy/data/tools/shell.md +13 -0
  11. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/llm/__init__.py +2 -1
  12. deepy_cli-0.1.7/src/deepy/llm/compaction.py +269 -0
  13. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/llm/context.py +14 -47
  14. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/llm/events.py +1 -0
  15. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/llm/runner.py +29 -1
  16. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/prompts/compact.py +40 -2
  17. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/prompts/runtime_context.py +9 -7
  18. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/prompts/system.py +5 -2
  19. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/prompts/tool_docs.py +1 -1
  20. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/sessions/jsonl.py +184 -4
  21. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/sessions/manager.py +13 -15
  22. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/status.py +8 -0
  23. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/tools/agents.py +10 -8
  24. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/tools/builtin.py +230 -46
  25. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/tools/shell_utils.py +84 -0
  26. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/ask_user_question.py +1 -1
  27. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/message_view.py +121 -31
  28. deepy_cli-0.1.7/src/deepy/ui/model_picker.py +178 -0
  29. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/prompt_input.py +2 -4
  30. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/slash_commands.py +2 -0
  31. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/styles.py +18 -21
  32. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/terminal.py +361 -29
  33. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/welcome.py +1 -2
  34. deepy_cli-0.1.5/src/deepy/data/tools/bash.md +0 -7
  35. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/__main__.py +0 -0
  36. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/data/__init__.py +0 -0
  37. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/data/tools/WebSearch.md +0 -0
  38. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/data/tools/__init__.py +0 -0
  39. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/data/tools/edit.md +0 -0
  40. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/data/tools/modify.md +0 -0
  41. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/data/tools/read.md +0 -0
  42. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/data/tools/write.md +0 -0
  43. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/errors.py +0 -0
  44. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/llm/agent.py +0 -0
  45. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/llm/model_capabilities.py +0 -0
  46. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/llm/provider.py +0 -0
  47. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/llm/replay.py +0 -0
  48. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/llm/thinking.py +0 -0
  49. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/prompts/__init__.py +0 -0
  50. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/prompts/rules.py +0 -0
  51. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/sessions/__init__.py +0 -0
  52. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/skills.py +0 -0
  53. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/tools/__init__.py +0 -0
  54. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/tools/file_state.py +0 -0
  55. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/tools/result.py +0 -0
  56. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/__init__.py +0 -0
  57. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/app.py +0 -0
  58. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/exit_summary.py +0 -0
  59. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/loading_text.py +0 -0
  60. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/markdown.py +0 -0
  61. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/prompt_buffer.py +0 -0
  62. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/session_list.py +0 -0
  63. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/session_picker.py +0 -0
  64. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/theme_picker.py +0 -0
  65. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/ui/thinking_state.py +0 -0
  66. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/update_check.py +0 -0
  67. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/usage.py +0 -0
  68. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/utils/__init__.py +0 -0
  69. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/utils/debug_logger.py +0 -0
  70. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/utils/error_logger.py +0 -0
  71. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/utils/json.py +0 -0
  72. {deepy_cli-0.1.5 → deepy_cli-0.1.7}/src/deepy/utils/notify.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: deepy-cli
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Deepy - Vibe coding for DeepSeek models in your terminal
5
5
  Keywords: deepseek,coding-agent,terminal,cli,agents
6
6
  Author: kirineko
@@ -62,7 +62,8 @@ context state visible while the agent works.
62
62
  ## Highlights
63
63
 
64
64
  - DeepSeek-first model setup with `deepseek-v4-pro`, thinking enabled, and
65
- `reasoning_effort=max` by default.
65
+ `reasoning_effort=max` by default; `/model` can switch between V4 Pro and V4
66
+ Flash with `none`, `high`, or `max` thinking strength.
66
67
  - OpenAI Agents SDK integration through `OpenAIChatCompletionsModel`.
67
68
  - Project-aware coding tools for reading files, modifying files, running shell
68
69
  commands, and showing readable diffs.
@@ -141,15 +142,24 @@ Deepy only uses TOML configuration. JSON config files are intentionally rejected
141
142
  api_key = "sk-..."
142
143
  name = "deepseek-v4-pro"
143
144
  base_url = "https://api.deepseek.com"
145
+ thinking = true
146
+ reasoning_effort = "max" # high or max when thinking is enabled
144
147
 
145
148
  [context]
146
149
  window_tokens = 1048576
147
150
  compact_trigger_ratio = 0.8
151
+ reserved_context_tokens = 50000
152
+ compact_preserve_recent_messages = 2
148
153
 
149
154
  [ui]
150
155
  theme = "auto" # auto, dark, or light
151
156
  ```
152
157
 
158
+ Supported interactive model choices are `deepseek-v4-pro` and
159
+ `deepseek-v4-flash`. In `/model`, thinking strength `none` saves
160
+ `thinking = false`; `high` and `max` save `thinking = true` with the matching
161
+ `reasoning_effort`.
162
+
153
163
  WebSearch uses Deepy's hosted SearXNG endpoint by default. You can override it
154
164
  with your own SearXNG instance:
155
165
 
@@ -192,8 +202,10 @@ Inside the interactive terminal:
192
202
 
193
203
  ```text
194
204
  /skills List available skills
205
+ /model Select model and thinking strength
195
206
  /new Start a fresh conversation
196
207
  /resume Pick a previous session
208
+ /compact Compact the active session context
197
209
  /theme Show or change UI theme
198
210
  /reset Delete config and run setup again
199
211
  / Open the command menu
@@ -226,6 +238,5 @@ assets live outside the package directory and are not included in the wheel.
226
238
 
227
239
  ## Release Status
228
240
 
229
- Deepy is preparing its first public `0.1.5` release. The current release path is
230
- GitHub + PyPI. Standalone binaries and npm wrappers can be added later, but the
231
- primary distribution is the Python CLI.
241
+ Deepy `0.1.7` is released through GitHub and PyPI. Standalone binaries and npm
242
+ wrappers can be added later, but the primary distribution is the Python CLI.
@@ -34,7 +34,8 @@ context state visible while the agent works.
34
34
  ## Highlights
35
35
 
36
36
  - DeepSeek-first model setup with `deepseek-v4-pro`, thinking enabled, and
37
- `reasoning_effort=max` by default.
37
+ `reasoning_effort=max` by default; `/model` can switch between V4 Pro and V4
38
+ Flash with `none`, `high`, or `max` thinking strength.
38
39
  - OpenAI Agents SDK integration through `OpenAIChatCompletionsModel`.
39
40
  - Project-aware coding tools for reading files, modifying files, running shell
40
41
  commands, and showing readable diffs.
@@ -113,15 +114,24 @@ Deepy only uses TOML configuration. JSON config files are intentionally rejected
113
114
  api_key = "sk-..."
114
115
  name = "deepseek-v4-pro"
115
116
  base_url = "https://api.deepseek.com"
117
+ thinking = true
118
+ reasoning_effort = "max" # high or max when thinking is enabled
116
119
 
117
120
  [context]
118
121
  window_tokens = 1048576
119
122
  compact_trigger_ratio = 0.8
123
+ reserved_context_tokens = 50000
124
+ compact_preserve_recent_messages = 2
120
125
 
121
126
  [ui]
122
127
  theme = "auto" # auto, dark, or light
123
128
  ```
124
129
 
130
+ Supported interactive model choices are `deepseek-v4-pro` and
131
+ `deepseek-v4-flash`. In `/model`, thinking strength `none` saves
132
+ `thinking = false`; `high` and `max` save `thinking = true` with the matching
133
+ `reasoning_effort`.
134
+
125
135
  WebSearch uses Deepy's hosted SearXNG endpoint by default. You can override it
126
136
  with your own SearXNG instance:
127
137
 
@@ -164,8 +174,10 @@ Inside the interactive terminal:
164
174
 
165
175
  ```text
166
176
  /skills List available skills
177
+ /model Select model and thinking strength
167
178
  /new Start a fresh conversation
168
179
  /resume Pick a previous session
180
+ /compact Compact the active session context
169
181
  /theme Show or change UI theme
170
182
  /reset Delete config and run setup again
171
183
  / Open the command menu
@@ -198,6 +210,5 @@ assets live outside the package directory and are not included in the wheel.
198
210
 
199
211
  ## Release Status
200
212
 
201
- Deepy is preparing its first public `0.1.5` release. The current release path is
202
- GitHub + PyPI. Standalone binaries and npm wrappers can be added later, but the
203
- primary distribution is the Python CLI.
213
+ Deepy `0.1.7` is released through GitHub and PyPI. Standalone binaries and npm
214
+ wrappers can be added later, but the primary distribution is the Python CLI.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepy-cli"
3
- version = "0.1.5"
3
+ version = "0.1.7"
4
4
  description = "Deepy - Vibe coding for DeepSeek models in your terminal"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.1.5"
3
+ __version__ = "0.1.7"
4
4
 
5
5
 
6
6
  def main() -> None:
@@ -226,6 +226,11 @@ def _doctor(args: argparse.Namespace) -> tuple[int, dict[str, object]]:
226
226
  or settings.context.resolved_compact_threshold > 0,
227
227
  str(settings.context.resolved_compact_threshold),
228
228
  )
229
+ check(
230
+ "reserved_context",
231
+ settings.context.reserved_context_tokens > 0,
232
+ str(settings.context.reserved_context_tokens),
233
+ )
229
234
 
230
235
  try:
231
236
  build_provider_bundle(settings)
@@ -241,6 +246,7 @@ def _doctor(args: argparse.Namespace) -> tuple[int, dict[str, object]]:
241
246
  "thinking": {
242
247
  "enabled": settings.model.thinking_enabled,
243
248
  "reasoning_effort": settings.model.reasoning_effort,
249
+ "reasoning_mode": settings.model.reasoning_mode,
244
250
  },
245
251
  }
246
252
 
@@ -306,7 +312,7 @@ def _cmd_doctor(args: argparse.Namespace) -> int:
306
312
  status = "ok" if item["ok"] else "fail"
307
313
  print(f"{status:4} {item['name']}: {item['detail']}")
308
314
  thinking = report["thinking"]
309
- print(f"info thinking: enabled={thinking['enabled']} effort={thinking['reasoning_effort']}")
315
+ print(f"info reasoning: mode={thinking['reasoning_mode']}")
310
316
  live = report.get("live")
311
317
  if isinstance(live, dict):
312
318
  if live.get("ok"):
@@ -2,18 +2,27 @@ from __future__ import annotations
2
2
 
3
3
  from .settings import (
4
4
  ContextConfig,
5
+ DEEPSEEK_MODEL_CATALOG,
6
+ DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES,
7
+ DEFAULT_RESERVED_CONTEXT_TOKENS,
5
8
  DEFAULT_UI_THEME,
6
9
  DEFAULT_WEB_SEARCH_SEARXNG_URL,
10
+ DeepSeekModelInfo,
7
11
  ModelConfig,
12
+ REASONING_MODES,
8
13
  Settings,
14
+ SUPPORTED_DEEPSEEK_MODELS,
9
15
  UI_THEME_OPTIONS,
10
16
  UI_THEMES,
11
17
  UiConfig,
12
18
  default_config_path,
19
+ is_supported_deepseek_model,
13
20
  is_valid_ui_theme,
21
+ is_valid_reasoning_mode,
14
22
  load_settings,
15
23
  mask_secret,
16
24
  settings_to_toml_dict,
25
+ update_config_model_settings,
17
26
  update_config_theme,
18
27
  ui_theme_from_selection,
19
28
  ui_theme_number,
@@ -22,18 +31,27 @@ from .settings import (
22
31
 
23
32
  __all__ = [
24
33
  "ContextConfig",
34
+ "DEEPSEEK_MODEL_CATALOG",
35
+ "DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES",
36
+ "DEFAULT_RESERVED_CONTEXT_TOKENS",
25
37
  "DEFAULT_UI_THEME",
26
38
  "DEFAULT_WEB_SEARCH_SEARXNG_URL",
39
+ "DeepSeekModelInfo",
27
40
  "ModelConfig",
41
+ "REASONING_MODES",
28
42
  "Settings",
43
+ "SUPPORTED_DEEPSEEK_MODELS",
29
44
  "UI_THEME_OPTIONS",
30
45
  "UI_THEMES",
31
46
  "UiConfig",
32
47
  "default_config_path",
48
+ "is_supported_deepseek_model",
33
49
  "is_valid_ui_theme",
50
+ "is_valid_reasoning_mode",
34
51
  "load_settings",
35
52
  "mask_secret",
36
53
  "settings_to_toml_dict",
54
+ "update_config_model_settings",
37
55
  "update_config_theme",
38
56
  "ui_theme_from_selection",
39
57
  "ui_theme_number",
@@ -12,14 +12,40 @@ DEFAULT_MODEL = "deepseek-v4-pro"
12
12
  DEFAULT_BASE_URL = "https://api.deepseek.com"
13
13
  DEFAULT_CONTEXT_WINDOW_TOKENS = 1_048_576
14
14
  DEFAULT_COMPACT_TRIGGER_RATIO = 0.8
15
- DEFAULT_COMPACT_PROMPT_TOKEN_THRESHOLD = 838_861
15
+ DEFAULT_RESERVED_CONTEXT_TOKENS = 50_000
16
+ DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES = 2
16
17
  DEFAULT_WEB_SEARCH_SEARXNG_URL = "https://s.kirineko.tech/"
17
18
  DEFAULT_UI_THEME = "auto"
18
19
  REASONING_EFFORTS = {"high", "max"}
20
+ REASONING_MODES = {"none", "high", "max"}
19
21
  UI_THEMES = {"auto", "dark", "light"}
20
22
  UI_THEME_OPTIONS = (("1", "auto"), ("2", "dark"), ("3", "light"))
21
23
 
22
24
 
25
+ @dataclass(frozen=True)
26
+ class DeepSeekModelInfo:
27
+ name: str
28
+ label: str
29
+ description: str
30
+ supports_thinking: bool = True
31
+ default_reasoning_mode: str = "max"
32
+
33
+
34
+ DEEPSEEK_MODEL_CATALOG = (
35
+ DeepSeekModelInfo(
36
+ name="deepseek-v4-pro",
37
+ label="DeepSeek V4 Pro",
38
+ description="Higher quality for agentic coding and complex reasoning.",
39
+ ),
40
+ DeepSeekModelInfo(
41
+ name="deepseek-v4-flash",
42
+ label="DeepSeek V4 Flash",
43
+ description="Lower latency and cost for faster everyday turns.",
44
+ ),
45
+ )
46
+ SUPPORTED_DEEPSEEK_MODELS = frozenset(model.name for model in DEEPSEEK_MODEL_CATALOG)
47
+
48
+
23
49
  def default_config_path() -> Path:
24
50
  return Path.home() / ".deepy" / "config.toml"
25
51
 
@@ -96,14 +122,22 @@ class ModelConfig:
96
122
  def thinking_enabled(self) -> bool:
97
123
  if self.thinking is not None:
98
124
  return self.thinking
99
- return self.name.lower() in {"deepseek-v4-pro", "deepseek-v4-flash"}
125
+ return self.name.lower() in SUPPORTED_DEEPSEEK_MODELS
126
+
127
+ @property
128
+ def reasoning_mode(self) -> str:
129
+ if not self.thinking_enabled:
130
+ return "none"
131
+ return self.reasoning_effort if self.reasoning_effort in REASONING_EFFORTS else "max"
100
132
 
101
133
 
102
134
  @dataclass(frozen=True)
103
135
  class ContextConfig:
104
136
  window_tokens: int = DEFAULT_CONTEXT_WINDOW_TOKENS
105
137
  compact_trigger_ratio: float = DEFAULT_COMPACT_TRIGGER_RATIO
106
- compact_prompt_token_threshold: int | None = None
138
+ reserved_context_tokens: int = DEFAULT_RESERVED_CONTEXT_TOKENS
139
+ compact_preserve_recent_messages: int = DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES
140
+ compact_preserve_recent_tokens: int | None = None
107
141
 
108
142
  @classmethod
109
143
  def from_mapping(cls, raw: Mapping[str, Any]) -> Self:
@@ -111,18 +145,30 @@ class ContextConfig:
111
145
  ratio = _as_float(raw.get("compact_trigger_ratio"), DEFAULT_COMPACT_TRIGGER_RATIO)
112
146
  if ratio <= 0 or ratio > 1:
113
147
  ratio = DEFAULT_COMPACT_TRIGGER_RATIO
114
- threshold = raw.get("compact_prompt_token_threshold")
115
- compact_threshold = _as_int(threshold, 0) if threshold is not None else None
148
+ reserved_context_tokens = _as_int(
149
+ raw.get("reserved_context_tokens"),
150
+ DEFAULT_RESERVED_CONTEXT_TOKENS,
151
+ )
152
+ preserve_recent_messages = _as_int(
153
+ raw.get("compact_preserve_recent_messages"),
154
+ DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES,
155
+ )
156
+ preserve_recent_tokens_raw = raw.get("compact_preserve_recent_tokens")
157
+ preserve_recent_tokens = (
158
+ _as_int(preserve_recent_tokens_raw, 0)
159
+ if preserve_recent_tokens_raw is not None
160
+ else None
161
+ )
116
162
  return cls(
117
163
  window_tokens=window_tokens,
118
164
  compact_trigger_ratio=ratio,
119
- compact_prompt_token_threshold=compact_threshold or None,
165
+ reserved_context_tokens=reserved_context_tokens,
166
+ compact_preserve_recent_messages=preserve_recent_messages,
167
+ compact_preserve_recent_tokens=preserve_recent_tokens or None,
120
168
  )
121
169
 
122
170
  @property
123
171
  def resolved_compact_threshold(self) -> int:
124
- if self.compact_prompt_token_threshold:
125
- return self.compact_prompt_token_threshold
126
172
  return int(self.window_tokens * self.compact_trigger_ratio + 0.999999)
127
173
 
128
174
 
@@ -234,9 +280,6 @@ def settings_to_toml_dict(settings: Settings, *, reveal_secret: bool = False) ->
234
280
  if api_key:
235
281
  data["model"]["api_key"] = api_key if reveal_secret else mask_secret(api_key)
236
282
  data["model"]["thinking"] = settings.model.thinking_enabled
237
- data["context"]["compact_prompt_token_threshold"] = (
238
- settings.context.resolved_compact_threshold
239
- )
240
283
  return _drop_empty(data)
241
284
 
242
285
 
@@ -244,6 +287,14 @@ def is_valid_ui_theme(value: str) -> bool:
244
287
  return value in UI_THEMES
245
288
 
246
289
 
290
+ def is_supported_deepseek_model(value: str) -> bool:
291
+ return value in SUPPORTED_DEEPSEEK_MODELS
292
+
293
+
294
+ def is_valid_reasoning_mode(value: str) -> bool:
295
+ return value in REASONING_MODES
296
+
297
+
247
298
  def ui_theme_number(theme: str) -> str:
248
299
  for number, value in UI_THEME_OPTIONS:
249
300
  if value == theme:
@@ -288,7 +339,8 @@ def write_config(
288
339
  "context": {
289
340
  "window_tokens": DEFAULT_CONTEXT_WINDOW_TOKENS,
290
341
  "compact_trigger_ratio": DEFAULT_COMPACT_TRIGGER_RATIO,
291
- "compact_prompt_token_threshold": DEFAULT_COMPACT_PROMPT_TOKEN_THRESHOLD,
342
+ "reserved_context_tokens": DEFAULT_RESERVED_CONTEXT_TOKENS,
343
+ "compact_preserve_recent_messages": DEFAULT_COMPACT_PRESERVE_RECENT_MESSAGES,
292
344
  },
293
345
  "logging": {
294
346
  "debug": False,
@@ -311,23 +363,59 @@ def write_config(
311
363
  os.chmod(path, 0o600)
312
364
 
313
365
 
366
+ def update_config_model_settings(
367
+ config_path: Path,
368
+ *,
369
+ model: str | None = None,
370
+ reasoning_mode: str | None = None,
371
+ ) -> None:
372
+ if model is not None and not is_supported_deepseek_model(model):
373
+ raise ValueError(
374
+ "Model must be one of: " + ", ".join(model_info.name for model_info in DEEPSEEK_MODEL_CATALOG)
375
+ )
376
+ if reasoning_mode is not None and not is_valid_reasoning_mode(reasoning_mode):
377
+ raise ValueError("Reasoning mode must be one of: none, high, max.")
378
+ path = config_path.expanduser()
379
+ if path.suffix == ".json":
380
+ raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
381
+ raw = _read_toml_mapping(path)
382
+ model_section = raw.get("model")
383
+ model_map = dict(model_section) if isinstance(model_section, Mapping) else {}
384
+ if model is not None:
385
+ model_map["name"] = model
386
+ if reasoning_mode is not None:
387
+ if reasoning_mode == "none":
388
+ model_map["thinking"] = False
389
+ else:
390
+ model_map["thinking"] = True
391
+ model_map["reasoning_effort"] = reasoning_mode
392
+ raw["model"] = model_map
393
+ _write_private_toml(path, raw)
394
+
395
+
314
396
  def update_config_theme(config_path: Path, theme: str) -> None:
315
397
  if not is_valid_ui_theme(theme):
316
398
  raise ValueError("UI theme must be one of: auto, dark, light.")
317
399
  path = config_path.expanduser()
318
400
  if path.suffix == ".json":
319
401
  raise ValueError("Deepy only supports TOML config files; JSON config is not supported.")
320
- raw: dict[str, Any]
321
- if path.exists():
322
- with path.open("rb") as fh:
323
- loaded = tomllib.load(fh)
324
- raw = dict(loaded)
325
- else:
326
- raw = {}
402
+ raw = _read_toml_mapping(path)
327
403
  ui = raw.get("ui")
328
404
  ui_map = dict(ui) if isinstance(ui, Mapping) else {}
329
405
  ui_map["theme"] = theme
330
406
  raw["ui"] = ui_map
407
+ _write_private_toml(path, raw)
408
+
409
+
410
+ def _read_toml_mapping(path: Path) -> dict[str, Any]:
411
+ if not path.exists():
412
+ return {}
413
+ with path.open("rb") as fh:
414
+ loaded = tomllib.load(fh)
415
+ return dict(loaded)
416
+
417
+
418
+ def _write_private_toml(path: Path, raw: Mapping[str, Any]) -> None:
331
419
  path.parent.mkdir(parents=True, exist_ok=True)
332
420
  path.write_text(tomli_w.dumps(raw), encoding="utf-8")
333
421
  os.chmod(path, 0o600)
@@ -1,6 +1,8 @@
1
1
  ## AskUserQuestion
2
2
 
3
- Ask the user only when progress is blocked by missing intent or a required decision.
3
+ Ask the user when clarification would materially improve the result: ambiguous intent,
4
+ unclear scope, user preferences, high-impact trade-offs, or required approval. Do not
5
+ ask for low-impact details when a reasonable assumption can keep progress moving.
4
6
 
5
7
  Args: `questions` (non-empty array). Each question needs `question` and non-empty `options`;
6
8
  each option needs `label` and may include `description`. Use `multiSelect=true` only when
@@ -5,5 +5,6 @@ Fetch a specific web page when the user provides a complete URL.
5
5
  Args: `url`.
6
6
 
7
7
  Accepts only complete `http://` or `https://` URLs. Returns the final URL, title,
8
- content type, and extracted readable text for HTML pages. Use `WebSearch` to
8
+ content type, and extracted readable text for HTML pages, including standard
9
+ description metadata when ordinary body text is unavailable. Use `WebSearch` to
9
10
  discover URLs; use `WebFetch` when the URL is already known.
@@ -0,0 +1,13 @@
1
+ ## shell
2
+
3
+ Run commands in the detected runtime shell for inspection, tests, builds, and
4
+ project operations.
5
+
6
+ Args: `command`, optional `timeout_ms`.
7
+
8
+ Use the runtime context's command dialect and path style: PowerShell uses
9
+ PowerShell commands and Windows paths, `cmd` uses cmd syntax, and `posix` uses
10
+ POSIX shell syntax.
11
+
12
+ Runs in the session cwd, preserves cwd between calls when supported, and returns
13
+ stdout/stderr JSON with cwd, exit-code, and shell metadata.
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from .context import build_session_input_callback
3
+ from .context import build_session_input_callback, should_auto_compact
4
4
  from .events import DeepyStreamEvent, normalize_stream_event
5
5
  from .thinking import build_model_settings, build_thinking_extra_body
6
6
 
@@ -10,4 +10,5 @@ __all__ = [
10
10
  "build_session_input_callback",
11
11
  "build_thinking_extra_body",
12
12
  "normalize_stream_event",
13
+ "should_auto_compact",
13
14
  ]