opalacoder 0.1.0__tar.gz → 0.1.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.
Files changed (68) hide show
  1. opalacoder-0.1.1/.gitignore +145 -0
  2. {opalacoder-0.1.0 → opalacoder-0.1.1}/PKG-INFO +1 -1
  3. {opalacoder-0.1.0 → opalacoder-0.1.1}/agents.yaml +1 -1
  4. opalacoder-0.1.1/direct_gemini_test.py +27 -0
  5. opalacoder-0.1.1/logotipo.png +0 -0
  6. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/agents.py +11 -5
  7. opalacoder-0.1.1/opalacoder/archival.py +77 -0
  8. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/cli.py +15 -3
  9. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/cli_commands.py +2 -2
  10. opalacoder-0.1.1/opalacoder/memgpt.py +411 -0
  11. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/orchestrator.py +92 -41
  12. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/project.py +30 -5
  13. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/tools.py +53 -5
  14. {opalacoder-0.1.0 → opalacoder-0.1.1}/pyproject.toml +1 -1
  15. opalacoder-0.1.1/pytest.ini +7 -0
  16. {opalacoder-0.1.0 → opalacoder-0.1.1}/requirements.txt +2 -1
  17. opalacoder-0.1.1/test_complexity.py +1 -0
  18. opalacoder-0.1.1/test_gemini.py +20 -0
  19. opalacoder-0.1.1/test_gemini_sequences.py +52 -0
  20. opalacoder-0.1.1/tests/test_executor_context_extraction.py +1 -0
  21. {opalacoder-0.1.0 → opalacoder-0.1.1}/tests/test_intent_classifier.py +7 -1
  22. opalacoder-0.1.1/tests/test_new_features.py +1 -0
  23. opalacoder-0.1.1/tests/test_pipeline_integration.py +1 -0
  24. opalacoder-0.1.1/tests/test_planner_flow.py +1 -0
  25. opalacoder-0.1.1/tests/test_project_dir_isolation.py +1 -0
  26. {opalacoder-0.1.0 → opalacoder-0.1.1}/tests/test_refinement_loop.py +40 -44
  27. opalacoder-0.1.0/.gitignore +0 -8
  28. opalacoder-0.1.0/test_complexity.py +0 -13
  29. opalacoder-0.1.0/tests/test_executor_context_extraction.py +0 -119
  30. opalacoder-0.1.0/tests/test_new_features.py +0 -262
  31. opalacoder-0.1.0/tests/test_pipeline_integration.py +0 -225
  32. opalacoder-0.1.0/tests/test_planner_flow.py +0 -146
  33. opalacoder-0.1.0/tests/test_project_dir_isolation.py +0 -248
  34. {opalacoder-0.1.0 → opalacoder-0.1.1}/.github/workflows/publish.yml +0 -0
  35. {opalacoder-0.1.0 → opalacoder-0.1.1}/AGENT.md +0 -0
  36. {opalacoder-0.1.0 → opalacoder-0.1.1}/ALGS.md +0 -0
  37. {opalacoder-0.1.0 → opalacoder-0.1.1}/ARCH_SUMMARY.md +0 -0
  38. {opalacoder-0.1.0 → opalacoder-0.1.1}/CLAUDE.md +0 -0
  39. {opalacoder-0.1.0 → opalacoder-0.1.1}/GEMINI.md +0 -0
  40. {opalacoder-0.1.0 → opalacoder-0.1.1}/README.md +0 -0
  41. {opalacoder-0.1.0 → opalacoder-0.1.1}/analysis_results.md +0 -0
  42. {opalacoder-0.1.0 → opalacoder-0.1.1}/main.py +0 -0
  43. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/__init__.py +0 -0
  44. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/api_keys.py +0 -0
  45. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/config.py +0 -0
  46. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/embeddings.py +0 -0
  47. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/i18n.py +0 -0
  48. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/planner.py +0 -0
  49. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/session.py +0 -0
  50. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/skills.py +0 -0
  51. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/structured.py +0 -0
  52. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/terminal.py +0 -0
  53. {opalacoder-0.1.0 → opalacoder-0.1.1}/opalacoder/vcs.py +0 -0
  54. {opalacoder-0.1.0 → opalacoder-0.1.1}/rename_project.py +0 -0
  55. {opalacoder-0.1.0 → opalacoder-0.1.1}/scratch_tail.py +0 -0
  56. {opalacoder-0.1.0 → opalacoder-0.1.1}/skills/generaldeveloper.md +0 -0
  57. {opalacoder-0.1.0 → opalacoder-0.1.1}/skills/html_css_js.md +0 -0
  58. {opalacoder-0.1.0 → opalacoder-0.1.1}/skills/opalacoder.md +0 -0
  59. {opalacoder-0.1.0 → opalacoder-0.1.1}/skills/python_subprocess.md +0 -0
  60. {opalacoder-0.1.0 → opalacoder-0.1.1}/skills/react_vite.md +0 -0
  61. {opalacoder-0.1.0 → opalacoder-0.1.1}/tests/test_agent_config.py +0 -0
  62. {opalacoder-0.1.0 → opalacoder-0.1.1}/tests/test_complexity_evaluator.py +0 -0
  63. {opalacoder-0.1.0 → opalacoder-0.1.1}/tests/test_context_guard.py +0 -0
  64. {opalacoder-0.1.0 → opalacoder-0.1.1}/tests/test_double_inference.py +0 -0
  65. {opalacoder-0.1.0 → opalacoder-0.1.1}/tests/test_i18n_coverage.py +0 -0
  66. {opalacoder-0.1.0 → opalacoder-0.1.1}/tests/test_orchestrator_prompt.py +0 -0
  67. {opalacoder-0.1.0 → opalacoder-0.1.1}/tests/test_project_store.py +0 -0
  68. {opalacoder-0.1.0 → opalacoder-0.1.1}/tests/test_skills_loading.py +0 -0
@@ -0,0 +1,145 @@
1
+ # --- Current project settings ---
2
+ *.env
3
+ .env
4
+ *.env*
5
+ .venv
6
+ *.venv
7
+ node_modules
8
+ plan.md
9
+
10
+ # --- Typical Python Project settings ---
11
+ # Byte-compiled / optimized / DLL files
12
+ __pycache__/
13
+ *.py[cod]
14
+ *$py.class
15
+
16
+ # C extensions
17
+ *.so
18
+
19
+ # Distribution / packaging
20
+ .Python
21
+ build/
22
+ develop-eggs/
23
+ dist/
24
+ downloads/
25
+ eggs/
26
+ .eggs/
27
+ lib/
28
+ lib64/
29
+ parts/
30
+ sdist/
31
+ var/
32
+ wheels/
33
+ share/python-wheels/
34
+ *.egg-info/
35
+ .installed.cfg
36
+ *.egg
37
+ MANIFEST
38
+
39
+ # PyInstaller
40
+ # Usually these files are written by a python script from a template
41
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
42
+ *.manifest
43
+ *.spec
44
+
45
+ # Installer logs
46
+ pip-log.txt
47
+ pip-delete-this-directory.txt
48
+
49
+ # Unit test / coverage reports
50
+ htmlcov/
51
+ .tox/
52
+ .nox/
53
+ .coverage
54
+ .coverage.*
55
+ .cache
56
+ nosetests.xml
57
+ coverage.xml
58
+ *.cover
59
+ *.py,cover
60
+ .hypothesis/
61
+ .pytest_cache/
62
+ cover/
63
+
64
+ # Translations
65
+ *.mo
66
+ *.pot
67
+
68
+ # Django stuff:
69
+ *.log
70
+ local_settings.py
71
+ db.sqlite3
72
+ db.sqlite3-journal
73
+
74
+ # Flask stuff:
75
+ instance/
76
+ .webassets-cache
77
+
78
+ # Scrapy stuff:
79
+ .scrapy
80
+
81
+ # Sphinx documentation
82
+ docs/_build/
83
+
84
+ # PyBuilder
85
+ .pybuilder/
86
+ target/
87
+
88
+ # Jupyter Notebook
89
+ .ipynb_checkpoints
90
+
91
+ # IPython
92
+ profile_default/
93
+ ipython_config.py
94
+
95
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
96
+ __pypackages__/
97
+
98
+ # Celery stuff
99
+ celerybeat-schedule
100
+ celerybeat.pid
101
+
102
+ # SageMath parsed files
103
+ *.sage.py
104
+
105
+ # Environments
106
+ env/
107
+ venv/
108
+ ENV/
109
+ env.bak/
110
+ venv.bak/
111
+
112
+ # Spyder project settings
113
+ .spyderproject
114
+ .spyproject
115
+
116
+ # Rope project settings
117
+ .ropeproject
118
+
119
+ # mkdocs documentation
120
+ /site
121
+
122
+ # mypy
123
+ .mypy_cache/
124
+ .dmypy.json
125
+ dmypy.json
126
+
127
+ # Pyre type checker
128
+ .pyre/
129
+
130
+ # pytype static type analyzer
131
+ .pytype/
132
+
133
+ # Cython debug symbols
134
+ cython_debug/
135
+
136
+ # IDEs / Editors
137
+ .vscode/
138
+ .idea/
139
+ *.swp
140
+ *.swo
141
+ opalacoder/__pycache__/cli_commands.cpython-314.pyc
142
+ opalacoder/__pycache__/config.cpython-314.pyc
143
+ opalacoder/__pycache__/i18n.cpython-314.pyc
144
+ opalacoder/__pycache__/orchestrator.cpython-314.pyc
145
+ opalacoder/__pycache__/skills.cpython-314.pyc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opalacoder
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Autonomous coding agent with interactive planning and modular execution
5
5
  Project-URL: Homepage, https://github.com/gilzamir/OpalaCoder
6
6
  Project-URL: Bug Tracker, https://github.com/gilzamir/OpalaCoder/issues
@@ -32,7 +32,7 @@ agents:
32
32
 
33
33
  intent_classifier:
34
34
  temperature: 0
35
- max_tokens: 64
35
+ max_tokens: 20
36
36
  num_ctx: 2048
37
37
  think: false
38
38
 
@@ -0,0 +1,27 @@
1
+ import asyncio
2
+ import litellm
3
+ import os
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv(os.path.expanduser("~/.opalacoder/.env"))
7
+ MODEL = "gemini/gemini-3-flash-preview"
8
+
9
+ async def test_tool_then_system():
10
+ tc = [{"id": "call_1", "type": "function", "function": {"name": "read_file", "arguments": "{}"}}]
11
+ tc_resp = {"role": "tool", "tool_call_id": "call_1", "name": "read_file", "content": "file contents"}
12
+ messages = [
13
+ {"role": "user", "content": "read my file"},
14
+ {"role": "assistant", "tool_calls": tc},
15
+ tc_resp,
16
+ {"role": "system", "content": "SYSTEM ALERT: Memory Pressure"}
17
+ ]
18
+
19
+ try:
20
+ res = await litellm.acompletion(model=MODEL, messages=messages)
21
+ with open("direct_test_result.txt", "w") as f:
22
+ f.write("SUCCESS")
23
+ except Exception as e:
24
+ with open("direct_test_result.txt", "w") as f:
25
+ f.write(f"ERROR: {str(e)}")
26
+
27
+ asyncio.run(test_tool_then_system())
Binary file
@@ -6,10 +6,11 @@ from agenticblocks.blocks.llm.memgpt_agent import MemGPTAgentBlock
6
6
  from .config import DEFAULT_MODEL, LITELLM_DEFAULTS, get_agent_llm_kwargs, get_agent_model, get_agent_max_heartbeats, get_agent_debug
7
7
  from . import i18n
8
8
 
9
-
10
- def _make_llm(name: str, system_prompt: str, model: str | None, **kwargs) -> LLMAgentBlock:
9
+ def _make_llm(name: str, system_prompt: str, model: str | None, disable_lang_rule: bool = False, **kwargs) -> LLMAgentBlock:
11
10
  lang_name = "English" if i18n._LANG == "en" else "Portuguese"
12
- lang_rule = f"\n\nCRITICAL RULE: The user interface language is set to {lang_name}. You MUST translate your final responses, explanations, and output to {lang_name}. However, keep your internal reasoning, code variables, and logic in English."
11
+ lang_rule = ""
12
+ if not disable_lang_rule:
13
+ lang_rule = f"\n\nCRITICAL RULE: The user interface language is set to {lang_name}. You MUST translate your final responses, explanations, and output to {lang_name}. However, keep your internal reasoning, code variables, and logic in English."
13
14
 
14
15
  resolved_model = get_agent_model(name, model or DEFAULT_MODEL)
15
16
 
@@ -91,6 +92,7 @@ Read each category carefully — they have clear, non-overlapping definitions.
91
92
  Respond with ONLY ONE WORD from the list: command_hint, greetings, question, plan, chat.
92
93
  No punctuation, no explanation.""",
93
94
  model=model,
95
+ disable_lang_rule=True,
94
96
  )
95
97
 
96
98
  def make_complexity_evaluator(model: str | None = None) -> LLMAgentBlock:
@@ -105,6 +107,7 @@ Classify the user's request complexity into EXACTLY ONE of the following categor
105
107
 
106
108
  Respond with ONLY ONE WORD from the list above. No punctuation, no explanation.""",
107
109
  model=model,
110
+ disable_lang_rule=True,
108
111
  )
109
112
 
110
113
 
@@ -123,7 +126,8 @@ Output ONLY valid JSON. No markdown formatting, no comments, no extra text.
123
126
  Example: {"model": "default", "estimated_steps": 12}
124
127
  """,
125
128
  model=model,
126
- response_format={"type": "json_object"}
129
+ disable_lang_rule=True,
130
+ litellm_kwargs={"response_format": {"type": "json_object"}}
127
131
  )
128
132
 
129
133
 
@@ -183,6 +187,7 @@ Strict rules:
183
187
  Do not explain, do not add anything else. Just: yes or no.
184
188
  """,
185
189
  model=model,
190
+ disable_lang_rule=True,
186
191
  )
187
192
 
188
193
 
@@ -204,6 +209,7 @@ Rules:
204
209
  Output: the refined plan only. No preamble, no explanation.
205
210
  """,
206
211
  model=model,
212
+ disable_lang_rule=True,
207
213
  )
208
214
 
209
215
 
@@ -229,5 +235,5 @@ CRITICAL RULES:
229
235
  3. ONLY select skills whose description perfectly matches the requested technologies.
230
236
  """,
231
237
  model=model,
238
+ disable_lang_rule=True,
232
239
  )
233
-
@@ -0,0 +1,77 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ # Singleton para evitar carregar o ChromaDB múltiplas vezes
5
+ _chroma_client = None
6
+
7
+ def _get_chroma_client():
8
+ global _chroma_client
9
+ if _chroma_client is None:
10
+ try:
11
+ import chromadb
12
+ except ImportError:
13
+ raise ImportError("Please install chromadb: pip install chromadb")
14
+
15
+ # O banco será salvo no mesmo diretório global do projects.db
16
+ # ex: ~/.opalacoder/chroma
17
+ db_path = Path.home() / ".opalacoder" / "chroma"
18
+ db_path.mkdir(parents=True, exist_ok=True)
19
+
20
+ _chroma_client = chromadb.PersistentClient(path=str(db_path))
21
+ return _chroma_client
22
+
23
+ def get_collection(project_name: str):
24
+ client = _get_chroma_client()
25
+ # Nomes de coleções no Chroma precisam ser alfanuméricos curtos
26
+ safe_name = "".join(c if c.isalnum() else "_" for c in project_name).strip("_")
27
+ if not safe_name:
28
+ safe_name = "default_project"
29
+ return client.get_or_create_collection(name=safe_name)
30
+
31
+ def append_to_archival(project_name: str, message_id: str, role: str, content: str, timestamp: str):
32
+ """
33
+ Adiciona uma mensagem ao Archival Memory (ChromaDB) do projeto.
34
+ """
35
+ if not content or not content.strip():
36
+ return
37
+
38
+ collection = get_collection(project_name)
39
+
40
+ metadata = {
41
+ "role": role,
42
+ "timestamp": timestamp
43
+ }
44
+
45
+ collection.add(
46
+ documents=[content],
47
+ metadatas=[metadata],
48
+ ids=[f"{message_id}"]
49
+ )
50
+
51
+ def search_archival(project_name: str, query: str, limit: int = 5) -> list[dict]:
52
+ """
53
+ Pesquisa o histórico usando similaridade de cosseno via ChromaDB.
54
+ """
55
+ try:
56
+ collection = get_collection(project_name)
57
+ results = collection.query(
58
+ query_texts=[query],
59
+ n_results=limit
60
+ )
61
+
62
+ out = []
63
+ if results and "documents" in results and results["documents"]:
64
+ docs = results["documents"][0]
65
+ metas = results["metadatas"][0] if "metadatas" in results else []
66
+ for i, doc in enumerate(docs):
67
+ meta = metas[i] if i < len(metas) else {}
68
+ out.append({
69
+ "content": doc,
70
+ "role": meta.get("role", "unknown"),
71
+ "timestamp": meta.get("timestamp", "")
72
+ })
73
+ return out
74
+ except Exception as e:
75
+ import traceback
76
+ traceback.print_exc()
77
+ return []
@@ -97,10 +97,10 @@ async def _create_project(store: ProjectStore, args) -> ProjectData:
97
97
  # ─── REPL Loop ────────────────────────────────────────────────────────────────
98
98
 
99
99
  async def repl_loop(project: ProjectData, store: ProjectStore, max_retries: int) -> None:
100
- from .tools import set_project_path
100
+ from .tools import set_project_context
101
101
  from .skills import load_project_skills
102
102
 
103
- set_project_path(project.project_path)
103
+ set_project_context(project, store)
104
104
  project_skills = load_project_skills(project.project_path, project.skills)
105
105
 
106
106
  T.section(f"Active Project: {_escape(project.project_name or project.name)}")
@@ -118,6 +118,18 @@ async def repl_loop(project: ProjectData, store: ProjectStore, max_retries: int)
118
118
  else:
119
119
  state.project.clear_state()
120
120
  store.save(state.project)
121
+ else:
122
+ checkpoint_path = os.path.join(project.project_path, ".opalacoder", "session_state.json")
123
+ if os.path.exists(checkpoint_path):
124
+ T.warning("[yellow]Foi detectada uma execução de agente não finalizada (checkpoint salvo).[/yellow]")
125
+ choice = T.choose(_("resume_or_clear"), [_("resume"), _("clear")])
126
+ if choice == _("resume"):
127
+ await run_pipeline(state.project, store, max_retries, request="[RESUME_EXECUTION]", project_skills=state.project_skills)
128
+ else:
129
+ try:
130
+ os.remove(checkpoint_path)
131
+ except Exception:
132
+ pass
121
133
 
122
134
  while True:
123
135
  try:
@@ -142,7 +154,7 @@ async def repl_loop(project: ProjectData, store: ProjectStore, max_retries: int)
142
154
  with T.spinner(_("agent_thinking")):
143
155
  intent_res = await classifier.run(AgentInput(prompt=user_input))
144
156
  _raw = intent_res.response.strip().lower()
145
- intent = _raw.split()[0].rstrip(".,!?") if _raw else ""
157
+ intent = _raw.split()[0].strip(".,!?*\"'") if _raw else ""
146
158
 
147
159
 
148
160
  if not intent or intent not in _VALID_INTENTS:
@@ -110,7 +110,7 @@ async def cmd_list(state: REPLState, _args: list[str]) -> None:
110
110
 
111
111
  @_registry.register("/load", usage="<name>", description="Load another project")
112
112
  async def cmd_load(state: REPLState, args: list[str]) -> str | None:
113
- from .tools import set_project_path
113
+ from .tools import set_project_context
114
114
  from .skills import load_project_skills
115
115
  if not args:
116
116
  T.error("Usage: /load <name>")
@@ -122,7 +122,7 @@ async def cmd_load(state: REPLState, args: list[str]) -> str | None:
122
122
  loaded = state.store.load(name)
123
123
  if loaded:
124
124
  state.project = loaded
125
- set_project_path(state.project.project_path)
125
+ set_project_context(state.project, state.store)
126
126
  state.project_skills = load_project_skills(state.project.project_path, state.project.skills)
127
127
  state.chat_agent = make_chat_memgpt_agent(state.project.model)
128
128
  T.success(f"Project '{name}' loaded.")