code-puppy 0.0.318__py3-none-any.whl → 0.0.320__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.
@@ -1267,13 +1267,9 @@ class BaseAgent(ABC):
1267
1267
  ctx: The run context.
1268
1268
  events: Async iterable of streaming events (PartStartEvent, PartDeltaEvent, etc.).
1269
1269
  """
1270
- import os
1271
- import time as time_module
1272
-
1273
1270
  from pydantic_ai import PartDeltaEvent, PartStartEvent
1274
1271
  from pydantic_ai.messages import TextPartDelta, ThinkingPartDelta
1275
1272
  from rich.console import Console
1276
- from rich.live import Live
1277
1273
  from rich.markdown import Markdown
1278
1274
  from rich.markup import escape
1279
1275
 
@@ -1288,14 +1284,6 @@ class BaseAgent(ABC):
1288
1284
  # Fallback if console not set (shouldn't happen in normal use)
1289
1285
  console = Console()
1290
1286
 
1291
- # Disable Live display in test mode or non-interactive environments
1292
- # This fixes issues with pexpect PTY where Live() hangs
1293
- use_live_display = (
1294
- console.is_terminal
1295
- and os.environ.get("CODE_PUPPY_TEST_FAST", "").lower() not in ("1", "true")
1296
- and os.environ.get("CI", "").lower() not in ("1", "true")
1297
- )
1298
-
1299
1287
  # Track which part indices we're currently streaming (for Text/Thinking parts)
1300
1288
  streaming_parts: set[int] = set()
1301
1289
  thinking_parts: set[int] = (
@@ -1303,11 +1291,9 @@ class BaseAgent(ABC):
1303
1291
  ) # Track which parts are thinking (for dim style)
1304
1292
  text_parts: set[int] = set() # Track which parts are text
1305
1293
  banner_printed: set[int] = set() # Track if banner was already printed
1306
- text_buffer: dict[int, list[str]] = {} # Buffer text for markdown
1307
- live_displays: dict[int, Live] = {} # Live displays for streaming markdown
1294
+ text_buffer: dict[int, list[str]] = {} # Buffer text for final markdown render
1295
+ token_count: dict[int, int] = {} # Track token count per text part
1308
1296
  did_stream_anything = False # Track if we streamed any content
1309
- last_render_time: dict[int, float] = {} # Track last render time per part
1310
- render_interval = 0.1 # Only re-render markdown every 100ms (throttle)
1311
1297
 
1312
1298
  def _print_thinking_banner() -> None:
1313
1299
  """Print the THINKING banner with spinner pause and line clear."""
@@ -1372,9 +1358,11 @@ class BaseAgent(ABC):
1372
1358
  streaming_parts.add(event.index)
1373
1359
  text_parts.add(event.index)
1374
1360
  text_buffer[event.index] = [] # Initialize buffer
1361
+ token_count[event.index] = 0 # Initialize token counter
1375
1362
  # Buffer initial content if present
1376
1363
  if part.content and part.content.strip():
1377
1364
  text_buffer[event.index].append(part.content)
1365
+ token_count[event.index] += 1
1378
1366
 
1379
1367
  # PartDeltaEvent - stream the content as it arrives
1380
1368
  elif isinstance(event, PartDeltaEvent):
@@ -1382,43 +1370,23 @@ class BaseAgent(ABC):
1382
1370
  delta = event.delta
1383
1371
  if isinstance(delta, (TextPartDelta, ThinkingPartDelta)):
1384
1372
  if delta.content_delta:
1385
- # For text parts, stream markdown with Live display
1373
+ # For text parts, show token counter then render at end
1386
1374
  if event.index in text_parts:
1387
- # Print banner and start Live on first content
1375
+ import sys
1376
+
1377
+ # Print banner on first content
1388
1378
  if event.index not in banner_printed:
1389
1379
  _print_response_banner()
1390
1380
  banner_printed.add(event.index)
1391
- # Only use Live display if enabled (disabled in test/CI)
1392
- if use_live_display:
1393
- live = Live(
1394
- Markdown(""),
1395
- console=console,
1396
- refresh_per_second=10,
1397
- vertical_overflow="visible", # Allow scrolling for long content
1398
- )
1399
- live.start()
1400
- live_displays[event.index] = live
1401
- # Accumulate text and throttle markdown rendering
1402
- # (Markdown parsing is O(n), doing it on every token = O(n²) death)
1381
+ # Accumulate text for final markdown render
1403
1382
  text_buffer[event.index].append(delta.content_delta)
1404
- now = time_module.monotonic()
1405
- last_render = last_render_time.get(event.index, 0)
1406
-
1407
- # Only re-render if enough time has passed (throttle)
1408
- # Skip Live updates when not using live display
1409
- if (
1410
- use_live_display
1411
- and now - last_render >= render_interval
1412
- ):
1413
- content = "".join(text_buffer[event.index])
1414
- if event.index in live_displays:
1415
- try:
1416
- live_displays[event.index].update(
1417
- Markdown(content)
1418
- )
1419
- last_render_time[event.index] = now
1420
- except Exception:
1421
- pass
1383
+ token_count[event.index] += 1
1384
+ # Update token counter in place (single line)
1385
+ count = token_count[event.index]
1386
+ sys.stdout.write(
1387
+ f"\r\x1b[K ⏳ Receiving... {count} tokens"
1388
+ )
1389
+ sys.stdout.flush()
1422
1390
  else:
1423
1391
  # For thinking parts, stream immediately (dim)
1424
1392
  if event.index not in banner_printed:
@@ -1430,36 +1398,24 @@ class BaseAgent(ABC):
1430
1398
  # PartEndEvent - finish the streaming with a newline
1431
1399
  elif isinstance(event, PartEndEvent):
1432
1400
  if event.index in streaming_parts:
1433
- # For text parts, do final render then stop the Live display
1401
+ # For text parts, clear counter line and render markdown
1434
1402
  if event.index in text_parts:
1435
- # Final render to ensure we show complete content
1436
- # (throttling may have skipped the last few tokens)
1437
- if event.index in live_displays and event.index in text_buffer:
1438
- try:
1439
- final_content = "".join(text_buffer[event.index])
1440
- live_displays[event.index].update(
1441
- Markdown(final_content)
1442
- )
1443
- except Exception:
1444
- pass
1445
- if event.index in live_displays:
1446
- try:
1447
- live_displays[event.index].stop()
1448
- except Exception:
1449
- pass
1450
- del live_displays[event.index]
1451
- # When not using Live display, print the final content as markdown
1452
- elif event.index in text_buffer:
1403
+ import sys
1404
+
1405
+ # Clear the token counter line
1406
+ sys.stdout.write("\r\x1b[K")
1407
+ sys.stdout.flush()
1408
+ # Render the final markdown nicely
1409
+ if event.index in text_buffer:
1453
1410
  try:
1454
1411
  final_content = "".join(text_buffer[event.index])
1455
1412
  if final_content.strip():
1456
1413
  console.print(Markdown(final_content))
1457
1414
  except Exception:
1458
1415
  pass
1459
- if event.index in text_buffer:
1460
1416
  del text_buffer[event.index]
1461
- # Clean up render time tracking
1462
- last_render_time.pop(event.index, None)
1417
+ # Clean up token count
1418
+ token_count.pop(event.index, None)
1463
1419
  # For thinking parts, just print newline
1464
1420
  elif event.index in banner_printed:
1465
1421
  console.print() # Final newline after streaming
@@ -84,6 +84,12 @@ SETTING_DEFINITIONS: Dict[str, Dict] = {
84
84
  "default": 10000,
85
85
  "format": "{:.0f}",
86
86
  },
87
+ "interleaved_thinking": {
88
+ "name": "Interleaved Thinking",
89
+ "description": "Enable thinking between tool calls (Claude 4 only: Opus 4.5, Opus 4.1, Opus 4, Sonnet 4). Adds beta header. WARNING: On Vertex/Bedrock, this FAILS for non-Claude 4 models!",
90
+ "type": "boolean",
91
+ "default": False,
92
+ },
87
93
  }
88
94
 
89
95
 
@@ -319,9 +319,21 @@ class ModelFactory:
319
319
  http2=http2_enabled,
320
320
  )
321
321
 
322
+ # Check if interleaved thinking is enabled for this model
323
+ # Only applies to Claude 4 models (Opus 4.5, Opus 4.1, Opus 4, Sonnet 4)
324
+ from code_puppy.config import get_effective_model_settings
325
+
326
+ effective_settings = get_effective_model_settings(model_name)
327
+ interleaved_thinking = effective_settings.get("interleaved_thinking", False)
328
+
329
+ default_headers = {}
330
+ if interleaved_thinking:
331
+ default_headers["anthropic-beta"] = "interleaved-thinking-2025-05-14"
332
+
322
333
  anthropic_client = AsyncAnthropic(
323
334
  api_key=api_key,
324
335
  http_client=client,
336
+ default_headers=default_headers if default_headers else None,
325
337
  )
326
338
 
327
339
  # Ensure cache_control is injected at the Anthropic SDK layer
@@ -351,10 +363,21 @@ class ModelFactory:
351
363
  http2=http2_enabled,
352
364
  )
353
365
 
366
+ # Check if interleaved thinking is enabled for this model
367
+ from code_puppy.config import get_effective_model_settings
368
+
369
+ effective_settings = get_effective_model_settings(model_name)
370
+ interleaved_thinking = effective_settings.get("interleaved_thinking", False)
371
+
372
+ default_headers = {}
373
+ if interleaved_thinking:
374
+ default_headers["anthropic-beta"] = "interleaved-thinking-2025-05-14"
375
+
354
376
  anthropic_client = AsyncAnthropic(
355
377
  base_url=url,
356
378
  http_client=client,
357
379
  api_key=api_key,
380
+ default_headers=default_headers if default_headers else None,
358
381
  )
359
382
 
360
383
  # Ensure cache_control is injected at the Anthropic SDK layer
@@ -370,6 +393,31 @@ class ModelFactory:
370
393
  )
371
394
  return None
372
395
 
396
+ # Check if interleaved thinking is enabled (defaults to True for OAuth models)
397
+ from code_puppy.config import get_effective_model_settings
398
+
399
+ effective_settings = get_effective_model_settings(model_name)
400
+ interleaved_thinking = effective_settings.get("interleaved_thinking", True)
401
+
402
+ # Handle anthropic-beta header based on interleaved_thinking setting
403
+ if "anthropic-beta" in headers:
404
+ beta_parts = [p.strip() for p in headers["anthropic-beta"].split(",")]
405
+ if interleaved_thinking:
406
+ # Ensure interleaved-thinking is in the header
407
+ if "interleaved-thinking-2025-05-14" not in beta_parts:
408
+ beta_parts.append("interleaved-thinking-2025-05-14")
409
+ else:
410
+ # Remove interleaved-thinking from the header
411
+ beta_parts = [
412
+ p for p in beta_parts if "interleaved-thinking" not in p
413
+ ]
414
+ headers["anthropic-beta"] = ",".join(beta_parts) if beta_parts else None
415
+ if headers.get("anthropic-beta") is None:
416
+ del headers["anthropic-beta"]
417
+ elif interleaved_thinking:
418
+ # No existing beta header, add one for interleaved thinking
419
+ headers["anthropic-beta"] = "interleaved-thinking-2025-05-14"
420
+
373
421
  # Use a dedicated client wrapper that injects cache_control on /v1/messages
374
422
  if verify is None:
375
423
  verify = get_cert_bundle_path()
code_puppy/models.json CHANGED
@@ -81,7 +81,7 @@
81
81
  "type": "anthropic",
82
82
  "name": "claude-opus-4-5",
83
83
  "context_length": 200000,
84
- "supported_settings": ["temperature", "extended_thinking", "budget_tokens"]
84
+ "supported_settings": ["temperature", "extended_thinking", "budget_tokens", "interleaved_thinking"]
85
85
  },
86
86
  "zai-glm-4.6-coding": {
87
87
  "type": "zai_coding",
@@ -368,6 +368,7 @@ def add_models_to_extra_config(models: List[str]) -> bool:
368
368
  "temperature",
369
369
  "extended_thinking",
370
370
  "budget_tokens",
371
+ "interleaved_thinking",
371
372
  ],
372
373
  }
373
374
  added += 1
@@ -81,7 +81,7 @@
81
81
  "type": "anthropic",
82
82
  "name": "claude-opus-4-5",
83
83
  "context_length": 200000,
84
- "supported_settings": ["temperature", "extended_thinking", "budget_tokens"]
84
+ "supported_settings": ["temperature", "extended_thinking", "budget_tokens", "interleaved_thinking"]
85
85
  },
86
86
  "zai-glm-4.6-coding": {
87
87
  "type": "zai_coding",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.318
3
+ Version: 0.0.320
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
@@ -10,9 +10,9 @@ code_puppy/gemini_code_assist.py,sha256=KGS7sO5OLc83nDF3xxS-QiU6vxW9vcm6hmzilu79
10
10
  code_puppy/http_utils.py,sha256=w5mWYIGIWJZJvgvMahXs9BmdidoJvGn4CASDRY88a8o,13414
11
11
  code_puppy/keymap.py,sha256=Uzvq7HB-6inTjKox-90JWzuijztRdWqhJpfTDZVy5no,3235
12
12
  code_puppy/main.py,sha256=82r3vZy_XcyEsenLn82BnUusaoyL3Bpm_Th_jKgqecE,273
13
- code_puppy/model_factory.py,sha256=2jXTpi3BuFO8RtbhKKRvb5EN02w_OKzQSdsTdSy78X0,31608
13
+ code_puppy/model_factory.py,sha256=H_a5nX462Q-dhX3g3ZY7dmBCIAUOd1aOSZa4HMxF1o4,34191
14
14
  code_puppy/model_utils.py,sha256=NU8W8NW5F7QS_PXHaLeh55Air1koUV7IVYFP7Rz3XpY,3615
15
- code_puppy/models.json,sha256=nbMCW9SJxWptVEOeMrn_gfyDp-lsbM7Chczjl944GKA,3077
15
+ code_puppy/models.json,sha256=mTpmJH0UJlmX8M2KVPbxMWb99de3IxKXCWO-B23b6xo,3101
16
16
  code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
17
17
  code_puppy/models_dev_parser.py,sha256=8ndmWrsSyKbXXpRZPXc0w6TfWMuCcgaHiMifmlaBaPc,20611
18
18
  code_puppy/pydantic_patches.py,sha256=YecAEeCOjSIwIBu2O5vEw72atMSL37cXGrbEuukI07o,4582
@@ -39,7 +39,7 @@ code_puppy/agents/agent_qa_expert.py,sha256=5Ikb4U3SZQknUEfwlHZiyZXKqnffnOTQagr_
39
39
  code_puppy/agents/agent_qa_kitten.py,sha256=5PeFFSwCFlTUvP6h5bGntx0xv5NmRwBiw0HnMqY8nLI,9107
40
40
  code_puppy/agents/agent_security_auditor.py,sha256=SpiYNA0XAsIwBj7S2_EQPRslRUmF_-b89pIJyW7DYtY,12022
41
41
  code_puppy/agents/agent_typescript_reviewer.py,sha256=vsnpp98xg6cIoFAEJrRTUM_i4wLEWGm5nJxs6fhHobM,10275
42
- code_puppy/agents/base_agent.py,sha256=62TcOKownogs8KVcFGuhuxp0Xvj9VrsaqLNwz1t96zs,80331
42
+ code_puppy/agents/base_agent.py,sha256=sT5bNdF2c-SnSdy1pwfZKnv2P-jwB7i67h353UqUN20,77624
43
43
  code_puppy/agents/json_agent.py,sha256=lhopDJDoiSGHvD8A6t50hi9ZBoNRKgUywfxd0Po_Dzc,4886
44
44
  code_puppy/agents/prompt_reviewer.py,sha256=JJrJ0m5q0Puxl8vFsyhAbY9ftU9n6c6UxEVdNct1E-Q,5558
45
45
  code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
@@ -56,7 +56,7 @@ code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwo
56
56
  code_puppy/command_line/load_context_completion.py,sha256=a3JvLDeLLSYxVgTjAdqWzS4spjv6ccCrK2LKZgVJ1IM,2202
57
57
  code_puppy/command_line/mcp_completion.py,sha256=eKzW2O7gun7HoHekOW0XVXhNS5J2xCtK7aaWyA8bkZk,6952
58
58
  code_puppy/command_line/model_picker_completion.py,sha256=nDnlf0qFCG2zAm_mWW2eMYwVC7eROVQrFe92hZqOKa8,6810
59
- code_puppy/command_line/model_settings_menu.py,sha256=-GDJyvkFeEIs-fYTvkBPxyVjmMo9CJRrwUq_IuiEbqY,31935
59
+ code_puppy/command_line/model_settings_menu.py,sha256=O5nPp_OyShFcXzpSmsCeYsnnVNrSwcTBFY9bzcayvj0,32263
60
60
  code_puppy/command_line/motd.py,sha256=OoNxwewsckexSgJ5H5y40IawP-TzqlqY-rqFUdRbIhs,2186
61
61
  code_puppy/command_line/pin_command_completion.py,sha256=juSvdqRpk7AdfkPy1DJx5NzfEUU5KYGlChvP0hisM18,11667
62
62
  code_puppy/command_line/prompt_toolkit_completion.py,sha256=x4Of32g8oH9ckhx-P6BigV7HUUhhjL8xkvK03uq9HRw,27308
@@ -130,7 +130,7 @@ code_puppy/plugins/claude_code_oauth/__init__.py,sha256=mCcOU-wM7LNCDjr-w-WLPzom
130
130
  code_puppy/plugins/claude_code_oauth/config.py,sha256=DjGySCkvjSGZds6DYErLMAi3TItt8iSLGvyJN98nSEM,2013
131
131
  code_puppy/plugins/claude_code_oauth/register_callbacks.py,sha256=0NeX1hhkYIlVfPmjZ1xmcf1yueDAJh_FMUmvJlxSO-E,10057
132
132
  code_puppy/plugins/claude_code_oauth/test_plugin.py,sha256=yQy4EeZl4bjrcog1d8BjknoDTRK75mRXXvkSQJYSSEM,9286
133
- code_puppy/plugins/claude_code_oauth/utils.py,sha256=uxNRrvtmyG_zZxcvCyZIU1fib8wV5KeorHgVv0RWS9s,13394
133
+ code_puppy/plugins/claude_code_oauth/utils.py,sha256=wDaOU21zB3y6PWkuMXwE4mFjQuffyDae-vXysPTS-w8,13438
134
134
  code_puppy/plugins/customizable_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
135
135
  code_puppy/plugins/customizable_commands/register_callbacks.py,sha256=zVMfIzr--hVn0IOXxIicbmgj2s-HZUgtrOc0NCDOnDw,5183
136
136
  code_puppy/plugins/example_custom_command/README.md,sha256=5c5Zkm7CW6BDSfe3WoLU7GW6t5mjjYAbu9-_pu-b3p4,8244
@@ -159,10 +159,10 @@ code_puppy/tools/browser/browser_scripts.py,sha256=sNb8eLEyzhasy5hV4B9OjM8yIVMLV
159
159
  code_puppy/tools/browser/browser_workflows.py,sha256=nitW42vCf0ieTX1gLabozTugNQ8phtoFzZbiAhw1V90,6491
160
160
  code_puppy/tools/browser/camoufox_manager.py,sha256=RZjGOEftE5sI_tsercUyXFSZI2wpStXf-q0PdYh2G3I,8680
161
161
  code_puppy/tools/browser/vqa_agent.py,sha256=DBn9HKloILqJSTSdNZzH_PYWT0B2h9VwmY6akFQI_uU,2913
162
- code_puppy-0.0.318.data/data/code_puppy/models.json,sha256=nbMCW9SJxWptVEOeMrn_gfyDp-lsbM7Chczjl944GKA,3077
163
- code_puppy-0.0.318.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
164
- code_puppy-0.0.318.dist-info/METADATA,sha256=_7lhagjL8fQpd7zBbl8DQ1Z9qmHGRY2gbQ7E9jwNp54,28030
165
- code_puppy-0.0.318.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
166
- code_puppy-0.0.318.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
167
- code_puppy-0.0.318.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
168
- code_puppy-0.0.318.dist-info/RECORD,,
162
+ code_puppy-0.0.320.data/data/code_puppy/models.json,sha256=mTpmJH0UJlmX8M2KVPbxMWb99de3IxKXCWO-B23b6xo,3101
163
+ code_puppy-0.0.320.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
164
+ code_puppy-0.0.320.dist-info/METADATA,sha256=Ah_-U-dxX8DnEHjQ0G8--g_1lDfyI3WMGcjnJOEfiBk,28030
165
+ code_puppy-0.0.320.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
166
+ code_puppy-0.0.320.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
167
+ code_puppy-0.0.320.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
168
+ code_puppy-0.0.320.dist-info/RECORD,,