alpiecode 10.6.9__tar.gz → 10.7.1__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.9 → alpiecode-10.7.1}/PKG-INFO +1 -1
- {alpiecode-10.6.9 → alpiecode-10.7.1}/pyproject.toml +1 -1
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/alpiecode.egg-info/PKG-INFO +1 -1
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/__init__.py +1 -1
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/backends/local_backend.py +87 -40
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/backends/openai_backend.py +1 -1
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/config.py +7 -3
- {alpiecode-10.6.9 → alpiecode-10.7.1}/test/test_non_thinking_and_local_stream.py +25 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/README.md +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/setup.cfg +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/alpiecode/__init__.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/alpiecode.egg-info/SOURCES.txt +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/alpiecode.egg-info/requires.txt +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/agent.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/assessment.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/backends/__init__.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/backends/base.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/cache.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/cli.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/client.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/compaction.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/context.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/discovery.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/doctor.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/dream_rsi.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/executor.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/extension/alpiecode.vsix +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/git_ops.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/github.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/guardian.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/guardrails.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/ipython_ext.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/local_model.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/media.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/memory.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/metrics.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/orchestrator.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/paths.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/progress.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/prompt.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/rephraser.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/server.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/session.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/system1.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/tools.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/updater.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/verifier.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/src/codeagent/vscode_installer.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/test/test_adaptive_resilience.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/test/test_cli_ui.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/test/test_context_and_trajectory.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/test/test_dream_rsi.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/test/test_local_model_download.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/test/test_task_assessment.py +0 -0
- {alpiecode-10.6.9 → alpiecode-10.7.1}/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.
|
|
20
|
+
__version__ = "10.7.1"
|
|
21
21
|
|
|
22
22
|
def run(task: str, workdir: str = ".", reasoning_level: str = "thinking", **kwargs):
|
|
23
23
|
"""Run an autonomous coding task programmatically."""
|
|
@@ -877,7 +877,7 @@ class iOSBackend(BasePlatformBackend):
|
|
|
877
877
|
if tools:
|
|
878
878
|
payload["tools"] = tools
|
|
879
879
|
payload["tool_choice"] = "auto"
|
|
880
|
-
resp = httpx.post(f"{bridge_url}/v1/chat/completions", json=payload, timeout=
|
|
880
|
+
resp = httpx.post(f"{bridge_url}/v1/chat/completions", json=payload, timeout=300.0)
|
|
881
881
|
if resp.status_code != 200:
|
|
882
882
|
raise RuntimeError(f"iOS bridge HTTP {resp.status_code}: {resp.text}")
|
|
883
883
|
data = json.loads(resp.text)
|
|
@@ -956,7 +956,7 @@ class iOSBackend(BasePlatformBackend):
|
|
|
956
956
|
if tools:
|
|
957
957
|
payload["tools"] = tools
|
|
958
958
|
payload["tool_choice"] = "auto"
|
|
959
|
-
client = httpx.Client(timeout=
|
|
959
|
+
client = httpx.Client(timeout=httpx.Timeout(300.0, connect=10.0, read=300.0, write=30.0))
|
|
960
960
|
req = client.build_request("POST", f"{bridge_url}/v1/chat/completions", json=payload)
|
|
961
961
|
resp = client.send(req, stream=True)
|
|
962
962
|
if resp.status_code != 200:
|
|
@@ -1007,27 +1007,63 @@ class AndroidBackend(BasePlatformBackend):
|
|
|
1007
1007
|
def _get_bridge_url(self) -> str:
|
|
1008
1008
|
return os.environ.get("ANDROID_LLAMA_BRIDGE", "http://127.0.0.1:8080").rstrip("/")
|
|
1009
1009
|
|
|
1010
|
+
def _find_llama_server_bin(self) -> Optional[Path]:
|
|
1011
|
+
custom = os.environ.get("LLAMA_SERVER_PATH") or os.environ.get("LLAMA_BIN")
|
|
1012
|
+
if custom and Path(custom).is_file():
|
|
1013
|
+
return Path(custom)
|
|
1014
|
+
which_bin = shutil.which("llama-server")
|
|
1015
|
+
if which_bin:
|
|
1016
|
+
return Path(which_bin)
|
|
1017
|
+
candidates = [
|
|
1018
|
+
Path("/data/data/com.termux/files/usr/bin/llama-server"),
|
|
1019
|
+
Path.home() / "llama.cpp" / "build" / "bin" / "llama-server",
|
|
1020
|
+
Path.home() / "llama.cpp" / "build" / "bin" / "Release" / "llama-server",
|
|
1021
|
+
Path.home() / ".alpiecode" / "bin" / "llama-server",
|
|
1022
|
+
Path.cwd() / "llama.cpp" / "build" / "bin" / "llama-server",
|
|
1023
|
+
]
|
|
1024
|
+
for c in candidates:
|
|
1025
|
+
if c.is_file() and os.access(c, os.X_OK):
|
|
1026
|
+
return c
|
|
1027
|
+
return None
|
|
1028
|
+
|
|
1029
|
+
def _find_gguf_model(self) -> Optional[Path]:
|
|
1030
|
+
from ..local_model import get_model_cache_dir
|
|
1031
|
+
candidate_dirs = [
|
|
1032
|
+
get_model_cache_dir(),
|
|
1033
|
+
Path.home() / ".alpiecode" / "models",
|
|
1034
|
+
Path.home() / "models",
|
|
1035
|
+
Path.cwd() / "models",
|
|
1036
|
+
Path.cwd() / ".alpiecode" / "models",
|
|
1037
|
+
]
|
|
1038
|
+
for d in candidate_dirs:
|
|
1039
|
+
if d.exists():
|
|
1040
|
+
ggufs = list(d.glob("*.gguf"))
|
|
1041
|
+
mains = [g for g in ggufs if "mmproj" not in g.name.lower()]
|
|
1042
|
+
if mains:
|
|
1043
|
+
mains.sort(key=lambda p: p.stat().st_size)
|
|
1044
|
+
return mains[0]
|
|
1045
|
+
return None
|
|
1046
|
+
|
|
1010
1047
|
def _ensure_local_server(self) -> bool:
|
|
1011
1048
|
"""Auto-detect or auto-start local llama-server in background if needed."""
|
|
1012
1049
|
if self._is_bridge_online():
|
|
1013
1050
|
return True
|
|
1014
|
-
server_bin =
|
|
1051
|
+
server_bin = self._find_llama_server_bin()
|
|
1015
1052
|
if not server_bin:
|
|
1053
|
+
self._last_server_error = "llama-server binary was not found in PATH or ~/llama.cpp/build/bin/llama-server."
|
|
1016
1054
|
return False
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
return False
|
|
1021
|
-
ggufs = list(cache_dir.glob("*.gguf"))
|
|
1022
|
-
main_models = [g for g in ggufs if "mmproj" not in g.name.lower()]
|
|
1023
|
-
if not main_models:
|
|
1055
|
+
model_path = self._find_gguf_model()
|
|
1056
|
+
if not model_path:
|
|
1057
|
+
self._last_server_error = "No GGUF model found in ~/.alpiecode/models or ~/models."
|
|
1024
1058
|
return False
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1059
|
+
n_ctx = getattr(self._cfg, "n_ctx", 2048)
|
|
1060
|
+
if n_ctx > 2048:
|
|
1061
|
+
n_ctx = 2048
|
|
1062
|
+
n_ctx_val = str(n_ctx)
|
|
1063
|
+
base_cmd = [
|
|
1064
|
+
str(server_bin),
|
|
1029
1065
|
"-m", str(model_path),
|
|
1030
|
-
"-c",
|
|
1066
|
+
"-c", n_ctx_val,
|
|
1031
1067
|
"-t", "4",
|
|
1032
1068
|
"-np", "1",
|
|
1033
1069
|
"-b", "512",
|
|
@@ -1036,9 +1072,21 @@ class AndroidBackend(BasePlatformBackend):
|
|
|
1036
1072
|
"--cache-type-v", "q8_0",
|
|
1037
1073
|
"--port", "8080",
|
|
1038
1074
|
]
|
|
1039
|
-
if
|
|
1040
|
-
|
|
1075
|
+
# Only load vision projector if specifically needed to keep RAM footprint under 5GB
|
|
1076
|
+
if getattr(self._cfg, "enable_vision", False):
|
|
1077
|
+
mmprojs = list(model_path.parent.glob("*mmproj*.gguf"))
|
|
1078
|
+
if mmprojs:
|
|
1079
|
+
base_cmd += ["--mmproj", str(mmprojs[0])]
|
|
1080
|
+
|
|
1081
|
+
taskset_bin = shutil.which("taskset")
|
|
1082
|
+
if taskset_bin and os.cpu_count() == 8:
|
|
1083
|
+
cmd = [taskset_bin, "-c", "4-7"] + base_cmd
|
|
1084
|
+
else:
|
|
1085
|
+
cmd = base_cmd
|
|
1086
|
+
|
|
1041
1087
|
try:
|
|
1088
|
+
print(f"⏳ Starting Alpie ({model_path.name}) in background...")
|
|
1089
|
+
print(f" Context window: {n_ctx_val} tokens | Performance cores: 4 | Lean KV cache active")
|
|
1042
1090
|
subprocess.Popen(
|
|
1043
1091
|
cmd,
|
|
1044
1092
|
stdout=subprocess.DEVNULL,
|
|
@@ -1046,12 +1094,13 @@ class AndroidBackend(BasePlatformBackend):
|
|
|
1046
1094
|
start_new_session=True,
|
|
1047
1095
|
)
|
|
1048
1096
|
import time
|
|
1049
|
-
for _ in range(
|
|
1097
|
+
for _ in range(40):
|
|
1050
1098
|
if self._is_bridge_online():
|
|
1051
1099
|
return True
|
|
1052
|
-
time.sleep(0.
|
|
1053
|
-
except Exception:
|
|
1054
|
-
|
|
1100
|
+
time.sleep(0.5)
|
|
1101
|
+
except Exception as e:
|
|
1102
|
+
self._last_server_error = f"Failed to launch llama-server: {e}"
|
|
1103
|
+
return False
|
|
1055
1104
|
return self._is_bridge_online()
|
|
1056
1105
|
|
|
1057
1106
|
@property
|
|
@@ -1060,7 +1109,21 @@ class AndroidBackend(BasePlatformBackend):
|
|
|
1060
1109
|
import llama_cpp
|
|
1061
1110
|
return True
|
|
1062
1111
|
except ImportError:
|
|
1063
|
-
return self._is_bridge_online() or bool(
|
|
1112
|
+
return self._is_bridge_online() or bool(self._find_llama_server_bin() or shutil.which("llama-cli"))
|
|
1113
|
+
|
|
1114
|
+
def _raise_missing_engine_error(self) -> None:
|
|
1115
|
+
err_detail = getattr(self, "_last_server_error", None)
|
|
1116
|
+
detail_msg = f"\n│ Reason: {err_detail}" if err_detail else ""
|
|
1117
|
+
raise RuntimeError(
|
|
1118
|
+
"\n╭────────────────────────────────────────────────────────────╮\n"
|
|
1119
|
+
"│ Android / Termux Local Inference: │\n"
|
|
1120
|
+
"│ Local inference server (http://127.0.0.1:8080) is offline.│"
|
|
1121
|
+
f"{detail_msg}\n"
|
|
1122
|
+
"│ │\n"
|
|
1123
|
+
"│ To start it manually in Termux: │\n"
|
|
1124
|
+
"│ llama-server -m ~/.alpiecode/models/*.gguf -c 2048 -t 4 │\n"
|
|
1125
|
+
"╰────────────────────────────────────────────────────────────╯"
|
|
1126
|
+
)
|
|
1064
1127
|
|
|
1065
1128
|
def chat_completion(
|
|
1066
1129
|
self,
|
|
@@ -1079,15 +1142,7 @@ class AndroidBackend(BasePlatformBackend):
|
|
|
1079
1142
|
if self._ensure_local_server():
|
|
1080
1143
|
return self._bridge_chat_completion(messages, tools, temperature, max_tokens, enable_thinking)
|
|
1081
1144
|
|
|
1082
|
-
|
|
1083
|
-
"\n╭────────────────────────────────────────────────────────────╮\n"
|
|
1084
|
-
"│ Android / Termux Native Inference: │\n"
|
|
1085
|
-
"│ No local engine is available. │\n"
|
|
1086
|
-
"│ To enable local inference on Termux, run: │\n"
|
|
1087
|
-
"│ pkg install llama-cpp │\n"
|
|
1088
|
-
"│ alpiecode init │\n"
|
|
1089
|
-
"╰────────────────────────────────────────────────────────────╯"
|
|
1090
|
-
)
|
|
1145
|
+
self._raise_missing_engine_error()
|
|
1091
1146
|
|
|
1092
1147
|
def chat_completion_stream(
|
|
1093
1148
|
self,
|
|
@@ -1108,15 +1163,7 @@ class AndroidBackend(BasePlatformBackend):
|
|
|
1108
1163
|
yield from self._bridge_chat_completion_stream(messages, tools, temperature, max_tokens, enable_thinking)
|
|
1109
1164
|
return
|
|
1110
1165
|
|
|
1111
|
-
|
|
1112
|
-
"\n╭────────────────────────────────────────────────────────────╮\n"
|
|
1113
|
-
"│ Android / Termux Native Inference: │\n"
|
|
1114
|
-
"│ No local engine is available. │\n"
|
|
1115
|
-
"│ To enable local inference on Termux, run: │\n"
|
|
1116
|
-
"│ pkg install llama-cpp │\n"
|
|
1117
|
-
"│ alpiecode init │\n"
|
|
1118
|
-
"╰────────────────────────────────────────────────────────────╯"
|
|
1119
|
-
)
|
|
1166
|
+
self._raise_missing_engine_error()
|
|
1120
1167
|
|
|
1121
1168
|
|
|
1122
1169
|
class MacOSBackend(BasePlatformBackend):
|
|
@@ -180,7 +180,7 @@ class _HTTPXClient:
|
|
|
180
180
|
elif "127.0.0.1" in base_url or "localhost" in base_url:
|
|
181
181
|
self.http_client = httpx.Client(
|
|
182
182
|
http2=False,
|
|
183
|
-
timeout=httpx.Timeout(
|
|
183
|
+
timeout=httpx.Timeout(300.0, connect=10.0, read=300.0, write=30.0),
|
|
184
184
|
limits=httpx.Limits(max_keepalive_connections=5, max_connections=10),
|
|
185
185
|
)
|
|
186
186
|
else:
|
|
@@ -233,9 +233,13 @@ def load_config() -> Config:
|
|
|
233
233
|
except ValueError:
|
|
234
234
|
pass
|
|
235
235
|
|
|
236
|
-
#
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
# On mobile (Android / Termux), keep context at 2048 to prevent ZRAM thrashing with 9B models
|
|
237
|
+
if is_android() and not any(os.environ.get(v) for v in ("ALPIECODE_N_CTX", "ALPIECODE_CTX_SIZE")):
|
|
238
|
+
if data.get("n_ctx", 32768) > 2048:
|
|
239
|
+
data["n_ctx"] = 2048
|
|
240
|
+
if data.get("max_tokens", 8192) > 1024:
|
|
241
|
+
data["max_tokens"] = 1024
|
|
242
|
+
elif data.get("base_url") == "offline" and not any(os.environ.get(v) for v in ("ALPIECODE_N_CTX", "ALPIECODE_CTX_SIZE")):
|
|
239
243
|
if data.get("n_ctx") in (32768, 4096, 16384):
|
|
240
244
|
data["n_ctx"] = 8192
|
|
241
245
|
if data.get("max_tokens", 8192) > 4096:
|
|
@@ -134,3 +134,28 @@ def test_prompt_builder_lean_offline_prompt():
|
|
|
134
134
|
assert "You are running in OFFLINE mode" in prompt
|
|
135
135
|
assert "Execution & Goal Convergence Rules" not in prompt
|
|
136
136
|
assert len(prompt) < 3500 # Lean offline prompt under 3500 chars (~600 tokens vs 4500 cloud tokens)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_android_backend_skips_mmproj_for_code_tasks(tmp_path):
|
|
140
|
+
from pathlib import Path
|
|
141
|
+
from codeagent.backends.local_backend import AndroidBackend
|
|
142
|
+
cfg = Config(base_url="offline")
|
|
143
|
+
backend = AndroidBackend(cfg)
|
|
144
|
+
|
|
145
|
+
fake_model = tmp_path / "alpie-vl-text-q3_k_m.gguf"
|
|
146
|
+
fake_model.touch()
|
|
147
|
+
fake_mmproj = tmp_path / "alpie-vl-mmproj-f16.gguf"
|
|
148
|
+
fake_mmproj.touch()
|
|
149
|
+
|
|
150
|
+
with patch.object(backend, "_find_llama_server_bin", return_value=Path("/bin/llama-server")):
|
|
151
|
+
with patch.object(backend, "_find_gguf_model", return_value=fake_model):
|
|
152
|
+
with patch("subprocess.Popen") as mock_popen:
|
|
153
|
+
with patch.object(backend, "_is_bridge_online", side_effect=[False, True]):
|
|
154
|
+
backend._ensure_local_server()
|
|
155
|
+
assert mock_popen.called
|
|
156
|
+
args = mock_popen.call_args[0][0]
|
|
157
|
+
# By default (for code tasks), --mmproj must NOT be loaded
|
|
158
|
+
assert "--mmproj" not in args
|
|
159
|
+
assert "-c" in args
|
|
160
|
+
assert "2048" in args
|
|
161
|
+
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|