lmcoding-local 3.2.0__tar.gz → 3.2.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.
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/PKG-INFO +9 -4
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/README.md +8 -3
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/pyproject.toml +1 -1
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/__init__.py +1 -1
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/agent.py +215 -24
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding_local.egg-info/PKG-INFO +9 -4
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/tests/test_core.py +29 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/LICENSE +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/setup.cfg +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/__main__.py +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/checkpoints.py +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/cli.py +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/config.py +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/permissions.py +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/sessions.py +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/tools.py +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/ui.py +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/verifier.py +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding/workspace.py +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding_local.egg-info/SOURCES.txt +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding_local.egg-info/dependency_links.txt +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding_local.egg-info/entry_points.txt +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding_local.egg-info/requires.txt +0 -0
- {lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding_local.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lmcoding-local
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.1
|
|
4
4
|
Summary: Agente de programación local para LM Studio con interfaz tipo Codex y seguridad por workspace
|
|
5
5
|
Author: TV Ofertas Ecuador
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,7 +21,7 @@ Requires-Dist: openai>=1.40.0
|
|
|
21
21
|
Requires-Dist: rich>=13.7.0
|
|
22
22
|
Dynamic: license-file
|
|
23
23
|
|
|
24
|
-
# llmCodex 3.2.
|
|
24
|
+
# llmCodex 3.2.1
|
|
25
25
|
|
|
26
26
|
Agente de programación local para **LM Studio**, con experiencia de terminal inspirada en agentes modernos tipo Codex, colores violeta propios y herramientas para inspeccionar, editar, verificar y reparar proyectos.
|
|
27
27
|
|
|
@@ -94,13 +94,13 @@ Y agrega al PATH:
|
|
|
94
94
|
## Instalar el wheel manualmente
|
|
95
95
|
|
|
96
96
|
```powershell
|
|
97
|
-
py -m pip install dist\lmcoding_local-3.2.
|
|
97
|
+
py -m pip install dist\lmcoding_local-3.2.1-py3-none-any.whl
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
Para aplicaciones CLI es recomendable usar `pipx`:
|
|
101
101
|
|
|
102
102
|
```powershell
|
|
103
|
-
pipx install dist\lmcoding_local-3.2.
|
|
103
|
+
pipx install dist\lmcoding_local-3.2.1-py3-none-any.whl
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
## Preparar LM Studio
|
|
@@ -342,3 +342,8 @@ Y selecciona **Build → Compile**.
|
|
|
342
342
|
## Límites reales
|
|
343
343
|
|
|
344
344
|
El sistema puede detectar, probar, corregir y revertir muchos errores, pero ningún agente puede garantizar reparar absolutamente cualquier bug. Puede necesitar credenciales, servicios externos, hardware, datos privados o una especificación más precisa. llmCodex evita declarar éxito cuando sus verificaciones siguen fallando y puede restaurar el checkpoint anterior.
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
## Corrección 3.2.1
|
|
348
|
+
|
|
349
|
+
Incluye un parser de compatibilidad para modelos que devuelven llamadas de herramientas como texto en lugar de `tool_calls` estructurados.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# llmCodex 3.2.
|
|
1
|
+
# llmCodex 3.2.1
|
|
2
2
|
|
|
3
3
|
Agente de programación local para **LM Studio**, con experiencia de terminal inspirada en agentes modernos tipo Codex, colores violeta propios y herramientas para inspeccionar, editar, verificar y reparar proyectos.
|
|
4
4
|
|
|
@@ -71,13 +71,13 @@ Y agrega al PATH:
|
|
|
71
71
|
## Instalar el wheel manualmente
|
|
72
72
|
|
|
73
73
|
```powershell
|
|
74
|
-
py -m pip install dist\lmcoding_local-3.2.
|
|
74
|
+
py -m pip install dist\lmcoding_local-3.2.1-py3-none-any.whl
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
Para aplicaciones CLI es recomendable usar `pipx`:
|
|
78
78
|
|
|
79
79
|
```powershell
|
|
80
|
-
pipx install dist\lmcoding_local-3.2.
|
|
80
|
+
pipx install dist\lmcoding_local-3.2.1-py3-none-any.whl
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
## Preparar LM Studio
|
|
@@ -319,3 +319,8 @@ Y selecciona **Build → Compile**.
|
|
|
319
319
|
## Límites reales
|
|
320
320
|
|
|
321
321
|
El sistema puede detectar, probar, corregir y revertir muchos errores, pero ningún agente puede garantizar reparar absolutamente cualquier bug. Puede necesitar credenciales, servicios externos, hardware, datos privados o una especificación más precisa. llmCodex evita declarar éxito cuando sus verificaciones siguen fallando y puede restaurar el checkpoint anterior.
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
## Corrección 3.2.1
|
|
325
|
+
|
|
326
|
+
Incluye un parser de compatibilidad para modelos que devuelven llamadas de herramientas como texto en lugar de `tool_calls` estructurados.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lmcoding-local"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.1"
|
|
8
8
|
description = "Agente de programación local para LM Studio con interfaz tipo Codex y seguridad por workspace"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
import re
|
|
5
5
|
import time
|
|
6
|
+
import uuid
|
|
6
7
|
from collections import Counter
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
@@ -29,6 +30,8 @@ Reglas obligatorias:
|
|
|
29
30
|
8. No declares que está corregido mientras las verificaciones sigan fallando.
|
|
30
31
|
9. Resume archivos modificados, pruebas ejecutadas y problemas pendientes.
|
|
31
32
|
10. Responde en el idioma del usuario y no reveles razonamiento privado paso a paso.
|
|
33
|
+
11. Solicita herramientas usando únicamente sus nombres exactos, sin prefijos como file_manager.
|
|
34
|
+
12. Nunca muestres etiquetas internas como <|tool_call>, <tool_call> o [TOOL_REQUEST] al usuario.
|
|
32
35
|
"""
|
|
33
36
|
|
|
34
37
|
|
|
@@ -96,23 +99,189 @@ class LMCodingAgent:
|
|
|
96
99
|
self.session.save()
|
|
97
100
|
|
|
98
101
|
@staticmethod
|
|
99
|
-
def
|
|
100
|
-
|
|
102
|
+
def _split_top_level(text: str, delimiter: str = ",") -> list[str]:
|
|
103
|
+
parts: list[str] = []
|
|
104
|
+
start = 0
|
|
105
|
+
depth = 0
|
|
106
|
+
quote: str | None = None
|
|
107
|
+
escaped = False
|
|
108
|
+
for index, char in enumerate(text):
|
|
109
|
+
if quote is not None:
|
|
110
|
+
if escaped:
|
|
111
|
+
escaped = False
|
|
112
|
+
elif char == "\\":
|
|
113
|
+
escaped = True
|
|
114
|
+
elif char == quote:
|
|
115
|
+
quote = None
|
|
116
|
+
continue
|
|
117
|
+
if char in {"\"", "'"}:
|
|
118
|
+
quote = char
|
|
119
|
+
elif char in "[{(":
|
|
120
|
+
depth += 1
|
|
121
|
+
elif char in "]})":
|
|
122
|
+
depth = max(0, depth - 1)
|
|
123
|
+
elif char == delimiter and depth == 0:
|
|
124
|
+
parts.append(text[start:index].strip())
|
|
125
|
+
start = index + 1
|
|
126
|
+
tail = text[start:].strip()
|
|
127
|
+
if tail:
|
|
128
|
+
parts.append(tail)
|
|
129
|
+
return parts
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def _parse_loose_value(cls, raw: str) -> Any:
|
|
133
|
+
value = raw.strip()
|
|
134
|
+
value = value.replace('<|"|>', '"').replace("<|'|>", "'")
|
|
135
|
+
if not value:
|
|
136
|
+
return ""
|
|
137
|
+
if value[0:1] in {"\"", "'"} and value[-1:] == value[0]:
|
|
138
|
+
# Preserve Windows paths such as C:\\Users\\name instead of treating
|
|
139
|
+
# backslashes as JSON escape sequences.
|
|
140
|
+
inner = value[1:-1]
|
|
141
|
+
return inner.replace(r'\"', '"').replace(r"\'", "'")
|
|
142
|
+
if value.startswith("{") and value.endswith("}"):
|
|
143
|
+
result: dict[str, Any] = {}
|
|
144
|
+
body = value[1:-1].strip()
|
|
145
|
+
if not body:
|
|
146
|
+
return result
|
|
147
|
+
for item in cls._split_top_level(body):
|
|
148
|
+
key_value = cls._split_key_value(item)
|
|
149
|
+
if key_value is None:
|
|
150
|
+
continue
|
|
151
|
+
key, item_value = key_value
|
|
152
|
+
result[key] = cls._parse_loose_value(item_value)
|
|
153
|
+
return result
|
|
154
|
+
if value.startswith("[") and value.endswith("]"):
|
|
155
|
+
body = value[1:-1].strip()
|
|
156
|
+
return [] if not body else [cls._parse_loose_value(item) for item in cls._split_top_level(body)]
|
|
157
|
+
lowered = value.lower()
|
|
158
|
+
if lowered == "true":
|
|
159
|
+
return True
|
|
160
|
+
if lowered == "false":
|
|
161
|
+
return False
|
|
162
|
+
if lowered in {"null", "none"}:
|
|
163
|
+
return None
|
|
164
|
+
try:
|
|
165
|
+
return int(value)
|
|
166
|
+
except ValueError:
|
|
167
|
+
try:
|
|
168
|
+
return float(value)
|
|
169
|
+
except ValueError:
|
|
170
|
+
return value
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def _split_key_value(item: str) -> tuple[str, str] | None:
|
|
174
|
+
depth = 0
|
|
175
|
+
quote: str | None = None
|
|
176
|
+
escaped = False
|
|
177
|
+
for index, char in enumerate(item):
|
|
178
|
+
if quote is not None:
|
|
179
|
+
if escaped:
|
|
180
|
+
escaped = False
|
|
181
|
+
elif char == "\\":
|
|
182
|
+
escaped = True
|
|
183
|
+
elif char == quote:
|
|
184
|
+
quote = None
|
|
185
|
+
continue
|
|
186
|
+
if char in {"\"", "'"}:
|
|
187
|
+
quote = char
|
|
188
|
+
elif char in "[{(":
|
|
189
|
+
depth += 1
|
|
190
|
+
elif char in "]})":
|
|
191
|
+
depth = max(0, depth - 1)
|
|
192
|
+
elif char == ":" and depth == 0:
|
|
193
|
+
key = item[:index].strip().strip("\"'")
|
|
194
|
+
return key, item[index + 1:].strip()
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def _repair_json(cls, raw: str) -> dict[str, Any]:
|
|
199
|
+
text = (raw or "{}").strip()
|
|
200
|
+
text = text.replace('<|"|>', '"').replace("<|'|>", "'")
|
|
101
201
|
try:
|
|
102
202
|
value = json.loads(text or "{}")
|
|
103
203
|
return value if isinstance(value, dict) else {}
|
|
104
|
-
except json.JSONDecodeError:
|
|
204
|
+
except (json.JSONDecodeError, TypeError):
|
|
105
205
|
pass
|
|
106
206
|
match = re.search(r"\{.*\}", text, re.DOTALL)
|
|
107
|
-
if match:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
207
|
+
if not match:
|
|
208
|
+
return {}
|
|
209
|
+
candidate = match.group(0)
|
|
210
|
+
value = cls._parse_loose_value(candidate)
|
|
211
|
+
return value if isinstance(value, dict) else {}
|
|
212
|
+
|
|
213
|
+
def _canonical_tool_name(self, raw_name: str) -> str:
|
|
214
|
+
name = raw_name.strip().removeprefix("call:")
|
|
215
|
+
aliases = {
|
|
216
|
+
"file_manager.apply_patch": "apply_edits",
|
|
217
|
+
"apply_patch": "apply_edits",
|
|
218
|
+
"patch_file": "apply_edits",
|
|
219
|
+
"terminal.exec": "run_command",
|
|
220
|
+
"terminal.run": "run_command",
|
|
221
|
+
"shell.exec": "run_command",
|
|
222
|
+
"shell.run": "run_command",
|
|
223
|
+
}
|
|
224
|
+
if name in aliases:
|
|
225
|
+
return aliases[name]
|
|
226
|
+
if name in self.toolbox.functions:
|
|
227
|
+
return name
|
|
228
|
+
tail = re.split(r"[.:/]", name)[-1]
|
|
229
|
+
return aliases.get(tail, tail)
|
|
230
|
+
|
|
231
|
+
def _parse_tool_block(self, block: str, call_id: str) -> dict[str, Any] | None:
|
|
232
|
+
cleaned = block.strip()
|
|
233
|
+
cleaned = cleaned.replace('<|"|>', '"').replace("<|'|>", "'")
|
|
234
|
+
|
|
235
|
+
# Format emitted by some local models:
|
|
236
|
+
# call:file_manager.list_files{path:"C:\\project"}
|
|
237
|
+
direct = re.match(r"(?:call:)?(?P<name>[A-Za-z_][\w.:-]*)\s*(?P<args>\{.*\})\s*$", cleaned, re.DOTALL)
|
|
238
|
+
if direct:
|
|
239
|
+
name = self._canonical_tool_name(direct.group("name"))
|
|
240
|
+
arguments = self._repair_json(direct.group("args"))
|
|
241
|
+
return {"id": call_id, "name": name, "arguments": arguments}
|
|
242
|
+
|
|
243
|
+
payload = self._repair_json(cleaned)
|
|
244
|
+
if not payload:
|
|
245
|
+
return None
|
|
246
|
+
function_payload = payload.get("function") if isinstance(payload.get("function"), dict) else {}
|
|
247
|
+
raw_name = payload.get("name") or function_payload.get("name")
|
|
248
|
+
if not isinstance(raw_name, str):
|
|
249
|
+
return None
|
|
250
|
+
raw_arguments = payload.get("arguments", function_payload.get("arguments", {}))
|
|
251
|
+
if isinstance(raw_arguments, str):
|
|
252
|
+
arguments = self._repair_json(raw_arguments)
|
|
253
|
+
elif isinstance(raw_arguments, dict):
|
|
254
|
+
arguments = raw_arguments
|
|
255
|
+
else:
|
|
256
|
+
arguments = {}
|
|
257
|
+
return {
|
|
258
|
+
"id": call_id,
|
|
259
|
+
"name": self._canonical_tool_name(raw_name),
|
|
260
|
+
"arguments": arguments,
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
def _extract_text_tool_calls(self, content: str, step: int) -> tuple[list[dict[str, Any]], str]:
|
|
264
|
+
if not content:
|
|
265
|
+
return [], content
|
|
266
|
+
patterns = [
|
|
267
|
+
re.compile(r"<\|tool_call>(.*?)<tool_call\|>", re.DOTALL | re.IGNORECASE),
|
|
268
|
+
re.compile(r"<tool_call>(.*?)</tool_call>", re.DOTALL | re.IGNORECASE),
|
|
269
|
+
re.compile(r"\[TOOL_REQUEST\](.*?)\[END_TOOL_REQUEST\]", re.DOTALL | re.IGNORECASE),
|
|
270
|
+
]
|
|
271
|
+
calls: list[dict[str, Any]] = []
|
|
272
|
+
visible = content
|
|
273
|
+
for pattern in patterns:
|
|
274
|
+
matches = list(pattern.finditer(visible))
|
|
275
|
+
for match in matches:
|
|
276
|
+
call = self._parse_tool_block(
|
|
277
|
+
match.group(1),
|
|
278
|
+
f"fallback_{step}_{len(calls) + 1}_{uuid.uuid4().hex[:8]}",
|
|
279
|
+
)
|
|
280
|
+
if call is not None:
|
|
281
|
+
calls.append(call)
|
|
282
|
+
if matches:
|
|
283
|
+
visible = pattern.sub("", visible)
|
|
284
|
+
return calls, visible.strip()
|
|
116
285
|
|
|
117
286
|
def _completion(self):
|
|
118
287
|
last_error: Exception | None = None
|
|
@@ -153,40 +322,62 @@ class LMCodingAgent:
|
|
|
153
322
|
with console.status(f"[brand]llmCodex trabajando · paso {step}[/brand]", spinner="dots"):
|
|
154
323
|
response = self._completion()
|
|
155
324
|
message = response.choices[0].message
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
325
|
+
content = message.content or ""
|
|
326
|
+
normalized_calls: list[dict[str, Any]] = []
|
|
327
|
+
|
|
328
|
+
for call in message.tool_calls or []:
|
|
329
|
+
normalized_calls.append({
|
|
330
|
+
"id": call.id or f"structured_{step}_{uuid.uuid4().hex[:8]}",
|
|
331
|
+
"name": self._canonical_tool_name(call.function.name),
|
|
332
|
+
"arguments": self._repair_json(call.function.arguments or "{}"),
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
# LM Studio documents that malformed calls can fall back into
|
|
336
|
+
# message.content. Recover common native/default formats here.
|
|
337
|
+
if not normalized_calls and content:
|
|
338
|
+
normalized_calls, content = self._extract_text_tool_calls(content, step)
|
|
339
|
+
|
|
340
|
+
assistant_payload: dict[str, Any] = {"role": "assistant", "content": content}
|
|
341
|
+
if normalized_calls:
|
|
159
342
|
assistant_payload["tool_calls"] = [
|
|
160
343
|
{
|
|
161
|
-
"id": call
|
|
344
|
+
"id": call["id"],
|
|
162
345
|
"type": "function",
|
|
163
|
-
"function": {
|
|
346
|
+
"function": {
|
|
347
|
+
"name": call["name"],
|
|
348
|
+
"arguments": json.dumps(call["arguments"], ensure_ascii=False),
|
|
349
|
+
},
|
|
164
350
|
}
|
|
165
|
-
for call in
|
|
351
|
+
for call in normalized_calls
|
|
166
352
|
]
|
|
167
353
|
self.session.messages.append(assistant_payload)
|
|
168
354
|
|
|
169
|
-
if not
|
|
170
|
-
final_text =
|
|
355
|
+
if not normalized_calls:
|
|
356
|
+
final_text = content or "(sin respuesta)"
|
|
171
357
|
if render:
|
|
172
358
|
console.print(Panel(Markdown(final_text), title="[brand]llmCodex[/brand]", border_style="magenta"))
|
|
173
359
|
self.session.save()
|
|
174
360
|
return final_text
|
|
175
361
|
|
|
176
|
-
for call in
|
|
177
|
-
name = call
|
|
178
|
-
arguments =
|
|
362
|
+
for call in normalized_calls:
|
|
363
|
+
name = call["name"]
|
|
364
|
+
arguments = call["arguments"]
|
|
179
365
|
signature = f"{name}:{json.dumps(arguments, sort_keys=True, ensure_ascii=False)}"
|
|
180
366
|
self.call_signatures[signature] += 1
|
|
181
367
|
if self.call_signatures[signature] >= 4:
|
|
182
368
|
result = "ERROR: bucle detectado; esta llamada idéntica ya se repitió varias veces. Cambia de estrategia."
|
|
369
|
+
elif name not in self.toolbox.functions:
|
|
370
|
+
result = (
|
|
371
|
+
f"ERROR: herramienta desconocida: {name}. "
|
|
372
|
+
f"Herramientas válidas: {', '.join(sorted(self.toolbox.functions))}"
|
|
373
|
+
)
|
|
183
374
|
else:
|
|
184
375
|
if render:
|
|
185
376
|
console.print(f"[accent]› {name}[/accent] [muted]{truncate(json.dumps(arguments, ensure_ascii=False), 700)}[/muted]")
|
|
186
377
|
result = self.toolbox.execute(name, arguments)
|
|
187
378
|
if render:
|
|
188
379
|
console.print(Panel(truncate(result, 3500), border_style="grey35", title="resultado"))
|
|
189
|
-
self.session.messages.append({"role": "tool", "tool_call_id": call
|
|
380
|
+
self.session.messages.append({"role": "tool", "tool_call_id": call["id"], "content": result})
|
|
190
381
|
|
|
191
382
|
final_text = "Se alcanzó el máximo de pasos. Revisa el estado y pide continuar."
|
|
192
383
|
self.session.save()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lmcoding-local
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.1
|
|
4
4
|
Summary: Agente de programación local para LM Studio con interfaz tipo Codex y seguridad por workspace
|
|
5
5
|
Author: TV Ofertas Ecuador
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,7 +21,7 @@ Requires-Dist: openai>=1.40.0
|
|
|
21
21
|
Requires-Dist: rich>=13.7.0
|
|
22
22
|
Dynamic: license-file
|
|
23
23
|
|
|
24
|
-
# llmCodex 3.2.
|
|
24
|
+
# llmCodex 3.2.1
|
|
25
25
|
|
|
26
26
|
Agente de programación local para **LM Studio**, con experiencia de terminal inspirada en agentes modernos tipo Codex, colores violeta propios y herramientas para inspeccionar, editar, verificar y reparar proyectos.
|
|
27
27
|
|
|
@@ -94,13 +94,13 @@ Y agrega al PATH:
|
|
|
94
94
|
## Instalar el wheel manualmente
|
|
95
95
|
|
|
96
96
|
```powershell
|
|
97
|
-
py -m pip install dist\lmcoding_local-3.2.
|
|
97
|
+
py -m pip install dist\lmcoding_local-3.2.1-py3-none-any.whl
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
Para aplicaciones CLI es recomendable usar `pipx`:
|
|
101
101
|
|
|
102
102
|
```powershell
|
|
103
|
-
pipx install dist\lmcoding_local-3.2.
|
|
103
|
+
pipx install dist\lmcoding_local-3.2.1-py3-none-any.whl
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
## Preparar LM Studio
|
|
@@ -342,3 +342,8 @@ Y selecciona **Build → Compile**.
|
|
|
342
342
|
## Límites reales
|
|
343
343
|
|
|
344
344
|
El sistema puede detectar, probar, corregir y revertir muchos errores, pero ningún agente puede garantizar reparar absolutamente cualquier bug. Puede necesitar credenciales, servicios externos, hardware, datos privados o una especificación más precisa. llmCodex evita declarar éxito cuando sus verificaciones siguen fallando y puede restaurar el checkpoint anterior.
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
## Corrección 3.2.1
|
|
348
|
+
|
|
349
|
+
Incluye un parser de compatibilidad para modelos que devuelven llamadas de herramientas como texto en lugar de `tool_calls` estructurados.
|
|
@@ -4,6 +4,7 @@ import tempfile
|
|
|
4
4
|
import unittest
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
from lmcoding.agent import LMCodingAgent
|
|
7
8
|
from lmcoding.checkpoints import CheckpointManager
|
|
8
9
|
from lmcoding.permissions import ApprovalPolicy, PermissionMode, Risk, classify_command
|
|
9
10
|
from lmcoding.tools import ToolBox
|
|
@@ -89,5 +90,33 @@ class VerifierTests(unittest.TestCase):
|
|
|
89
90
|
self.assertTrue(verifier.verify("quick").ok)
|
|
90
91
|
|
|
91
92
|
|
|
93
|
+
class ToolCallFallbackTests(unittest.TestCase):
|
|
94
|
+
def make_agent(self):
|
|
95
|
+
agent = object.__new__(LMCodingAgent)
|
|
96
|
+
agent.toolbox = type("FakeToolbox", (), {"functions": {"list_files": lambda: None, "run_command": lambda: None, "apply_edits": lambda: None}})()
|
|
97
|
+
return agent
|
|
98
|
+
|
|
99
|
+
def test_recovers_reported_lmstudio_format(self):
|
|
100
|
+
agent = self.make_agent()
|
|
101
|
+
content = r'<|tool_call>call:file_manager.list_files{path:<|"|>C:\Users\tyler\Downloads\GemAI<|"|>}<tool_call|>'
|
|
102
|
+
calls, visible = agent._extract_text_tool_calls(content, 1)
|
|
103
|
+
self.assertEqual(visible, "")
|
|
104
|
+
self.assertEqual(len(calls), 1)
|
|
105
|
+
self.assertEqual(calls[0]["name"], "list_files")
|
|
106
|
+
self.assertEqual(calls[0]["arguments"]["path"], r"C:\Users\tyler\Downloads\GemAI")
|
|
107
|
+
|
|
108
|
+
def test_recovers_lmstudio_default_tool_request(self):
|
|
109
|
+
agent = self.make_agent()
|
|
110
|
+
content = '[TOOL_REQUEST]{"name":"run_command","arguments":{"command":"python -m pytest"}}[END_TOOL_REQUEST]'
|
|
111
|
+
calls, visible = agent._extract_text_tool_calls(content, 2)
|
|
112
|
+
self.assertEqual(visible, "")
|
|
113
|
+
self.assertEqual(calls[0]["name"], "run_command")
|
|
114
|
+
self.assertEqual(calls[0]["arguments"]["command"], "python -m pytest")
|
|
115
|
+
|
|
116
|
+
def test_aliases_apply_patch(self):
|
|
117
|
+
agent = self.make_agent()
|
|
118
|
+
self.assertEqual(agent._canonical_tool_name("file_manager.apply_patch"), "apply_edits")
|
|
119
|
+
|
|
120
|
+
|
|
92
121
|
if __name__ == "__main__":
|
|
93
122
|
unittest.main()
|
|
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
|
{lmcoding_local-3.2.0 → lmcoding_local-3.2.1}/src/lmcoding_local.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|