code-puppy 0.0.204__tar.gz → 0.0.206__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 (130) hide show
  1. {code_puppy-0.0.204 → code_puppy-0.0.206}/PKG-INFO +2 -2
  2. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/attachments.py +36 -7
  3. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/prompt_toolkit_completion.py +18 -8
  4. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/config.py +22 -0
  5. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/http_utils.py +37 -6
  6. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/models.json +5 -0
  7. {code_puppy-0.0.204 → code_puppy-0.0.206}/pyproject.toml +2 -2
  8. {code_puppy-0.0.204 → code_puppy-0.0.206}/.gitignore +0 -0
  9. {code_puppy-0.0.204 → code_puppy-0.0.206}/LICENSE +0 -0
  10. {code_puppy-0.0.204 → code_puppy-0.0.206}/README.md +0 -0
  11. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/__init__.py +0 -0
  12. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/__main__.py +0 -0
  13. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/__init__.py +0 -0
  14. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_c_reviewer.py +0 -0
  15. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_code_puppy.py +0 -0
  16. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_code_reviewer.py +0 -0
  17. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  18. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_creator_agent.py +0 -0
  19. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  20. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  21. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_manager.py +0 -0
  22. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_python_reviewer.py +0 -0
  23. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_qa_expert.py +0 -0
  24. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_qa_kitten.py +0 -0
  25. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_security_auditor.py +0 -0
  26. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  27. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/base_agent.py +0 -0
  28. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/agents/json_agent.py +0 -0
  29. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/callbacks.py +0 -0
  30. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/__init__.py +0 -0
  31. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/command_handler.py +0 -0
  32. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/file_path_completion.py +0 -0
  33. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/load_context_completion.py +0 -0
  34. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/__init__.py +0 -0
  35. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/add_command.py +0 -0
  36. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/base.py +0 -0
  37. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/handler.py +0 -0
  38. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/help_command.py +0 -0
  39. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/install_command.py +0 -0
  40. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/list_command.py +0 -0
  41. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/logs_command.py +0 -0
  42. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/remove_command.py +0 -0
  43. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/restart_command.py +0 -0
  44. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/search_command.py +0 -0
  45. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  46. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/start_command.py +0 -0
  47. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/status_command.py +0 -0
  48. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  49. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/stop_command.py +0 -0
  50. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/test_command.py +0 -0
  51. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/utils.py +0 -0
  52. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  53. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/model_picker_completion.py +0 -0
  54. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/motd.py +0 -0
  55. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/command_line/utils.py +0 -0
  56. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/main.py +0 -0
  57. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/__init__.py +0 -0
  58. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/async_lifecycle.py +0 -0
  59. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/blocking_startup.py +0 -0
  60. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  61. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/circuit_breaker.py +0 -0
  62. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/config_wizard.py +0 -0
  63. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/dashboard.py +0 -0
  64. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/error_isolation.py +0 -0
  65. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/examples/retry_example.py +0 -0
  66. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/health_monitor.py +0 -0
  67. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/managed_server.py +0 -0
  68. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/manager.py +0 -0
  69. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/registry.py +0 -0
  70. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/retry_manager.py +0 -0
  71. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  72. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/status_tracker.py +0 -0
  73. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/mcp_/system_tools.py +0 -0
  74. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/messaging/__init__.py +0 -0
  75. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/messaging/message_queue.py +0 -0
  76. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/messaging/queue_console.py +0 -0
  77. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/messaging/renderers.py +0 -0
  78. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/messaging/spinner/__init__.py +0 -0
  79. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  80. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  81. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/messaging/spinner/textual_spinner.py +0 -0
  82. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/model_factory.py +0 -0
  83. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/plugins/__init__.py +0 -0
  84. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  85. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/reopenable_async_client.py +0 -0
  86. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/round_robin_model.py +0 -0
  87. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/session_storage.py +0 -0
  88. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/status_display.py +0 -0
  89. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/summarization_agent.py +0 -0
  90. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/__init__.py +0 -0
  91. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/agent_tools.py +0 -0
  92. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/browser/__init__.py +0 -0
  93. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/browser/browser_control.py +0 -0
  94. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/browser/browser_interactions.py +0 -0
  95. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/browser/browser_locators.py +0 -0
  96. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/browser/browser_navigation.py +0 -0
  97. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  98. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/browser/browser_scripts.py +0 -0
  99. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/browser/browser_workflows.py +0 -0
  100. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/browser/camoufox_manager.py +0 -0
  101. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/browser/vqa_agent.py +0 -0
  102. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/command_runner.py +0 -0
  103. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/common.py +0 -0
  104. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/file_modifications.py +0 -0
  105. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/file_operations.py +0 -0
  106. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tools/tools_content.py +0 -0
  107. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/__init__.py +0 -0
  108. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/app.py +0 -0
  109. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/components/__init__.py +0 -0
  110. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/components/chat_view.py +0 -0
  111. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/components/command_history_modal.py +0 -0
  112. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/components/copy_button.py +0 -0
  113. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/components/custom_widgets.py +0 -0
  114. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/components/human_input_modal.py +0 -0
  115. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/components/input_area.py +0 -0
  116. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/components/sidebar.py +0 -0
  117. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/components/status_bar.py +0 -0
  118. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/messages.py +0 -0
  119. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/models/__init__.py +0 -0
  120. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/models/chat_message.py +0 -0
  121. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/models/command_history.py +0 -0
  122. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/models/enums.py +0 -0
  123. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/screens/__init__.py +0 -0
  124. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/screens/autosave_picker.py +0 -0
  125. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/screens/help.py +0 -0
  126. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/screens/mcp_install_wizard.py +0 -0
  127. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/screens/settings.py +0 -0
  128. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui/screens/tools.py +0 -0
  129. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/tui_state.py +0 -0
  130. {code_puppy-0.0.204 → code_puppy-0.0.206}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.204
3
+ Version: 0.0.206
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
@@ -19,7 +19,7 @@ Requires-Dist: bs4>=0.0.2
19
19
  Requires-Dist: camoufox>=0.4.11
20
20
  Requires-Dist: fastapi>=0.110.0
21
21
  Requires-Dist: httpx-limiter>=0.3.0
22
- Requires-Dist: httpx>=0.24.1
22
+ Requires-Dist: httpx[http2]>=0.24.1
23
23
  Requires-Dist: json-repair>=0.46.2
24
24
  Requires-Dist: logfire>=0.7.1
25
25
  Requires-Dist: openai>=1.99.1
@@ -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")
@@ -132,6 +132,7 @@ def get_config_keys():
132
132
  "openai_reasoning_effort",
133
133
  "auto_save_session",
134
134
  "max_saved_sessions",
135
+ "http2",
135
136
  ]
136
137
  config = configparser.ConfigParser()
137
138
  config.read(CONFIG_FILE)
@@ -589,6 +590,27 @@ def get_compaction_strategy() -> str:
589
590
  return "truncation"
590
591
 
591
592
 
593
+ def get_http2() -> bool:
594
+ """
595
+ Get the http2 configuration value.
596
+ Returns False if not set (default).
597
+ """
598
+ val = get_value("http2")
599
+ if val is None:
600
+ return False
601
+ return str(val).lower() in ("1", "true", "yes", "on")
602
+
603
+
604
+ def set_http2(enabled: bool) -> None:
605
+ """
606
+ Sets the http2 configuration value.
607
+
608
+ Args:
609
+ enabled: Whether to enable HTTP/2 for httpx clients
610
+ """
611
+ set_config_value("http2", "true" if enabled else "false")
612
+
613
+
592
614
  def get_message_limit(default: int = 100) -> int:
593
615
  """
594
616
  Returns the user-configured message/request limit for the agent.
@@ -12,6 +12,8 @@ import httpx
12
12
  import requests
13
13
  from tenacity import stop_after_attempt, wait_exponential
14
14
 
15
+ from code_puppy.config import get_http2
16
+
15
17
  try:
16
18
  from pydantic_ai.retries import (
17
19
  AsyncTenacityTransport,
@@ -55,6 +57,9 @@ def create_client(
55
57
  if verify is None:
56
58
  verify = get_cert_bundle_path()
57
59
 
60
+ # Check if HTTP/2 is enabled in config
61
+ http2_enabled = get_http2()
62
+
58
63
  # If retry components are available, create a client with retry transport
59
64
  if TenacityTransport and RetryConfig and wait_retry_after:
60
65
 
@@ -81,11 +86,17 @@ def create_client(
81
86
  )
82
87
 
83
88
  return httpx.Client(
84
- transport=transport, verify=verify, headers=headers or {}, timeout=timeout
89
+ transport=transport,
90
+ verify=verify,
91
+ headers=headers or {},
92
+ timeout=timeout,
93
+ http2=http2_enabled,
85
94
  )
86
95
  else:
87
96
  # Fallback to regular client if retry components are not available
88
- return httpx.Client(verify=verify, headers=headers or {}, timeout=timeout)
97
+ return httpx.Client(
98
+ verify=verify, headers=headers or {}, timeout=timeout, http2=http2_enabled
99
+ )
89
100
 
90
101
 
91
102
  def create_async_client(
@@ -97,6 +108,9 @@ def create_async_client(
97
108
  if verify is None:
98
109
  verify = get_cert_bundle_path()
99
110
 
111
+ # Check if HTTP/2 is enabled in config
112
+ http2_enabled = get_http2()
113
+
100
114
  # If retry components are available, create a client with retry transport
101
115
  if AsyncTenacityTransport and RetryConfig and wait_retry_after:
102
116
 
@@ -120,11 +134,17 @@ def create_async_client(
120
134
  )
121
135
 
122
136
  return httpx.AsyncClient(
123
- transport=transport, verify=verify, headers=headers or {}, timeout=timeout
137
+ transport=transport,
138
+ verify=verify,
139
+ headers=headers or {},
140
+ timeout=timeout,
141
+ http2=http2_enabled,
124
142
  )
125
143
  else:
126
144
  # Fallback to regular client if retry components are not available
127
- return httpx.AsyncClient(verify=verify, headers=headers or {}, timeout=timeout)
145
+ return httpx.AsyncClient(
146
+ verify=verify, headers=headers or {}, timeout=timeout, http2=http2_enabled
147
+ )
128
148
 
129
149
 
130
150
  def create_requests_session(
@@ -176,6 +196,9 @@ def create_reopenable_async_client(
176
196
  if verify is None:
177
197
  verify = get_cert_bundle_path()
178
198
 
199
+ # Check if HTTP/2 is enabled in config
200
+ http2_enabled = get_http2()
201
+
179
202
  # If retry components are available, create a client with retry transport
180
203
  if AsyncTenacityTransport and RetryConfig and wait_retry_after:
181
204
 
@@ -207,6 +230,7 @@ def create_reopenable_async_client(
207
230
  verify=verify,
208
231
  headers=headers or {},
209
232
  timeout=timeout,
233
+ http2=http2_enabled,
210
234
  )
211
235
  else:
212
236
  # Fallback to regular AsyncClient if ReopenableAsyncClient is not available
@@ -215,17 +239,24 @@ def create_reopenable_async_client(
215
239
  verify=verify,
216
240
  headers=headers or {},
217
241
  timeout=timeout,
242
+ http2=http2_enabled,
218
243
  )
219
244
  else:
220
245
  # Fallback to regular clients if retry components are not available
221
246
  if ReopenableAsyncClient is not None:
222
247
  return ReopenableAsyncClient(
223
- verify=verify, headers=headers or {}, timeout=timeout
248
+ verify=verify,
249
+ headers=headers or {},
250
+ timeout=timeout,
251
+ http2=http2_enabled,
224
252
  )
225
253
  else:
226
254
  # Fallback to regular AsyncClient if ReopenableAsyncClient is not available
227
255
  return httpx.AsyncClient(
228
- verify=verify, headers=headers or {}, timeout=timeout
256
+ verify=verify,
257
+ headers=headers or {},
258
+ timeout=timeout,
259
+ http2=http2_enabled,
229
260
  )
230
261
 
231
262
 
@@ -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"
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.204"
7
+ version = "0.0.206"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
11
  dependencies = [
12
12
  "pydantic-ai==1.0.5",
13
- "httpx>=0.24.1",
13
+ "httpx[http2]>=0.24.1",
14
14
  "rich>=13.4.2",
15
15
  "logfire>=0.7.1",
16
16
  "pydantic>=2.4.0",
File without changes
File without changes
File without changes