opalacoder 0.1.0__py3-none-any.whl

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.
opalacoder/config.py ADDED
@@ -0,0 +1,215 @@
1
+ """Global configuration defaults for OpalaCoder."""
2
+
3
+ import os
4
+ import yaml
5
+ import pathlib
6
+ from dotenv import load_dotenv
7
+
8
+ # Load local .env
9
+ load_dotenv()
10
+
11
+ # Suppress the non-fatal LiteLLM logging worker warning (coroutine never awaited)
12
+ # which frequently occurs in asynchronous contexts on newer Python versions.
13
+ import warnings
14
+ warnings.filterwarnings(
15
+ "ignore",
16
+ category=RuntimeWarning,
17
+ message=".*coroutine 'Logging.async_success_handler' was never awaited.*"
18
+ )
19
+
20
+ # Load global .env
21
+ global_env = pathlib.Path.home() / ".opalacoder" / ".env"
22
+ if global_env.exists():
23
+ load_dotenv(dotenv_path=global_env)
24
+
25
+ def _load_agents_config() -> dict:
26
+ # Search for agents.yaml next to this file (project root), then fall back to cwd.
27
+ candidates = [
28
+ pathlib.Path(__file__).parent / "agents.yaml",
29
+ pathlib.Path(__file__).parent.parent / "agents.yaml",
30
+ pathlib.Path(os.getcwd()) / "agents.yaml",
31
+ ]
32
+ for config_path in candidates:
33
+ if config_path.exists():
34
+ try:
35
+ with open(config_path, "r", encoding="utf-8") as f:
36
+ return yaml.safe_load(f) or {}
37
+ except Exception as e:
38
+ print(f"Warning: Failed to parse {config_path}: {e}")
39
+ return {}
40
+
41
+ _AGENTS_CONFIG = _load_agents_config()
42
+
43
+ # Model used for all agents (can be overridden via CLI --model)
44
+ DEFAULT_MODEL = _AGENTS_CONFIG.get("default", os.getenv("OPALA_MODEL", "ollama/mistral-nemo"))
45
+ ALTERNATIVE_MODEL = _AGENTS_CONFIG.get("alternative", "gemini/gemini-3.1-flash-lite")
46
+
47
+ # Global LLM defaults (temperature, max_tokens, num_ctx) — can be set in agents.yaml
48
+ _LLM_DEFAULTS: dict = {
49
+ "temperature": 0.7,
50
+ "max_tokens": 4096,
51
+ "num_ctx": 8192,
52
+ **_AGENTS_CONFIG.get("llm_defaults", {}),
53
+ }
54
+
55
+ # Per-agent overrides loaded from agents.yaml
56
+ _AGENT_OVERRIDES: dict[str, dict] = _AGENTS_CONFIG.get("agents", {})
57
+
58
+
59
+ # Fields that are consumed outside of litellm kwargs and must not be forwarded.
60
+ _NON_LITELLM_FIELDS = {"model", "max_heartbeats", "debug", "strategy"}
61
+
62
+
63
+ from typing import Union
64
+
65
+ def get_git_strategy() -> str:
66
+ """Return the git strategy configured in agents.yaml ('hybrid', 'agent_driven', 'auto', 'none')."""
67
+ return _AGENTS_CONFIG.get("git_strategy", "hybrid")
68
+
69
+ def get_complexity_inference_mode() -> str:
70
+ """Return the complexity inference mode (simple or double)."""
71
+ return _AGENTS_CONFIG.get("complexity_inference_mode", "simple")
72
+
73
+ def get_agent_max_heartbeats(agent_name: str, default: int) -> Union[int, str]:
74
+ """Return max_heartbeats configured for *agent_name* in agents.yaml (can be 'auto'), or *default*."""
75
+ val = _AGENT_OVERRIDES.get(agent_name, {}).get("max_heartbeats", default)
76
+ if val == "auto":
77
+ return "auto"
78
+ return int(val)
79
+
80
+
81
+ def get_agent_debug(agent_name: str, default: bool = False) -> bool:
82
+ """Return debug flag configured for *agent_name* in agents.yaml, or *default*."""
83
+ return bool(_AGENT_OVERRIDES.get(agent_name, {}).get("debug", default))
84
+
85
+
86
+ def get_agent_model(agent_name: str, default: str | None = None) -> str:
87
+ """Return the model configured for *agent_name* in agents.yaml, or *default*."""
88
+ override = _AGENT_OVERRIDES.get(agent_name, {}).get("model")
89
+ if override:
90
+ return override
91
+ return default if default is not None else DEFAULT_MODEL
92
+
93
+
94
+ def get_agent_llm_kwargs(agent_name: str) -> dict:
95
+ """Return merged litellm kwargs for *agent_name*.
96
+
97
+ Priority (highest first):
98
+ 1. Per-agent override in agents.yaml ``agents.<name>``
99
+ 2. Global ``llm_defaults`` in agents.yaml
100
+ 3. Hard-coded defaults above
101
+
102
+ Non-litellm fields (model, max_heartbeats) are excluded.
103
+ """
104
+ merged = dict(_LLM_DEFAULTS)
105
+ merged.update(_AGENT_OVERRIDES.get(agent_name, {}))
106
+ for field in _NON_LITELLM_FIELDS:
107
+ merged.pop(field, None)
108
+ return merged
109
+
110
+ # Maximum retry attempts for a failing subplan step
111
+ DEFAULT_MAX_RETRIES = 3
112
+
113
+ # MemGPT heartbeat budget per planning turn
114
+ DEFAULT_MAX_HEARTBEATS = 15
115
+
116
+ # SQLite database file for session persistence
117
+ DEFAULT_DB_PATH = os.path.join(
118
+ os.path.expanduser("~"), ".opalacoder", "sessions.db"
119
+ )
120
+
121
+ # Execution mode: "auto" | "plan" | "edit"
122
+ DEFAULT_MODE = "plan"
123
+
124
+ def _get_system_lang() -> str:
125
+ env_lang = os.getenv("OPALA_LANG")
126
+ if env_lang in ("en", "pt"):
127
+ return env_lang
128
+
129
+ try:
130
+ for var in ("LC_ALL", "LC_CTYPE", "LANG"):
131
+ val = os.getenv(var)
132
+ if val and len(val) >= 2:
133
+ lang_code = val[:2].lower()
134
+ if lang_code in ("en", "pt"):
135
+ return lang_code
136
+
137
+ import locale
138
+ loc, _ = locale.getdefaultlocale()
139
+ if loc and len(loc) >= 2:
140
+ lang_code = loc[:2].lower()
141
+ if lang_code in ("en", "pt"):
142
+ return lang_code
143
+ except Exception:
144
+ pass
145
+
146
+ return "en"
147
+
148
+ # Default Language
149
+ DEFAULT_LANG = _get_system_lang()
150
+
151
+ # Default litellm kwargs applied to all local model calls (kept for back-compat)
152
+ LITELLM_DEFAULTS: dict = {"num_ctx": _LLM_DEFAULTS["num_ctx"]}
153
+
154
+ # Sensitive operations that require user approval in "edit" mode
155
+ SENSITIVE_OPS = {
156
+ "write_file", "delete_file", "run_shell",
157
+ "send_network_request", "create_user", "delete_user",
158
+ }
159
+
160
+ # ─── Debug Logging ────────────────────────────────────────────────────────────
161
+
162
+ def setup_litellm_debug():
163
+ import litellm
164
+ import logging
165
+ from datetime import datetime
166
+
167
+ log_dir = os.path.join(os.path.expanduser("~"), ".opalacoder", "logs")
168
+ os.makedirs(log_dir, exist_ok=True)
169
+ log_file = os.path.join(log_dir, "llm_debug.log")
170
+
171
+ # Configure litellm to use our logger
172
+ litellm.set_verbose = True
173
+
174
+ logger = logging.getLogger("LiteLLM")
175
+ logger.setLevel(logging.DEBUG)
176
+
177
+ # File handler
178
+ fh = logging.FileHandler(log_file, encoding='utf-8')
179
+ fh.setLevel(logging.DEBUG)
180
+
181
+ # Formatter
182
+ formatter = logging.Formatter('\n' + '='*80 + '\n[%(asctime)s] %(message)s\n' + '='*80)
183
+ fh.setFormatter(formatter)
184
+
185
+ # Remove existing handlers to avoid duplicates if called multiple times
186
+ if logger.handlers:
187
+ logger.handlers.clear()
188
+
189
+ logger.addHandler(fh)
190
+
191
+ # Custom callback to capture inputs and outputs cleanly
192
+ def custom_callback(
193
+ kwargs, completion_response, start_time, end_time
194
+ ):
195
+ messages = kwargs.get("messages", [])
196
+ model = kwargs.get("model", "unknown")
197
+
198
+ log_text = f"MODEL: {model}\n\n=== PROMPT ===\n"
199
+ for msg in messages:
200
+ role = msg.get("role", "unknown").upper()
201
+ content = msg.get("content", "")
202
+ log_text += f"[{role}]:\n{content}\n\n"
203
+
204
+ if completion_response:
205
+ try:
206
+ response_text = completion_response.choices[0].message.content
207
+ log_text += f"=== RESPONSE ===\n{response_text}"
208
+ except Exception:
209
+ log_text += f"=== RAW RESPONSE ===\n{completion_response}"
210
+
211
+ logger.debug(log_text)
212
+
213
+ litellm.success_callback = [custom_callback]
214
+ litellm.failure_callback = [custom_callback]
215
+
@@ -0,0 +1,85 @@
1
+ import os
2
+ import numpy as np
3
+ from typing import Optional
4
+
5
+ # Singleton pattern for the embedding model to avoid reloading it
6
+ _model = None
7
+
8
+ def _get_model():
9
+ global _model
10
+ if _model is None:
11
+ try:
12
+ from sentence_transformers import SentenceTransformer
13
+ except ImportError:
14
+ raise ImportError("Please install sentence-transformers: pip install sentence-transformers")
15
+
16
+ # We use a lightweight multilingual model
17
+ # It downloads ~470MB on first use and caches it locally
18
+ model_name = "paraphrase-multilingual-MiniLM-L12-v2"
19
+ _model = SentenceTransformer(model_name)
20
+ return _model
21
+
22
+ def get_embedding(text: str) -> np.ndarray:
23
+ """Returns the embedding vector for the given text."""
24
+ if not text.strip():
25
+ # Return a zero vector of size 384 (standard for MiniLM)
26
+ return np.zeros(384, dtype=np.float32)
27
+ model = _get_model()
28
+ # encode() returns a numpy array
29
+ return model.encode(text)
30
+
31
+ def cosine_similarity(vec1: np.ndarray, vec2: np.ndarray) -> float:
32
+ """Computes the cosine similarity between two vectors."""
33
+ norm1 = np.linalg.norm(vec1)
34
+ norm2 = np.linalg.norm(vec2)
35
+ if norm1 == 0 or norm2 == 0:
36
+ return 0.0
37
+ return float(np.dot(vec1, vec2) / (norm1 * norm2))
38
+
39
+ # Intent Anchors (Multilingual)
40
+ INTENT_ANCHORS = {
41
+ "chat": [
42
+ "hello", "hi", "how are you", "what are the commands", "list", "help", "clear",
43
+ "olá", "oi", "quais os comandos", "listar", "ajuda", "limpar", "tudo bem",
44
+ "hola", "ayuda", "comandos"
45
+ ],
46
+ "plan": [
47
+ "create an app", "write code", "build a website", "refactor the function", "make a script",
48
+ "crie um aplicativo", "escreva o código", "faça um script", "construa um site", "refatorar",
49
+ "crea una aplicación", "escribe el código", "construye"
50
+ ],
51
+ "question": [
52
+ "how does python work", "what is a closure", "explain react hooks", "why is this happening",
53
+ "como o python funciona", "o que é isso", "explique", "por que isso acontece",
54
+ "cómo funciona", "qué es", "explica"
55
+ ]
56
+ }
57
+
58
+ _intent_embeddings = {}
59
+
60
+ def classify_intent_embedded(text: str) -> str:
61
+ """Classifies intent using cosine similarity against multilingual anchors."""
62
+ global _intent_embeddings
63
+ if not _intent_embeddings:
64
+ # Lazy initialization
65
+ for intent, phrases in INTENT_ANCHORS.items():
66
+ _intent_embeddings[intent] = [get_embedding(p) for p in phrases]
67
+
68
+ text_vec = get_embedding(text)
69
+
70
+ best_intent = "chat"
71
+ highest_sim = -1.0
72
+
73
+ for intent, vec_list in _intent_embeddings.items():
74
+ for anchor_vec in vec_list:
75
+ sim = cosine_similarity(text_vec, anchor_vec)
76
+ if sim > highest_sim:
77
+ highest_sim = sim
78
+ best_intent = intent
79
+
80
+ # If the highest similarity is very low, default to chat
81
+ if highest_sim < 0.2:
82
+ return "chat"
83
+
84
+ return best_intent
85
+
opalacoder/i18n.py ADDED
@@ -0,0 +1,249 @@
1
+ """Internationalization support for OpalaCoder CLI."""
2
+
3
+ _LANG = "en"
4
+
5
+ _STRINGS = {
6
+ "en": {
7
+ "active_session": "Active Session: {name}",
8
+ "default_session_warning": "You are in the 'default' session. It is good practice to use '/rename <new_name>' to save it with a meaningful name.",
9
+ "type_help": "Type '/help' to see available commands.",
10
+ "pending_demand": "Session has an ongoing demand: '{request}...' \n[resume/clear]",
11
+ "resume_or_clear": "Do you want to resume execution or clear and start over?",
12
+ "resume": "Resume",
13
+ "clear": "Clear",
14
+ "available_commands": "Available Commands:",
15
+ "help_desc": "Shows this message",
16
+ "rename_desc": "Renames the current session",
17
+ "list_desc": "Lists all sessions",
18
+ "load_desc": "Loads another session",
19
+ "delete_desc": "Deletes a session",
20
+ "exit_desc": "Exits OpalaCoder",
21
+ "undo_desc": "Reverts the last change made by the agent",
22
+ "commit_desc": "Forces a commit to the local shadow git control",
23
+ "undo_success": "Last change successfully undone.",
24
+ "undo_fail": "Cannot undo: no previous checkpoints or VCS is disabled.",
25
+ "commit_success": "Changes successfully committed.",
26
+ "commit_fail": "Cannot commit: {err}",
27
+ "usage_rename": "Usage: /rename <new_name>",
28
+ "session_renamed": "Session renamed to '{name}'.",
29
+ "session_exists": "A session with the name '{name}' already exists.",
30
+ "no_sessions": "No sessions found.",
31
+ "existing_sessions": "Existing sessions:",
32
+ "usage_load": "Usage: /load <name>",
33
+ "session_not_found": "Session '{name}' not found.",
34
+ "session_loaded": "Session '{name}' loaded.",
35
+ "usage_delete": "Usage: /delete <name>",
36
+ "session_deleted": "Session '{name}' deleted.",
37
+ "current_deleted": "The current session was deleted. Loading 'default'.",
38
+ "exiting": "Exiting OpalaCoder. Goodbye!",
39
+ "unknown_command": "Unknown command: {cmd}. Type /help for help.",
40
+ "agent_thinking": "Agent thinking...",
41
+ "agent_plan_triggered": "The agent triggered the planning mode for the demand: '{req}'",
42
+ "repl_interrupted": "\nInterrupted by user. Session saved.",
43
+ "repl_cancelled": "\nOperation cancelled by user. Returning to REPL.",
44
+ "unexpected_error": "Unexpected error: {err}",
45
+ "resuming_session": "Resuming session with request: {req}",
46
+ "prev_session_plan": "Previous Session Plan",
47
+ "new_demand": "New Demand",
48
+ "phase1": "Phase 1 — Landscape",
49
+ "phase2": "Phase 2 — Plan Refinement",
50
+ "plan_auto_mode": "Plan (auto mode)",
51
+ "phase3": "Phase 3 — Decomposition",
52
+ "no_subplans": "No subplans extracted. Exiting.",
53
+ "identified_subplans": "Identified subplans: {ids}",
54
+ "phase4": "Phase 4 — Execution",
55
+ "phase5": "Phase 5 — Final Result",
56
+ "invalid_option": "Invalid option, try again.",
57
+ "table_id": "ID",
58
+ "table_objective": "Objective",
59
+ "table_status": "Status",
60
+ "generated_plan": "Generated Plan",
61
+ "final_result": "Final Result",
62
+ "exec_errors": "Execution Errors",
63
+ "subplan": "Subplan",
64
+ "error": "Error",
65
+ "plan_ok": "Is the plan OK? (answer 'yes' or 'y' to approve, or describe the desired changes)",
66
+ "evaluating_results": "Evaluating results...",
67
+ "exec_failed": "Execution failed: {err}",
68
+ "no_code_generated": "No code generated.",
69
+ "exec_success": "Execution completed successfully.",
70
+ "exec_error_occurred": "Error during execution: {err}",
71
+ "tool_start_planning": "Starts code planning and execution. Use this tool ONLY when the user explicitly asks to create code, a script, refactor, or execute a software project. Pass the description of what the user wants in the 'request' parameter.",
72
+ "tool_planning_activated": "Planning mode activated. Control will be transferred to the code orchestrator.",
73
+ "recent_history": "RECENT CONVERSATION HISTORY:",
74
+ "new_message": "NEW USER MESSAGE:",
75
+ "assistant": "Assistant",
76
+ "user": "User",
77
+ "yes_hints": ["y", "yes", "sim", "s"],
78
+ "generating_panorama": "Generating landscape plan...",
79
+ "plan_review": "Plan Review",
80
+ "plan_approved": "Plan approved!",
81
+ "interpreting_response": "Interpreting response...",
82
+ "refining_plan": "Refining plan based on your feedback...",
83
+ "refining": "Refining...",
84
+ "max_refinement_cycles": "Max refinement cycles reached. Using the last plan.",
85
+ "decomposing_plan": "Decomposing plan into subtasks...",
86
+ "decomposing": "Decomposing...",
87
+ "no_subplan_returned": "No subplan returned by the model.",
88
+ "refinement_prompt": "ORIGINAL REQUEST: {request}\nORIGINAL PLAN: {plan_text}\nUSER FEEDBACK: {feedback}",
89
+ "sensitive_op": "{id} contains sensitive operation: {obj}",
90
+ "execute_subplan": "Execute {id}?",
91
+ "skipped_by_user": "[SKIPPED by user]",
92
+ "executing_subplans": "Executing Subplans (AgenticBlocks WorkflowGraph)",
93
+ "completed": "completed",
94
+ "failed": "failed",
95
+ "aggregating_results": "Aggregating results...",
96
+ "synthesizing_final_result": "Synthesizing final result...",
97
+ "confirm_plan_execution": "Are you sure you want to start the planning phase? This might be an expensive operation.",
98
+ "plan_execution_cancelled": "Planning execution cancelled.",
99
+ "plan_execution_cancelled_msg": "I offered to start the planning phase, but you cancelled it. Let me know if you need anything else.",
100
+ "cancel_reminder": "💡 Tip: You can type /cancel at any time to abort the planning and return to the chat.",
101
+ "plan_saved_to_file": "Plan saved to '{path}'. Edit it in your editor, save it, and then come back here.",
102
+ "plan_confirm_after_edit": "Press Enter when you are done editing (or type your changes here, or 'yes'/'y' to approve as-is):",
103
+ "plan_file_read_error": "Could not read plan.md: {err}. Using the in-memory plan.",
104
+ "intent_unclear": "Your message wasn't clear enough. Could you please rephrase or provide more details?",
105
+ "command_hint_suggestion": "Did you mean [bold]/{cmd}[/bold]? Commands must be prefixed with [bold]/[/bold]. Type [bold]/help[/bold] to see all available commands.",
106
+ "evaluating_complexity": "Evaluating task complexity...",
107
+ "selecting_skills": "Selecting skills for this project...",
108
+ "routing_complex_task": "Complex task detected. Routing to alternative model ({model})...",
109
+ "api_key_missing_fallback": "API Key missing or skipped. Falling back to {model}.",
110
+ "alt_model_error": "Error using alternative model ({model}): {err}",
111
+ "fallback_to_model": "Falling back to {model}",
112
+ "using_model": "Using model: {model}",
113
+ "delete_dir_confirm": "Do you also want to delete the project directory ({path})?",
114
+ "dir_deleted": "Project directory '{path}' deleted.",
115
+ "dir_delete_failed": "Failed to delete directory: {err}",
116
+ "vcs_deleted": "Internal version control deleted.",
117
+ "vcs_delete_failed": "Failed to delete internal version control: {err}",
118
+ },
119
+ "pt": {
120
+ "active_session": "Sessão Ativa: {name}",
121
+ "default_session_warning": "Você está na sessão 'default'. É uma boa prática usar '/rename <novo_nome>' para salvar com um nome significativo.",
122
+ "type_help": "Digite '/help' para ver os comandos disponíveis.",
123
+ "pending_demand": "Sessão possui uma demanda em andamento: '{request}...'",
124
+ "resume_or_clear": "Deseja retomar a execução ou limpar e começar de novo?",
125
+ "resume": "Retomar",
126
+ "clear": "Limpar",
127
+ "available_commands": "Comandos Disponíveis:",
128
+ "help_desc": "Mostra esta mensagem",
129
+ "rename_desc": "Renomeia a sessão atual",
130
+ "list_desc": "Lista todas as sessões",
131
+ "load_desc": "Carrega outra sessão",
132
+ "delete_desc": "Apaga uma sessão",
133
+ "exit_desc": "Sai do OpalaCoder",
134
+ "undo_desc": "Desfaz a última alteração feita pelo agente",
135
+ "commit_desc": "Força o envio das alterações para o controle local",
136
+ "undo_success": "Última alteração desfeita com sucesso.",
137
+ "undo_fail": "Não foi possível desfazer: sem checkpoints anteriores ou controle de versão desativado.",
138
+ "commit_success": "Alterações enviadas com sucesso.",
139
+ "commit_fail": "Não foi possível enviar: {err}",
140
+ "usage_rename": "Uso: /rename <novo_nome>",
141
+ "session_renamed": "Sessão renomeada para '{name}'.",
142
+ "session_exists": "Já existe uma sessão com o nome '{name}'.",
143
+ "no_sessions": "Nenhuma sessão encontrada.",
144
+ "existing_sessions": "Sessões existentes:",
145
+ "usage_load": "Uso: /load <nome>",
146
+ "session_not_found": "Sessão '{name}' não encontrada.",
147
+ "session_loaded": "Sessão '{name}' carregada.",
148
+ "usage_delete": "Uso: /delete <nome>",
149
+ "session_deleted": "Sessão '{name}' apagada.",
150
+ "current_deleted": "A sessão atual foi apagada. Carregando 'default'.",
151
+ "exiting": "Encerrando OpalaCoder. Até logo!",
152
+ "unknown_command": "Comando desconhecido: {cmd}. Digite /help para ajuda.",
153
+ "agent_thinking": "Agente pensando...",
154
+ "agent_plan_triggered": "O agente acionou o modo de planejamento para a demanda: '{req}'",
155
+ "repl_interrupted": "\nInterrompido pelo usuário. Sessão salva.",
156
+ "repl_cancelled": "\nOperação cancelada pelo usuário. Retornando ao REPL.",
157
+ "unexpected_error": "Erro inesperado: {err}",
158
+ "resuming_session": "Retomando sessão com pedido: {req}",
159
+ "prev_session_plan": "Plano da Sessão Anterior",
160
+ "new_demand": "Nova Demanda",
161
+ "phase1": "Fase 1 — Panorama",
162
+ "phase2": "Fase 2 — Refinamento do Plano",
163
+ "plan_auto_mode": "Plano (modo auto)",
164
+ "phase3": "Fase 3 — Decomposição",
165
+ "no_subplans": "Nenhum subplano extraído. Encerrando.",
166
+ "identified_subplans": "Subplanos identificados: {ids}",
167
+ "phase4": "Fase 4 — Execução",
168
+ "phase5": "Fase 5 — Resultado Final",
169
+ "invalid_option": "Opção inválida, tente novamente.",
170
+ "table_id": "ID",
171
+ "table_objective": "Objetivo",
172
+ "table_status": "Status",
173
+ "generated_plan": "Plano Gerado",
174
+ "final_result": "Resultado Final",
175
+ "exec_errors": "Erros de Execução",
176
+ "subplan": "Subplano",
177
+ "error": "Erro",
178
+ "plan_ok": "O plano está ok? (responda 'sim' ou 's' para aprovar, ou descreva as alterações desejadas)",
179
+ "evaluating_results": "Avaliando resultados...",
180
+ "exec_failed": "Falhou: {err}",
181
+ "no_code_generated": "Nenhum código gerado.",
182
+ "exec_success": "Execução concluída com sucesso.",
183
+ "exec_error_occurred": "Erro durante a execução: {err}",
184
+ "tool_start_planning": "Inicia o planejamento e execução de código. Use esta ferramenta APENAS quando o usuário pedir explicitamente para criar código, um script, refatorar, ou executar um projeto de software. Passe a descrição do que o usuário quer no parâmetro 'request'.",
185
+ "tool_planning_activated": "Modo de planejamento ativado. O controle será transferido para o orquestrador de código.",
186
+ "recent_history": "HISTÓRICO RECENTE DA CONVERSA:",
187
+ "new_message": "NOVA MENSAGEM DO USUÁRIO:",
188
+ "assistant": "Assistente",
189
+ "user": "Usuário",
190
+ "yes_hints": ["s", "sim", "y", "yes"],
191
+ "generating_panorama": "Gerando panorama do plano...",
192
+ "plan_review": "Revisão do Plano",
193
+ "plan_approved": "Plano aprovado!",
194
+ "interpreting_response": "Interpretando resposta...",
195
+ "refining_plan": "Refinando plano com base no seu feedback...",
196
+ "refining": "Refinando...",
197
+ "max_refinement_cycles": "Número máximo de ciclos de refinamento atingido. Usando último plano.",
198
+ "decomposing_plan": "Decompondo plano em subetapas...",
199
+ "decomposing": "Decompondo...",
200
+ "no_subplan_returned": "Nenhum subplano retornado pelo modelo.",
201
+ "refinement_prompt": "PEDIDO ORIGINAL: {request}\nPLANO ORIGINAL: {plan_text}\nFEEDBACK DO USUÁRIO: {feedback}",
202
+ "sensitive_op": "{id} contém operação sensível: {obj}",
203
+ "execute_subplan": "Executar {id}?",
204
+ "skipped_by_user": "[PULADO pelo usuário]",
205
+ "executing_subplans": "Executando Subplanos (AgenticBlocks WorkflowGraph)",
206
+ "completed": "concluído",
207
+ "failed": "falhou",
208
+ "aggregating_results": "Agregando resultados...",
209
+ "synthesizing_final_result": "Sintetizando resultado final...",
210
+ "confirm_plan_execution": "Você tem certeza que deseja iniciar a fase de planejamento? Esta pode ser uma operação demorada/custosa.",
211
+ "plan_execution_cancelled": "Execução do planejamento cancelada.",
212
+ "plan_execution_cancelled_msg": "Eu ofereci iniciar a fase de planejamento, mas você cancelou. Me avise se precisar de mais alguma coisa.",
213
+ "cancel_reminder": "💡 Dica: Você pode digitar /cancel a qualquer momento para abortar o planejamento e voltar ao chat.",
214
+ "plan_saved_to_file": "Plano salvo em '{path}'. Edite-o no seu editor, salve e depois volte aqui.",
215
+ "plan_confirm_after_edit": "Pressione Enter quando terminar de editar (ou descreva as alterações aqui, ou 'sim'/'s' para aprovar como está):",
216
+ "plan_file_read_error": "Não foi possível ler plan.md: {err}. Usando o plano em memória.",
217
+ "intent_unclear": "Sua mensagem não ficou clara o suficiente. Poderia reformulá-la ou fornecer mais detalhes?",
218
+ "command_hint_suggestion": "Você quis dizer [bold]/{cmd}[/bold]? Comandos devem ser prefixados com [bold]/[/bold]. Digite [bold]/help[/bold] para ver todos os comandos disponíveis.",
219
+ "evaluating_complexity": "Avaliando complexidade da tarefa...",
220
+ "selecting_skills": "Selecionando skills para este projeto...",
221
+ "routing_complex_task": "Tarefa complexa detectada. Roteando para modelo alternativo ({model})...",
222
+ "api_key_missing_fallback": "API Key ausente ou ignorada. Usando {model} como fallback.",
223
+ "alt_model_error": "Erro ao usar modelo alternativo ({model}): {err}",
224
+ "fallback_to_model": "Usando {model} como fallback",
225
+ "using_model": "Usando o modelo: {model}",
226
+ "delete_dir_confirm": "Deseja deletar também o diretório do projeto ({path})?",
227
+ "dir_deleted": "Diretório do projeto '{path}' apagado.",
228
+ "dir_delete_failed": "Falha ao apagar o diretório: {err}",
229
+ "vcs_deleted": "Controle de versão interno apagado.",
230
+ "vcs_delete_failed": "Falha ao apagar controle de versão interno: {err}",
231
+ }
232
+ }
233
+
234
+ def set_lang(lang: str):
235
+ global _LANG
236
+ if lang in _STRINGS:
237
+ _LANG = lang
238
+
239
+ def get_lang() -> str:
240
+ return _LANG
241
+
242
+ def get_lang_name() -> str:
243
+ return "Portuguese" if _LANG == "pt" else "English"
244
+
245
+ def _(key: str, **kwargs):
246
+ val = _STRINGS[_LANG].get(key, _STRINGS["en"].get(key, key))
247
+ if isinstance(val, str) and kwargs:
248
+ return val.format(**kwargs)
249
+ return val