mycode-coding-agent 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.
Files changed (121) hide show
  1. mycode/__init__.py +0 -0
  2. mycode/adapters/__init__.py +21 -0
  3. mycode/adapters/jsonl.py +692 -0
  4. mycode/agent/__init__.py +25 -0
  5. mycode/agent/events.py +111 -0
  6. mycode/agent/outcome.py +103 -0
  7. mycode/agent/progress.py +373 -0
  8. mycode/agent/runner.py +1481 -0
  9. mycode/application/__init__.py +38 -0
  10. mycode/application/agent_session.py +367 -0
  11. mycode/application/events.py +59 -0
  12. mycode/application/runtime.py +211 -0
  13. mycode/application/sessions.py +180 -0
  14. mycode/cli.py +840 -0
  15. mycode/config.py +355 -0
  16. mycode/context/__init__.py +1 -0
  17. mycode/context/artifacts.py +672 -0
  18. mycode/context/budget.py +752 -0
  19. mycode/context/builder.py +112 -0
  20. mycode/context/compact.py +795 -0
  21. mycode/context/tool_result_format.py +199 -0
  22. mycode/context/tool_result_retention.py +261 -0
  23. mycode/conversation.py +78 -0
  24. mycode/error_handling.py +481 -0
  25. mycode/event_format.py +147 -0
  26. mycode/instructions.py +285 -0
  27. mycode/llm.py +771 -0
  28. mycode/mcp/__init__.py +41 -0
  29. mycode/mcp/client.py +44 -0
  30. mycode/mcp/config.py +207 -0
  31. mycode/mcp/errors.py +302 -0
  32. mycode/mcp/manager.py +339 -0
  33. mycode/mcp/models.py +20 -0
  34. mycode/mcp/result_adapter.py +58 -0
  35. mycode/mcp/tool_adapter.py +145 -0
  36. mycode/mcp/trust.py +313 -0
  37. mycode/memory.py +570 -0
  38. mycode/memory_context.py +245 -0
  39. mycode/messages.py +63 -0
  40. mycode/observability.py +28 -0
  41. mycode/permissions.py +262 -0
  42. mycode/persistence/__init__.py +1 -0
  43. mycode/persistence/filesystem.py +291 -0
  44. mycode/persistence/project_storage.py +208 -0
  45. mycode/persistence/session_lock.py +138 -0
  46. mycode/persistence/session_store.py +503 -0
  47. mycode/presentation/__init__.py +1 -0
  48. mycode/presentation/cli/__init__.py +14 -0
  49. mycode/presentation/cli/confirmer.py +116 -0
  50. mycode/presentation/cli/mcp_trust.py +61 -0
  51. mycode/presentation/cli/presenter.py +320 -0
  52. mycode/presentation/cli/session_menu.py +146 -0
  53. mycode/presentation/cli/subagent_observer.py +124 -0
  54. mycode/presentation/command_format.py +90 -0
  55. mycode/presentation/commands.py +95 -0
  56. mycode/presentation/tui/__init__.py +6 -0
  57. mycode/presentation/tui/app.py +1351 -0
  58. mycode/presentation/tui/interactions.py +253 -0
  59. mycode/presentation/tui/presenter.py +266 -0
  60. mycode/presentation/tui/screens.py +305 -0
  61. mycode/presentation/tui/widgets.py +214 -0
  62. mycode/project.py +22 -0
  63. mycode/prompts.py +181 -0
  64. mycode/reasoning.py +40 -0
  65. mycode/session.py +86 -0
  66. mycode/skills/__init__.py +27 -0
  67. mycode/skills/builtin/database-recovery/SKILL.md +138 -0
  68. mycode/skills/builtin/database-recovery/references/sqlite.md +235 -0
  69. mycode/skills/registry.py +295 -0
  70. mycode/skills/state.py +68 -0
  71. mycode/subagents/__init__.py +1 -0
  72. mycode/subagents/audit.py +212 -0
  73. mycode/subagents/concurrency.py +124 -0
  74. mycode/subagents/contracts.py +421 -0
  75. mycode/subagents/delegate.py +80 -0
  76. mycode/subagents/delegation.py +128 -0
  77. mycode/subagents/lifecycle.py +86 -0
  78. mycode/subagents/limits.py +7 -0
  79. mycode/subagents/observability.py +150 -0
  80. mycode/subagents/persistence.py +152 -0
  81. mycode/subagents/profiles.py +184 -0
  82. mycode/subagents/prompts.py +67 -0
  83. mycode/subagents/results.py +178 -0
  84. mycode/subagents/runtime.py +528 -0
  85. mycode/subagents/snapshots.py +211 -0
  86. mycode/subagents/tool_batch.py +260 -0
  87. mycode/tools/__init__.py +81 -0
  88. mycode/tools/base.py +222 -0
  89. mycode/tools/bounds.py +14 -0
  90. mycode/tools/command_executor.py +167 -0
  91. mycode/tools/command_output.py +166 -0
  92. mycode/tools/command_risk.py +596 -0
  93. mycode/tools/defaults.py +59 -0
  94. mycode/tools/edit_file.py +524 -0
  95. mycode/tools/file_mutation.py +30 -0
  96. mycode/tools/glob.py +247 -0
  97. mycode/tools/grep.py +324 -0
  98. mycode/tools/ignore.py +122 -0
  99. mycode/tools/inspect_changes.py +269 -0
  100. mycode/tools/load_skill.py +92 -0
  101. mycode/tools/memory.py +264 -0
  102. mycode/tools/path_permissions.py +78 -0
  103. mycode/tools/patterns.py +48 -0
  104. mycode/tools/permission_metadata.py +27 -0
  105. mycode/tools/process_tree.py +166 -0
  106. mycode/tools/read_file.py +242 -0
  107. mycode/tools/read_skill_resource.py +93 -0
  108. mycode/tools/registry.py +279 -0
  109. mycode/tools/run_command.py +237 -0
  110. mycode/tools/run_skill_script.py +206 -0
  111. mycode/tools/run_validation.py +107 -0
  112. mycode/tools/submit_result.py +93 -0
  113. mycode/tools/text.py +15 -0
  114. mycode/tools/validation_command.py +377 -0
  115. mycode/tools/workspace.py +33 -0
  116. mycode/tools/write_file.py +169 -0
  117. mycode_coding_agent-0.1.0.dist-info/METADATA +244 -0
  118. mycode_coding_agent-0.1.0.dist-info/RECORD +121 -0
  119. mycode_coding_agent-0.1.0.dist-info/WHEEL +4 -0
  120. mycode_coding_agent-0.1.0.dist-info/entry_points.txt +2 -0
  121. mycode_coding_agent-0.1.0.dist-info/licenses/LICENSE +21 -0
mycode/config.py ADDED
@@ -0,0 +1,355 @@
1
+ import os
2
+ from collections.abc import Iterator, Mapping
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Literal
6
+
7
+ from dotenv import dotenv_values
8
+
9
+ MYCODE_CONFIG_DIR_NAME = ".mycode"
10
+ MYCODE_ENV_FILE_NAME = ".env"
11
+ DEFAULT_LLM_CONTEXT_WINDOW_TOKENS = 128000
12
+ DEFAULT_LLM_RESERVED_OUTPUT_TOKENS = 8192
13
+ DEFAULT_LLM_CONTEXT_SAFETY_MARGIN_TOKENS = 4096
14
+ DEFAULT_LLM_MEMORY_CONTEXT_TOKENS = 2048
15
+ DEFAULT_LLM_STREAM_INCLUDE_USAGE = True
16
+
17
+
18
+ ReasoningEffort = Literal["high", "max"]
19
+ SUPPORTED_REASONING_EFFORTS: frozenset[str] = frozenset({"high", "max"})
20
+
21
+
22
+ class MissingLLMConfigError(ValueError):
23
+ pass
24
+
25
+
26
+ class _RedactedConfigValues(Mapping[str, str | None]):
27
+ def __init__(self, values: Mapping[str, str | None]) -> None:
28
+ self._values = values
29
+
30
+ def __getitem__(self, key: str) -> str | None:
31
+ return self._values[key]
32
+
33
+ def __iter__(self) -> Iterator[str]:
34
+ return iter(self._values)
35
+
36
+ def __len__(self) -> int:
37
+ return len(self._values)
38
+
39
+ def __repr__(self) -> str:
40
+ return "<redacted config values>"
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class LLMConfig:
45
+ api_key: str = field(repr=False)
46
+ base_url: str
47
+ model: str
48
+ compact_model: str | None = None
49
+ subagent_model: str | None = None
50
+ context_window_tokens: int = DEFAULT_LLM_CONTEXT_WINDOW_TOKENS
51
+ reserved_output_tokens: int = DEFAULT_LLM_RESERVED_OUTPUT_TOKENS
52
+ context_safety_margin_tokens: int = DEFAULT_LLM_CONTEXT_SAFETY_MARGIN_TOKENS
53
+ memory_context_tokens: int = DEFAULT_LLM_MEMORY_CONTEXT_TOKENS
54
+ stream_include_usage: bool = DEFAULT_LLM_STREAM_INCLUDE_USAGE
55
+ thinking_enabled: bool | None = None
56
+ reasoning_effort: ReasoningEffort | None = None
57
+ max_output_tokens: int | None = None
58
+
59
+ def __post_init__(self) -> None:
60
+ main_model = self.model.strip()
61
+ if main_model == "":
62
+ raise ValueError("MYCODE_MODEL must not be empty.")
63
+
64
+ compact_model = self.compact_model
65
+ subagent_model = self.subagent_model
66
+ object.__setattr__(self, "model", main_model)
67
+ object.__setattr__(
68
+ self,
69
+ "compact_model",
70
+ (
71
+ main_model
72
+ if compact_model is None or compact_model.strip() == ""
73
+ else compact_model.strip()
74
+ ),
75
+ )
76
+ object.__setattr__(
77
+ self,
78
+ "subagent_model",
79
+ main_model
80
+ if subagent_model is None or subagent_model.strip() == ""
81
+ else subagent_model.strip(),
82
+ )
83
+ if self.reasoning_effort is not None:
84
+ normalized_effort = self.reasoning_effort.strip().lower()
85
+ if normalized_effort not in SUPPORTED_REASONING_EFFORTS:
86
+ raise ValueError(
87
+ "LLM_REASONING_EFFORT must be one of: high, max."
88
+ )
89
+ object.__setattr__(self, "reasoning_effort", normalized_effort)
90
+ if self.thinking_enabled is True and self.reasoning_effort is None:
91
+ object.__setattr__(self, "reasoning_effort", "high")
92
+ if self.thinking_enabled is not True and self.reasoning_effort is not None:
93
+ raise ValueError(
94
+ "LLM_REASONING_EFFORT requires LLM_THINKING_ENABLED=true."
95
+ )
96
+ if self.max_output_tokens is not None:
97
+ if self.max_output_tokens < 1:
98
+ raise ValueError("LLM_MAX_OUTPUT_TOKENS must be at least 1.")
99
+ if self.max_output_tokens > self.reserved_output_tokens:
100
+ raise ValueError(
101
+ "LLM_MAX_OUTPUT_TOKENS must not exceed "
102
+ "LLM_RESERVED_OUTPUT_TOKENS."
103
+ )
104
+
105
+
106
+ def default_user_env_file() -> Path:
107
+ return Path.home() / MYCODE_CONFIG_DIR_NAME / MYCODE_ENV_FILE_NAME
108
+
109
+
110
+ def project_env_file(workspace_root: str | Path) -> Path:
111
+ return Path(workspace_root) / MYCODE_CONFIG_DIR_NAME / MYCODE_ENV_FILE_NAME
112
+
113
+
114
+ def load_layered_environment(
115
+ env_file: str | Path | None = None,
116
+ *,
117
+ workspace_root: str | Path | None = None,
118
+ environ: Mapping[str, str] | None = None,
119
+ ) -> dict[str, str]:
120
+ user_values = dotenv_values(
121
+ default_user_env_file() if env_file is None else env_file
122
+ )
123
+ project_values = (
124
+ {}
125
+ if workspace_root is None
126
+ else dotenv_values(project_env_file(workspace_root))
127
+ )
128
+ process_values = os.environ if environ is None else environ
129
+ merged: dict[str, str] = {}
130
+ for values in (user_values, project_values, process_values):
131
+ for name, value in values.items():
132
+ if value is not None and value.strip() != "":
133
+ merged[name] = value
134
+ return merged
135
+
136
+
137
+ def load_llm_config(
138
+ env_file: str | Path | None = None,
139
+ *,
140
+ workspace_root: str | Path | None = None,
141
+ ) -> LLMConfig:
142
+ values = _RedactedConfigValues(
143
+ load_layered_environment(
144
+ env_file,
145
+ workspace_root=workspace_root,
146
+ )
147
+ )
148
+
149
+ api_key = _required_env_value(
150
+ "MYCODE_API_KEY",
151
+ values,
152
+ )
153
+ base_url = _required_env_value(
154
+ "MYCODE_BASE_URL",
155
+ values,
156
+ )
157
+ model = _required_env_value(
158
+ "MYCODE_MODEL",
159
+ values,
160
+ )
161
+ compact_model = _env_value(
162
+ "MYCODE_COMPACT_MODEL",
163
+ model,
164
+ values,
165
+ )
166
+ subagent_model = _env_value(
167
+ "MYCODE_SUBAGENT_MODEL",
168
+ model,
169
+ values,
170
+ )
171
+ context_window_tokens = _int_env_value(
172
+ "LLM_CONTEXT_WINDOW_TOKENS",
173
+ DEFAULT_LLM_CONTEXT_WINDOW_TOKENS,
174
+ values,
175
+ minimum=1,
176
+ )
177
+ reserved_output_tokens = _int_env_value(
178
+ "LLM_RESERVED_OUTPUT_TOKENS",
179
+ DEFAULT_LLM_RESERVED_OUTPUT_TOKENS,
180
+ values,
181
+ minimum=0,
182
+ )
183
+ context_safety_margin_tokens = _int_env_value(
184
+ "LLM_CONTEXT_SAFETY_MARGIN_TOKENS",
185
+ DEFAULT_LLM_CONTEXT_SAFETY_MARGIN_TOKENS,
186
+ values,
187
+ minimum=0,
188
+ )
189
+ memory_context_tokens = _int_env_value(
190
+ "LLM_MEMORY_CONTEXT_TOKENS",
191
+ DEFAULT_LLM_MEMORY_CONTEXT_TOKENS,
192
+ values,
193
+ minimum=0,
194
+ )
195
+ stream_include_usage = _bool_env_value(
196
+ "LLM_STREAM_INCLUDE_USAGE",
197
+ DEFAULT_LLM_STREAM_INCLUDE_USAGE,
198
+ values,
199
+ )
200
+ thinking_enabled = _optional_bool_env_value(
201
+ "LLM_THINKING_ENABLED",
202
+ values,
203
+ )
204
+ reasoning_effort = _optional_reasoning_effort_env_value(
205
+ "LLM_REASONING_EFFORT",
206
+ values,
207
+ )
208
+ max_output_tokens = _optional_int_env_value(
209
+ "LLM_MAX_OUTPUT_TOKENS",
210
+ values,
211
+ minimum=1,
212
+ )
213
+
214
+ if reserved_output_tokens + context_safety_margin_tokens >= context_window_tokens:
215
+ raise ValueError(
216
+ "LLM_RESERVED_OUTPUT_TOKENS and LLM_CONTEXT_SAFETY_MARGIN_TOKENS "
217
+ "must leave at least 1 input token."
218
+ )
219
+
220
+ return LLMConfig(
221
+ api_key=api_key,
222
+ base_url=base_url,
223
+ model=model,
224
+ compact_model=compact_model,
225
+ subagent_model=subagent_model,
226
+ context_window_tokens=context_window_tokens,
227
+ reserved_output_tokens=reserved_output_tokens,
228
+ context_safety_margin_tokens=context_safety_margin_tokens,
229
+ memory_context_tokens=memory_context_tokens,
230
+ stream_include_usage=stream_include_usage,
231
+ thinking_enabled=thinking_enabled,
232
+ reasoning_effort=reasoning_effort,
233
+ max_output_tokens=max_output_tokens,
234
+ )
235
+
236
+
237
+ def _required_env_value(
238
+ name: str,
239
+ *value_maps: Mapping[str, str | None],
240
+ ) -> str:
241
+ value = _env_value(name, "", *value_maps)
242
+
243
+ if value == "":
244
+ raise MissingLLMConfigError(f"Missing required environment variable: {name}")
245
+
246
+ return value
247
+
248
+
249
+ def _env_value(
250
+ name: str,
251
+ default: str,
252
+ *value_maps: Mapping[str, str | None],
253
+ ) -> str:
254
+ values = [value_map.get(name) for value_map in value_maps]
255
+
256
+ for value in values:
257
+ if value is None:
258
+ continue
259
+
260
+ value = value.strip()
261
+ if value != "":
262
+ return value
263
+
264
+ return default
265
+
266
+
267
+ def _int_env_value(
268
+ name: str,
269
+ default: int,
270
+ *value_maps: Mapping[str, str | None],
271
+ minimum: int,
272
+ ) -> int:
273
+ raw_value = _env_value(name, str(default), *value_maps)
274
+ try:
275
+ value = int(raw_value)
276
+ except ValueError as error:
277
+ raise ValueError(f"{name} must be an integer.") from error
278
+
279
+ if value < minimum:
280
+ raise ValueError(f"{name} must be at least {minimum}.")
281
+
282
+ return value
283
+
284
+
285
+ def _bool_env_value(
286
+ name: str,
287
+ default: bool,
288
+ *value_maps: Mapping[str, str | None],
289
+ ) -> bool:
290
+ raw_value = _env_value(name, str(default).lower(), *value_maps).lower()
291
+ if raw_value in {"1", "true", "yes", "on"}:
292
+ return True
293
+ if raw_value in {"0", "false", "no", "off"}:
294
+ return False
295
+
296
+ raise ValueError(f"{name} must be true or false.")
297
+
298
+
299
+ def _optional_bool_env_value(
300
+ name: str,
301
+ *value_maps: Mapping[str, str | None],
302
+ ) -> bool | None:
303
+ raw_value = _optional_env_value(name, *value_maps)
304
+ if raw_value is None:
305
+ return None
306
+ normalized = raw_value.lower()
307
+ if normalized in {"1", "true", "yes", "on"}:
308
+ return True
309
+ if normalized in {"0", "false", "no", "off"}:
310
+ return False
311
+ raise ValueError(f"{name} must be true or false.")
312
+
313
+
314
+ def _optional_int_env_value(
315
+ name: str,
316
+ *value_maps: Mapping[str, str | None],
317
+ minimum: int,
318
+ ) -> int | None:
319
+ raw_value = _optional_env_value(name, *value_maps)
320
+ if raw_value is None:
321
+ return None
322
+ try:
323
+ value = int(raw_value)
324
+ except ValueError as error:
325
+ raise ValueError(f"{name} must be an integer.") from error
326
+ if value < minimum:
327
+ raise ValueError(f"{name} must be at least {minimum}.")
328
+ return value
329
+
330
+
331
+ def _optional_reasoning_effort_env_value(
332
+ name: str,
333
+ *value_maps: Mapping[str, str | None],
334
+ ) -> ReasoningEffort | None:
335
+ raw_value = _optional_env_value(name, *value_maps)
336
+ if raw_value is None:
337
+ return None
338
+ normalized = raw_value.lower()
339
+ if normalized not in SUPPORTED_REASONING_EFFORTS:
340
+ raise ValueError(f"{name} must be one of: high, max.")
341
+ return normalized # type: ignore[return-value]
342
+
343
+
344
+ def _optional_env_value(
345
+ name: str,
346
+ *value_maps: Mapping[str, str | None],
347
+ ) -> str | None:
348
+ values = [value_map.get(name) for value_map in value_maps]
349
+ for value in values:
350
+ if value is None:
351
+ continue
352
+ normalized = value.strip()
353
+ if normalized != "":
354
+ return normalized
355
+ return None
@@ -0,0 +1 @@
1
+ """Context construction, compaction, artifacts, and tool-result handling."""