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.
- {alpiecode-10.6.6 → alpiecode-10.6.7}/PKG-INFO +1 -1
- {alpiecode-10.6.6 → alpiecode-10.6.7}/pyproject.toml +1 -1
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/PKG-INFO +1 -1
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/SOURCES.txt +1 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/__init__.py +1 -1
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/agent.py +12 -6
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/backends/openai_backend.py +53 -6
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/cli.py +3 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/config.py +1 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/orchestrator.py +8 -3
- alpiecode-10.6.7/test/test_non_thinking_and_local_stream.py +70 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/README.md +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/setup.cfg +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode/__init__.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/requires.txt +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/assessment.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/backends/__init__.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/backends/base.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/backends/local_backend.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/cache.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/client.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/compaction.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/context.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/discovery.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/doctor.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/dream_rsi.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/executor.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/extension/alpiecode.vsix +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/git_ops.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/github.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/guardian.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/guardrails.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/ipython_ext.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/local_model.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/media.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/memory.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/metrics.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/paths.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/progress.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/prompt.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/rephraser.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/server.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/session.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/system1.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/tools.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/updater.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/verifier.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/src/codeagent/vscode_installer.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_adaptive_resilience.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_cli_ui.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_context_and_trajectory.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_dream_rsi.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_local_model_download.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_task_assessment.py +0 -0
- {alpiecode-10.6.6 → alpiecode-10.6.7}/test/test_verifier.py +0 -0
|
@@ -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.
|
|
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
|
|
380
|
-
status_spinner.pause()
|
|
379
|
+
elif event.type == "thinking_delta" and verbose:
|
|
381
380
|
delta = event.data.get("delta", "")
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
@@ -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, "
|
|
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, "
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|