code-puppy 0.0.204__py3-none-any.whl → 0.0.205__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.
@@ -13,6 +13,10 @@ from pydantic_ai import BinaryContent, DocumentUrl, ImageUrl
13
13
 
14
14
  SUPPORTED_INLINE_SCHEMES = {"http", "https"}
15
15
 
16
+ # Maximum path length to consider - conservative limit to avoid OS errors
17
+ # Most OS have limits around 4096, but we set lower to catch garbage early
18
+ MAX_PATH_LENGTH = 1024
19
+
16
20
  # Allow common extensions people drag in the terminal.
17
21
  DEFAULT_ACCEPTED_IMAGE_EXTENSIONS = {
18
22
  ".png",
@@ -61,6 +65,9 @@ def _is_probable_path(token: str) -> bool:
61
65
 
62
66
  if not token:
63
67
  return False
68
+ # Reject absurdly long tokens before any processing to avoid OS errors
69
+ if len(token) > MAX_PATH_LENGTH:
70
+ return False
64
71
  if token.startswith("#"):
65
72
  return False
66
73
  # Windows drive letters or Unix absolute/relative paths
@@ -69,7 +76,7 @@ def _is_probable_path(token: str) -> bool:
69
76
  if len(token) >= 2 and token[1] == ":":
70
77
  return True
71
78
  # Things like `path/to/file.png`
72
- return os.sep in token or "\"" in token
79
+ return os.sep in token or '"' in token
73
80
 
74
81
 
75
82
  def _unescape_dragged_path(token: str) -> str:
@@ -107,9 +114,13 @@ def _load_binary(path: Path) -> bytes:
107
114
  except FileNotFoundError as exc:
108
115
  raise AttachmentParsingError(f"Attachment not found: {path}") from exc
109
116
  except PermissionError as exc:
110
- raise AttachmentParsingError(f"Cannot read attachment (permission denied): {path}") from exc
117
+ raise AttachmentParsingError(
118
+ f"Cannot read attachment (permission denied): {path}"
119
+ ) from exc
111
120
  except OSError as exc:
112
- raise AttachmentParsingError(f"Failed to read attachment {path}: {exc}") from exc
121
+ raise AttachmentParsingError(
122
+ f"Failed to read attachment {path}: {exc}"
123
+ ) from exc
113
124
 
114
125
 
115
126
  def _tokenise(prompt: str) -> Iterable[str]:
@@ -147,7 +158,10 @@ def _candidate_paths(
147
158
 
148
159
  def _is_supported_extension(path: Path) -> bool:
149
160
  suffix = path.suffix.lower()
150
- return suffix in DEFAULT_ACCEPTED_IMAGE_EXTENSIONS | DEFAULT_ACCEPTED_DOCUMENT_EXTENSIONS
161
+ return (
162
+ suffix
163
+ in DEFAULT_ACCEPTED_IMAGE_EXTENSIONS | DEFAULT_ACCEPTED_DOCUMENT_EXTENSIONS
164
+ )
151
165
 
152
166
 
153
167
  def _parse_link(token: str) -> PromptLinkAttachment | None:
@@ -203,6 +217,11 @@ def _detect_path_tokens(prompt: str) -> tuple[list[_DetectedPath], list[str]]:
203
217
  index += 1
204
218
  continue
205
219
 
220
+ # Additional guard: skip if stripped token exceeds reasonable path length
221
+ if len(stripped_token) > MAX_PATH_LENGTH:
222
+ index += 1
223
+ continue
224
+
206
225
  start_index = index
207
226
  consumed_until = index + 1
208
227
  candidate_path_token = stripped_token
@@ -223,7 +242,16 @@ def _detect_path_tokens(prompt: str) -> tuple[list[_DetectedPath], list[str]]:
223
242
  index = consumed_until
224
243
  continue
225
244
 
226
- if not path.exists() or not path.is_file():
245
+ # Guard filesystem operations against OS errors (ENAMETOOLONG, etc.)
246
+ try:
247
+ path_exists = path.exists()
248
+ path_is_file = path.is_file() if path_exists else False
249
+ except OSError:
250
+ # Skip this token if filesystem check fails (path too long, etc.)
251
+ index = consumed_until
252
+ continue
253
+
254
+ if not path_exists or not path_is_file:
227
255
  found_span = False
228
256
  last_path = path
229
257
  for joined, end_index in _candidate_paths(tokens, index):
@@ -328,7 +356,8 @@ def parse_prompt_attachments(prompt: str) -> ProcessedPrompt:
328
356
  spans = [
329
357
  (d.start_index, d.consumed_until)
330
358
  for d in detections
331
- if (d.path is not None and not d.unsupported) or (d.link is not None and d.path is None)
359
+ if (d.path is not None and not d.unsupported)
360
+ or (d.link is not None and d.path is None)
332
361
  ]
333
362
  cleaned_parts: list[str] = []
334
363
  i = 0
@@ -360,4 +389,4 @@ __all__ = [
360
389
  "PromptLinkAttachment",
361
390
  "AttachmentParsingError",
362
391
  "parse_prompt_attachments",
363
- ]
392
+ ]
@@ -12,14 +12,20 @@ from typing import Optional
12
12
 
13
13
  from prompt_toolkit import PromptSession
14
14
  from prompt_toolkit.completion import Completer, Completion, merge_completers
15
+ from prompt_toolkit.filters import is_searching
15
16
  from prompt_toolkit.formatted_text import FormattedText
16
17
  from prompt_toolkit.history import FileHistory
17
- from prompt_toolkit.filters import is_searching
18
18
  from prompt_toolkit.key_binding import KeyBindings
19
19
  from prompt_toolkit.keys import Keys
20
20
  from prompt_toolkit.layout.processors import Processor, Transformation
21
21
  from prompt_toolkit.styles import Style
22
22
 
23
+ from code_puppy.command_line.attachments import (
24
+ DEFAULT_ACCEPTED_DOCUMENT_EXTENSIONS,
25
+ DEFAULT_ACCEPTED_IMAGE_EXTENSIONS,
26
+ _detect_path_tokens,
27
+ _tokenise,
28
+ )
23
29
  from code_puppy.command_line.file_path_completion import FilePathCompleter
24
30
  from code_puppy.command_line.load_context_completion import LoadContextCompleter
25
31
  from code_puppy.command_line.model_picker_completion import (
@@ -34,11 +40,6 @@ from code_puppy.config import (
34
40
  get_puppy_name,
35
41
  get_value,
36
42
  )
37
- from code_puppy.command_line.attachments import (
38
- DEFAULT_ACCEPTED_DOCUMENT_EXTENSIONS,
39
- DEFAULT_ACCEPTED_IMAGE_EXTENSIONS,
40
- _detect_path_tokens, _tokenise,
41
- )
42
43
 
43
44
 
44
45
  class SetCompleter(Completer):
@@ -108,6 +109,8 @@ class AttachmentPlaceholderProcessor(Processor):
108
109
  """Display friendly placeholders for recognised attachments."""
109
110
 
110
111
  _PLACEHOLDER_STYLE = "class:attachment-placeholder"
112
+ # Skip expensive path detection for very long input (likely pasted content)
113
+ _MAX_TEXT_LENGTH_FOR_REALTIME = 500
111
114
 
112
115
  def apply_transformation(self, transformation_input):
113
116
  document = transformation_input.document
@@ -115,6 +118,10 @@ class AttachmentPlaceholderProcessor(Processor):
115
118
  if not text:
116
119
  return Transformation(list(transformation_input.fragments))
117
120
 
121
+ # Skip real-time path detection for long text to avoid slowdown
122
+ if len(text) > self._MAX_TEXT_LENGTH_FOR_REALTIME:
123
+ return Transformation(list(transformation_input.fragments))
124
+
118
125
  detections, _warnings = _detect_path_tokens(text)
119
126
  replacements: list[tuple[int, int, str]] = []
120
127
  search_cursor = 0
@@ -138,7 +145,7 @@ class AttachmentPlaceholderProcessor(Processor):
138
145
  continue
139
146
 
140
147
  # Use token-span for robust lookup (handles escaped spaces)
141
- span_tokens = token_view[detection.start_index:detection.consumed_until]
148
+ span_tokens = token_view[detection.start_index : detection.consumed_until]
142
149
  raw_span = " ".join(span_tokens).replace(ESCAPE_MARKER, r"\ ")
143
150
  index = text.find(raw_span, search_cursor)
144
151
  span_len = len(raw_span)
@@ -188,7 +195,9 @@ class AttachmentPlaceholderProcessor(Processor):
188
195
  display_index += 1
189
196
 
190
197
  for _ in text[source_index:end]:
191
- source_to_display_map.append(placeholder_start if placeholder else display_index)
198
+ source_to_display_map.append(
199
+ placeholder_start if placeholder else display_index
200
+ )
192
201
  source_index += 1
193
202
 
194
203
  if source_index < len(text):
@@ -340,6 +349,7 @@ async def get_input_with_combined_completion(
340
349
 
341
350
  # Also allow Ctrl+Enter for newline (terminal-dependent)
342
351
  try:
352
+
343
353
  @bindings.add("c-enter", eager=True)
344
354
  def _(event):
345
355
  event.app.current_buffer.insert_text("\n")
code_puppy/models.json CHANGED
@@ -50,6 +50,11 @@
50
50
  "name": "claude-sonnet-4-5-20250929",
51
51
  "context_length": 200000
52
52
  },
53
+ "claude-4-1-opus": {
54
+ "type": "anthropic",
55
+ "name": "claude-opus-4-1-20250805",
56
+ "context_length": 200000
57
+ },
53
58
  "glm-4.5-coding": {
54
59
  "type": "zai_coding",
55
60
  "name": "glm-4.5"
@@ -50,6 +50,11 @@
50
50
  "name": "claude-sonnet-4-5-20250929",
51
51
  "context_length": 200000
52
52
  },
53
+ "claude-4-1-opus": {
54
+ "type": "anthropic",
55
+ "name": "claude-opus-4-1-20250805",
56
+ "context_length": 200000
57
+ },
53
58
  "glm-4.5-coding": {
54
59
  "type": "zai_coding",
55
60
  "name": "glm-4.5"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.204
3
+ Version: 0.0.205
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -5,7 +5,7 @@ code_puppy/config.py,sha256=xT-nU1U4n7u8pyzJPG18-cJZBKv5OZI2CtHLt9DGRzU,26065
5
5
  code_puppy/http_utils.py,sha256=YLd8Y16idbI32JGeBXG8n5rT4o4X_zxk9FgUvK9XFo8,8248
6
6
  code_puppy/main.py,sha256=SUh2UNbbEwVWSQwDkz-xBp80Q8qenX7tItsEEopcZfI,24024
7
7
  code_puppy/model_factory.py,sha256=ZbIAJWMNKNdTCEMQK8Ig6TDDZlVNyGO9hOLHoLLPMYw,15397
8
- code_puppy/models.json,sha256=dClUciCo2RlVDs0ZAQCIur8MOavZUEAXHEecn0uPa-4,1629
8
+ code_puppy/models.json,sha256=dppxeQ43J5aDl-5Ytr9Rq31tiEpcQndgDVTuF1Csr_g,1751
9
9
  code_puppy/reopenable_async_client.py,sha256=4UJRaMp5np8cbef9F0zKQ7TPKOfyf5U-Kv-0zYUWDho,8274
10
10
  code_puppy/round_robin_model.py,sha256=UEfw-Ix7GpNRWSxxuJtA-EE4_A46KXjMgFRciprfLmg,5634
11
11
  code_puppy/session_storage.py,sha256=Pf5C-qyC6xLhZCTlbAdkPwOFyqlaDomVnj9Z4cZcZsE,9595
@@ -30,13 +30,13 @@ code_puppy/agents/agent_typescript_reviewer.py,sha256=EDY1mFkVpuJ1BPXsJFu2wQ2pfA
30
30
  code_puppy/agents/base_agent.py,sha256=4hwqOCXQwL2F6luQsYn_IAGjboz1MTU2YIfHG6IYKnU,41409
31
31
  code_puppy/agents/json_agent.py,sha256=lhopDJDoiSGHvD8A6t50hi9ZBoNRKgUywfxd0Po_Dzc,4886
32
32
  code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
33
- code_puppy/command_line/attachments.py,sha256=rqBUR52wJj4cP-iTkN4KdoHZ9Oovc6tBp6ayifi9UF0,12022
33
+ code_puppy/command_line/attachments.py,sha256=eOf0zqBWnoAgC1FhWOkOyLjx_es0HGEFQ6EV1ZZuc1c,12934
34
34
  code_puppy/command_line/command_handler.py,sha256=alxMe5v_4jq8Sm6HETsgfF-VoDtgExj9dVzxP77fwmY,31614
35
35
  code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwoXKKn-yvCqit7h2y6Gg,2931
36
36
  code_puppy/command_line/load_context_completion.py,sha256=6eZxV6Bs-EFwZjN93V8ZDZUC-6RaWxvtZk-04Wtikyw,2240
37
37
  code_puppy/command_line/model_picker_completion.py,sha256=uqwpbMYnCcWUZZ10Y4pMBKBfW52wQ-KdML2PO4Xjwr0,4501
38
38
  code_puppy/command_line/motd.py,sha256=PEdkp3ZnydVfvd7mNJylm8YyFNUKg9jmY6uwkA1em8c,2152
39
- code_puppy/command_line/prompt_toolkit_completion.py,sha256=VZanuLcv1Py4sknTJWlJOQ6IqWqDjlHPoBcoeHEcPCA,15325
39
+ code_puppy/command_line/prompt_toolkit_completion.py,sha256=ce0VB2TeaqcbdXolRwk_kI19tKgX-1AROhJPBbfI10s,15694
40
40
  code_puppy/command_line/utils.py,sha256=7eyxDHjPjPB9wGDJQQcXV_zOsGdYsFgI0SGCetVmTqE,1251
41
41
  code_puppy/command_line/mcp/__init__.py,sha256=0-OQuwjq_pLiTVJ1_NrirVwdRerghyKs_MTZkwPC7YY,315
42
42
  code_puppy/command_line/mcp/add_command.py,sha256=lZ09RpFDIeghX1zhc2YIAqBASs5Ra52x5YAasUKvqJg,6409
@@ -123,9 +123,9 @@ code_puppy/tui/screens/help.py,sha256=eJuPaOOCp7ZSUlecearqsuX6caxWv7NQszUh0tZJjB
123
123
  code_puppy/tui/screens/mcp_install_wizard.py,sha256=vObpQwLbXjQsxmSg-WCasoev1usEi0pollKnL0SHu9U,27693
124
124
  code_puppy/tui/screens/settings.py,sha256=EoMxiguyeF0srwV1bj4_MG9rrxkNthh6TdTNsxnXLfE,11460
125
125
  code_puppy/tui/screens/tools.py,sha256=3pr2Xkpa9Js6Yhf1A3_wQVRzFOui-KDB82LwrsdBtyk,1715
126
- code_puppy-0.0.204.data/data/code_puppy/models.json,sha256=dClUciCo2RlVDs0ZAQCIur8MOavZUEAXHEecn0uPa-4,1629
127
- code_puppy-0.0.204.dist-info/METADATA,sha256=Fcbi95ybiGcekCuITFxE1_3YGaFJjdXoICpjpw4FNPQ,20759
128
- code_puppy-0.0.204.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
129
- code_puppy-0.0.204.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
130
- code_puppy-0.0.204.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
131
- code_puppy-0.0.204.dist-info/RECORD,,
126
+ code_puppy-0.0.205.data/data/code_puppy/models.json,sha256=dppxeQ43J5aDl-5Ytr9Rq31tiEpcQndgDVTuF1Csr_g,1751
127
+ code_puppy-0.0.205.dist-info/METADATA,sha256=k7rTavcaOz2Wc2VAf_Fy0dBaRuI0qQUXMAYK8Htumew,20759
128
+ code_puppy-0.0.205.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
129
+ code_puppy-0.0.205.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
130
+ code_puppy-0.0.205.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
131
+ code_puppy-0.0.205.dist-info/RECORD,,