alpiecode 10.6.6__tar.gz → 10.6.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 (58) hide show
  1. {alpiecode-10.6.6 → alpiecode-10.6.7}/PKG-INFO +1 -1
  2. {alpiecode-10.6.6 → alpiecode-10.6.7}/pyproject.toml +1 -1
  3. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/PKG-INFO +1 -1
  4. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/SOURCES.txt +1 -0
  5. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/__init__.py +1 -1
  6. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/agent.py +12 -6
  7. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/backends/openai_backend.py +53 -6
  8. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/cli.py +3 -0
  9. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/config.py +1 -0
  10. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/orchestrator.py +8 -3
  11. alpiecode-10.6.7/test/test_non_thinking_and_local_stream.py +70 -0
  12. {alpiecode-10.6.6 → alpiecode-10.6.7}/README.md +0 -0
  13. {alpiecode-10.6.6 → alpiecode-10.6.7}/setup.cfg +0 -0
  14. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode/__init__.py +0 -0
  15. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/dependency_links.txt +0 -0
  16. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/entry_points.txt +0 -0
  17. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/requires.txt +0 -0
  18. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/top_level.txt +0 -0
  19. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/assessment.py +0 -0
  20. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/backends/__init__.py +0 -0
  21. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/backends/base.py +0 -0
  22. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/backends/local_backend.py +0 -0
  23. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/cache.py +0 -0
  24. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/client.py +0 -0
  25. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/compaction.py +0 -0
  26. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/context.py +0 -0
  27. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/discovery.py +0 -0
  28. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/doctor.py +0 -0
  29. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/dream_rsi.py +0 -0
  30. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/executor.py +0 -0
  31. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/extension/alpiecode.vsix +0 -0
  32. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/git_ops.py +0 -0
  33. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/github.py +0 -0
  34. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/guardian.py +0 -0
  35. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/guardrails.py +0 -0
  36. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/ipython_ext.py +0 -0
  37. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/local_model.py +0 -0
  38. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/media.py +0 -0
  39. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/memory.py +0 -0
  40. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/metrics.py +0 -0
  41. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/paths.py +0 -0
  42. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/progress.py +0 -0
  43. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/prompt.py +0 -0
  44. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/rephraser.py +0 -0
  45. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/server.py +0 -0
  46. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/session.py +0 -0
  47. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/system1.py +0 -0
  48. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/tools.py +0 -0
  49. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/updater.py +0 -0
  50. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/verifier.py +0 -0
  51. {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/vscode_installer.py +0 -0
  52. {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_adaptive_resilience.py +0 -0
  53. {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_cli_ui.py +0 -0
  54. {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_context_and_trajectory.py +0 -0
  55. {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_dream_rsi.py +0 -0
  56. {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_local_model_download.py +0 -0
  57. {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_task_assessment.py +0 -0
  58. {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_verifier.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 10.6.6
3
+ Version: 10.6.7
4
4
  Summary: AlpieCode
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "alpiecode"
7
- version = "10.6.6"
7
+ version = "10.6.7"
8
8
  description = "AlpieCode"
9
9
  requires-python = ">=3.9"
10
10
  dependencies = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 10.6.6
3
+ Version: 10.6.7
4
4
  Summary: AlpieCode
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
@@ -51,5 +51,6 @@ test/test_cli_ui.py
51
51
  test/test_context_and_trajectory.py
52
52
  test/test_dream_rsi.py
53
53
  test/test_local_model_download.py
54
+ test/test_non_thinking_and_local_stream.py
54
55
  test/test_task_assessment.py
55
56
  test/test_verifier.py
@@ -17,7 +17,7 @@ Supports:
17
17
  from pathlib import Path
18
18
  from typing import Optional, Any, Dict
19
19
 
20
- __version__ = "10.6.6"
20
+ __version__ = "10.6.7"
21
21
 
22
22
  def run(task: str, workdir: str = ".", reasoning_level: str = "thinking", **kwargs):
23
23
  """Run an autonomous coding task programmatically."""
@@ -376,12 +376,16 @@ def run_agent(
376
376
  else:
377
377
  status_spinner.update("Reasoning through implementation...", style="bold magenta")
378
378
 
379
- elif event.type == "thinking_delta" and verbose and debug:
380
- status_spinner.pause()
379
+ elif event.type == "thinking_delta" and verbose:
381
380
  delta = event.data.get("delta", "")
382
- if delta:
383
- sys.stdout.write(delta)
384
- sys.stdout.flush()
381
+ count = event.data.get("count", 0)
382
+ if debug:
383
+ if delta:
384
+ status_spinner.pause()
385
+ sys.stdout.write(delta)
386
+ sys.stdout.flush()
387
+ elif count > 0 and count % 15 == 0:
388
+ status_spinner.update(f"Reasoning through implementation ({count} tokens)...", style="bold magenta")
385
389
 
386
390
  elif event.type == "thinking_end" and verbose:
387
391
  duration = event.data.get("duration", 0)
@@ -504,7 +508,9 @@ def run_agent(
504
508
  else:
505
509
  print(f"── Step {current_turn} ──")
506
510
  if current_turn > 0:
507
- status_spinner.resume(f"Step {current_turn}: Thinking...", style="bold cyan")
511
+ is_turn_thinking = event.data.get("thinking", True)
512
+ turn_label = "Thinking..." if is_turn_thinking else "Working..."
513
+ status_spinner.resume(f"Step {current_turn}: {turn_label}", style="bold cyan")
508
514
 
509
515
  elif event.type == "compaction" and verbose:
510
516
  status_spinner.pause()
@@ -175,7 +175,16 @@ class _HTTPXClient:
175
175
  def __init__(self, base_url: str, api_key: str, http_client: Any = None):
176
176
  self.base_url = base_url
177
177
  self.api_key = api_key or "not-needed"
178
- self.http_client = http_client if http_client is not None else get_shared_http_client()
178
+ if http_client is not None:
179
+ self.http_client = http_client
180
+ elif "127.0.0.1" in base_url or "localhost" in base_url:
181
+ self.http_client = httpx.Client(
182
+ http2=False,
183
+ timeout=httpx.Timeout(180.0, connect=5.0, read=180.0, write=30.0),
184
+ limits=httpx.Limits(max_keepalive_connections=5, max_connections=10),
185
+ )
186
+ else:
187
+ self.http_client = get_shared_http_client()
179
188
  self.chat = _Namespace(completions=_HTTPXChatCompletions(self))
180
189
  self.models = _HTTPXModels(self)
181
190
 
@@ -397,6 +406,17 @@ class OpenAIBackend:
397
406
  except ValueError:
398
407
  pass
399
408
 
409
+ # For local endpoints (e.g. llama-server on Android), instruct model to skip <think> in fast mode
410
+ if not enable_thinking and ("127.0.0.1" in getattr(self._cfg, "base_url", "") or "localhost" in getattr(self._cfg, "base_url", "")):
411
+ clean_messages = []
412
+ for m in messages:
413
+ clean_messages.append(dict(m))
414
+ if clean_messages and clean_messages[-1].get("role") == "user":
415
+ u_text = str(clean_messages[-1].get("content", ""))
416
+ if "[Direct Mode:" not in u_text:
417
+ clean_messages[-1]["content"] = u_text + "\n\n[Direct Mode: Output code and tool calls directly without <think> tags. Do not deliberate.]"
418
+ messages = clean_messages
419
+
400
420
  params = {
401
421
  "model": model_name,
402
422
  "messages": messages,
@@ -419,6 +439,8 @@ class OpenAIBackend:
419
439
  response = client.chat.completions.create(**params)
420
440
 
421
441
  in_think = enable_thinking
442
+ suppress_think = not enable_thinking
443
+ inside_suppressed_think = False
422
444
  think_started = False
423
445
  full_content = []
424
446
  full_reasoning = []
@@ -472,16 +494,38 @@ class OpenAIBackend:
472
494
 
473
495
  # Explicit reasoning field from some models
474
496
  explicit_reasoning = getattr(delta, "reasoning", None) or getattr(delta, "reasoning_content", None)
475
- if explicit_reasoning:
497
+ if explicit_reasoning and enable_thinking:
476
498
  if not think_started:
477
499
  think_started = True
478
500
  yield ("thinking_start", {"time": start_time})
479
501
  full_reasoning.append(explicit_reasoning)
480
- yield ("thinking_delta", {"delta": explicit_reasoning})
502
+ yield ("thinking_delta", {"delta": explicit_reasoning, "count": len(full_reasoning)})
481
503
 
482
504
  # Content deltas (contains <think>...</think> on vLLM/Qwen models)
483
505
  if delta.content:
484
506
  text = delta.content
507
+ if suppress_think:
508
+ # In non-thinking mode, swallow any think tags/blocks emitted by local models
509
+ if "<think>" in text:
510
+ inside_suppressed_think = True
511
+ text = text.replace("<think>", "")
512
+ if inside_suppressed_think:
513
+ if "<tool_call>" in text or "DONE:" in text or "<function=" in text:
514
+ inside_suppressed_think = False
515
+ elif "</think>" in text:
516
+ _, after = text.split("</think>", 1)
517
+ inside_suppressed_think = False
518
+ text = after
519
+ else:
520
+ text = ""
521
+ else:
522
+ text = text.replace("</think>", "")
523
+ if not text:
524
+ continue
525
+ full_content.append(text)
526
+ yield ("token", {"delta": text})
527
+ continue
528
+
485
529
  if in_think:
486
530
  if not think_started:
487
531
  think_started = True
@@ -491,7 +535,7 @@ class OpenAIBackend:
491
535
  before, after = text.split("</think>", 1)
492
536
  if before:
493
537
  full_reasoning.append(before)
494
- yield ("thinking_delta", {"delta": before})
538
+ yield ("thinking_delta", {"delta": before, "count": len(full_reasoning)})
495
539
  in_think = False
496
540
  elapsed = time.time() - start_time
497
541
  yield ("thinking_end", {"duration": round(elapsed, 1), "content": "".join(full_reasoning).strip()})
@@ -503,7 +547,7 @@ class OpenAIBackend:
503
547
  before, after = text.split("DONE:", 1)
504
548
  if before:
505
549
  full_reasoning.append(before)
506
- yield ("thinking_delta", {"delta": before})
550
+ yield ("thinking_delta", {"delta": before, "count": len(full_reasoning)})
507
551
  in_think = False
508
552
  elapsed = time.time() - start_time
509
553
  yield ("thinking_end", {"duration": round(elapsed, 1), "content": "".join(full_reasoning).strip()})
@@ -513,7 +557,7 @@ class OpenAIBackend:
513
557
  else:
514
558
  clean_text = text.replace("<think>", "")
515
559
  full_reasoning.append(clean_text)
516
- yield ("thinking_delta", {"delta": clean_text})
560
+ yield ("thinking_delta", {"delta": clean_text, "count": len(full_reasoning)})
517
561
  else:
518
562
  full_content.append(text)
519
563
  yield ("token", {"delta": text})
@@ -664,6 +708,9 @@ class OpenAIBackend:
664
708
  reasoning = re.sub(r"(?i)^thinking process:\s*", "", content).strip()
665
709
  content = ""
666
710
 
711
+ if not enable_thinking:
712
+ reasoning = None
713
+
667
714
  return ChatResponse(
668
715
  content=content,
669
716
  reasoning=reasoning,
@@ -274,10 +274,13 @@ def main():
274
274
  if getattr(args, "force_thinking", False):
275
275
  cfg.force_thinking = True
276
276
  cfg.enable_thinking = True
277
+ cfg.force_no_thinking = False
277
278
  elif getattr(args, "thinking", False):
278
279
  cfg.enable_thinking = True
280
+ cfg.force_no_thinking = False
279
281
  elif getattr(args, "no_thinking", False):
280
282
  cfg.enable_thinking = False
283
+ cfg.force_no_thinking = True
281
284
 
282
285
  if args.command == "run":
283
286
  if args.max_turns:
@@ -152,6 +152,7 @@ class Config:
152
152
  enable_thinking: bool = False
153
153
  adaptive_thinking: bool = True
154
154
  force_thinking: bool = False
155
+ force_no_thinking: bool = False
155
156
  n_ctx: int = 32768
156
157
  n_gpu_layers: Optional[int] = None
157
158
 
@@ -327,7 +327,10 @@ class AgentOrchestrator:
327
327
 
328
328
  # ── Adaptive thinking (base setting) ──
329
329
  strict_format = getattr(getattr(task_context, "assessment", None), "requires_strict_format", False) or getattr(task_context, "requires_strict_format", False)
330
- if getattr(cfg, "force_thinking", False):
330
+ if getattr(cfg, "force_no_thinking", False):
331
+ base_enable_thinking = False
332
+ yield AgentEvent("adaptive_mode", {"message": "Non-thinking mode explicitly requested via --no-thinking; fast execution enabled.", "thinking": False})
333
+ elif getattr(cfg, "force_thinking", False):
331
334
  base_enable_thinking = True
332
335
  elif strict_format:
333
336
  base_enable_thinking = False
@@ -400,7 +403,9 @@ class AgentOrchestrator:
400
403
  )
401
404
 
402
405
  # ── Turn-Level Adaptive Reasoning Dynamics ──
403
- if getattr(cfg, "force_thinking", False) or not getattr(cfg, "adaptive_thinking", True):
406
+ if getattr(cfg, "force_no_thinking", False):
407
+ turn_enable_thinking = False
408
+ elif getattr(cfg, "force_thinking", False) or not getattr(cfg, "adaptive_thinking", True):
404
409
  turn_enable_thinking = base_enable_thinking
405
410
  elif strict_format:
406
411
  if prior_turn_had_error:
@@ -440,7 +445,7 @@ class AgentOrchestrator:
440
445
  yield AgentEvent("safety_ceiling", {"turn": turn, "ceiling": safety_ceiling})
441
446
  break
442
447
 
443
- yield AgentEvent("turn_start", {"turn": turn})
448
+ yield AgentEvent("turn_start", {"turn": turn, "thinking": turn_enable_thinking})
444
449
 
445
450
  # In offline mode, guide the model with current workspace progress
446
451
  if is_offline and turn >= 1:
@@ -0,0 +1,70 @@
1
+ import pytest
2
+ from unittest.mock import MagicMock, patch
3
+ from codeagent.config import Config
4
+ from codeagent.backends.openai_backend import _HTTPXClient, OpenAIBackend
5
+
6
+
7
+ def test_httpx_client_uses_http1_for_local_bridge():
8
+ """Verify that localhost/127.0.0.1 uses an unbuffered HTTP/1.1 client for llama-server."""
9
+ client_local = _HTTPXClient("http://127.0.0.1:8080/v1", "not-needed")
10
+ # http2 is disabled for local server
11
+ assert client_local.http_client._transport._pool._http2 is False or not getattr(client_local.http_client, "_http2", True)
12
+
13
+ client_remote = _HTTPXClient("https://test.169pi.ai/v1", "not-needed")
14
+ assert client_remote.http_client is not None
15
+
16
+
17
+ def test_force_no_thinking_flag_on_config():
18
+ """Verify that force_no_thinking flag defaults to False and can be set to True."""
19
+ cfg = Config()
20
+ assert cfg.force_no_thinking is False
21
+
22
+ cfg_no_think = Config(force_no_thinking=True)
23
+ assert cfg_no_think.force_no_thinking is True
24
+
25
+
26
+ def test_openai_backend_suppresses_think_in_non_thinking_mode():
27
+ """Verify that OpenAIBackend suppresses think tokens when enable_thinking=False."""
28
+ cfg = Config(base_url="http://127.0.0.1:8080/v1")
29
+ backend = OpenAIBackend(cfg)
30
+
31
+ # Mock response chunks containing a <think> block
32
+ class MockDelta:
33
+ def __init__(self, content=None, tool_calls=None):
34
+ self.content = content
35
+ self.tool_calls = tool_calls
36
+
37
+ class MockChoice:
38
+ def __init__(self, delta):
39
+ self.delta = delta
40
+
41
+ class MockChunk:
42
+ def __init__(self, content):
43
+ self.choices = [MockChoice(MockDelta(content=content))]
44
+
45
+ mock_stream = [
46
+ MockChunk("<think>"),
47
+ MockChunk("Reasoning about prime numbers..."),
48
+ MockChunk("</think>"),
49
+ MockChunk("def is_prime(n):"),
50
+ MockChunk(" return n > 1"),
51
+ ]
52
+
53
+ mock_client = MagicMock()
54
+ mock_client.chat.completions.create.return_value = mock_stream
55
+ backend._client = mock_client
56
+ backend._resolved_model = "alpie_9b"
57
+
58
+ events = list(backend.chat_completion_stream(
59
+ messages=[{"role": "user", "content": "Write a prime checker"}],
60
+ enable_thinking=False,
61
+ ))
62
+
63
+ # In non-thinking mode, thinking_start and thinking_delta must NOT be emitted
64
+ event_types = [e[0] for e in events]
65
+ assert "thinking_start" not in event_types
66
+ assert "thinking_delta" not in event_types
67
+
68
+ # Only real content tokens must be emitted
69
+ token_events = [e[1]["delta"] for e in events if e[0] == "token"]
70
+ assert "".join(token_events).strip() == "def is_prime(n): return n > 1"
File without changes
File without changes