rlmness-cli 0.2.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 (203) hide show
  1. rlmagent/__init__.py +1 -0
  2. rlmagent/__main__.py +8 -0
  3. rlmagent/acp/__init__.py +1 -0
  4. rlmagent/acp/runtime.py +71 -0
  5. rlmagent/acp/server.py +598 -0
  6. rlmagent/acp/updates.py +128 -0
  7. rlmagent/agentnet.py +237 -0
  8. rlmagent/agents/__init__.py +1 -0
  9. rlmagent/agents/approval.py +90 -0
  10. rlmagent/agents/bridge.py +109 -0
  11. rlmagent/agents/budget.py +212 -0
  12. rlmagent/agents/child_handles.py +46 -0
  13. rlmagent/agents/child_mailbox.py +30 -0
  14. rlmagent/agents/kernel_api.py +156 -0
  15. rlmagent/agents/output.py +34 -0
  16. rlmagent/agents/patch.py +107 -0
  17. rlmagent/agents/prompts.py +156 -0
  18. rlmagent/agents/trace.py +290 -0
  19. rlmagent/agents/tree.py +682 -0
  20. rlmagent/agents/wasm_api.py +105 -0
  21. rlmagent/agents/workspace.py +357 -0
  22. rlmagent/autopilot.py +447 -0
  23. rlmagent/backend.py +946 -0
  24. rlmagent/backend_context.py +74 -0
  25. rlmagent/cli/__init__.py +5 -0
  26. rlmagent/cli/config_cmd.py +177 -0
  27. rlmagent/cli/health.py +156 -0
  28. rlmagent/cli/main.py +1194 -0
  29. rlmagent/cli/update.py +188 -0
  30. rlmagent/clipboard.py +63 -0
  31. rlmagent/config/__init__.py +37 -0
  32. rlmagent/config/config.py +297 -0
  33. rlmagent/config/loader.py +119 -0
  34. rlmagent/config/prompts.py +124 -0
  35. rlmagent/config/secure_input.py +213 -0
  36. rlmagent/config/store.py +275 -0
  37. rlmagent/context/__init__.py +47 -0
  38. rlmagent/context/budget.py +557 -0
  39. rlmagent/contract.py +311 -0
  40. rlmagent/contract_run.py +232 -0
  41. rlmagent/conversation.py +2547 -0
  42. rlmagent/cron.py +77 -0
  43. rlmagent/directives.py +893 -0
  44. rlmagent/discovery.py +356 -0
  45. rlmagent/execution_log.py +66 -0
  46. rlmagent/factory.py +189 -0
  47. rlmagent/factory_graph.py +413 -0
  48. rlmagent/harness/__init__.py +63 -0
  49. rlmagent/harness/contracts/__init__.py +1 -0
  50. rlmagent/harness/contracts/stream.py +129 -0
  51. rlmagent/harness/contracts/tooling.py +127 -0
  52. rlmagent/harness/contracts/transcript/__init__.py +68 -0
  53. rlmagent/harness/contracts/transcript/_base.py +29 -0
  54. rlmagent/harness/contracts/transcript/diagnostics.py +52 -0
  55. rlmagent/harness/contracts/transcript/entries.py +180 -0
  56. rlmagent/harness/contracts/transcript/segments.py +64 -0
  57. rlmagent/harness/contracts/values.py +6 -0
  58. rlmagent/harness/driver.py +397 -0
  59. rlmagent/harness/engine.py +489 -0
  60. rlmagent/harness/mending.py +153 -0
  61. rlmagent/harness/provider/__init__.py +1 -0
  62. rlmagent/harness/provider/base.py +27 -0
  63. rlmagent/harness/provider/wire.py +189 -0
  64. rlmagent/harness/py.typed +0 -0
  65. rlmagent/harness/session/__init__.py +5 -0
  66. rlmagent/harness/session/index.py +218 -0
  67. rlmagent/harness/session/lease.py +66 -0
  68. rlmagent/harness/session/records.py +118 -0
  69. rlmagent/harness/session/replay.py +165 -0
  70. rlmagent/harness/session/store.py +155 -0
  71. rlmagent/harness/session/summary.py +46 -0
  72. rlmagent/hints.py +47 -0
  73. rlmagent/history.py +92 -0
  74. rlmagent/hooks/__init__.py +1 -0
  75. rlmagent/hooks/hooks.py +1 -0
  76. rlmagent/instructions.py +157 -0
  77. rlmagent/job_runner.py +117 -0
  78. rlmagent/jobs.py +365 -0
  79. rlmagent/kept_agents.py +397 -0
  80. rlmagent/kernel/__init__.py +5 -0
  81. rlmagent/kernel/session.py +205 -0
  82. rlmagent/kernel/wasm.py +169 -0
  83. rlmagent/logs.py +208 -0
  84. rlmagent/managed_release.py +368 -0
  85. rlmagent/mcp.py +411 -0
  86. rlmagent/mcp_oauth.py +213 -0
  87. rlmagent/model/__init__.py +29 -0
  88. rlmagent/model/_claude/__init__.py +1 -0
  89. rlmagent/model/_claude/courier.py +117 -0
  90. rlmagent/model/_claude/emitter.py +378 -0
  91. rlmagent/model/_oai/__init__.py +1 -0
  92. rlmagent/model/_oai/helpers.py +256 -0
  93. rlmagent/model/_oai/normalize.py +263 -0
  94. rlmagent/model/_oai/parsers.py +317 -0
  95. rlmagent/model/_oai/payloads.py +226 -0
  96. rlmagent/model/_oai/transport.py +127 -0
  97. rlmagent/model/claude.py +552 -0
  98. rlmagent/model/copilot.py +142 -0
  99. rlmagent/model/correlation.py +21 -0
  100. rlmagent/model/deadline.py +19 -0
  101. rlmagent/model/discovery.py +181 -0
  102. rlmagent/model/external/__init__.py +11 -0
  103. rlmagent/model/external/acp.py +202 -0
  104. rlmagent/model/external/acp_install.py +109 -0
  105. rlmagent/model/external/cli.py +248 -0
  106. rlmagent/model/external/textcalls.py +57 -0
  107. rlmagent/model/failover.py +129 -0
  108. rlmagent/model/google.py +199 -0
  109. rlmagent/model/limits.py +46 -0
  110. rlmagent/model/oai_compatible.py +414 -0
  111. rlmagent/model/preferences.py +56 -0
  112. rlmagent/model/py.typed +0 -0
  113. rlmagent/model/registered_oauth.py +195 -0
  114. rlmagent/model/scripted.py +55 -0
  115. rlmagent/model/settings.py +271 -0
  116. rlmagent/model/subscription.py +398 -0
  117. rlmagent/model/token_bounds.py +15 -0
  118. rlmagent/model/transport/__init__.py +1 -0
  119. rlmagent/model/transport/backoff.py +87 -0
  120. rlmagent/model/transport/client.py +87 -0
  121. rlmagent/model/transport/faults.py +65 -0
  122. rlmagent/model/transport/responses_socket.py +223 -0
  123. rlmagent/model/vertex.py +93 -0
  124. rlmagent/network.py +123 -0
  125. rlmagent/packages.py +246 -0
  126. rlmagent/planning.py +84 -0
  127. rlmagent/playbook/__init__.py +1 -0
  128. rlmagent/playbook/apply.py +106 -0
  129. rlmagent/playbook/digest.py +63 -0
  130. rlmagent/playbook/improver.py +272 -0
  131. rlmagent/playbook/plan.py +99 -0
  132. rlmagent/playbook/store.py +67 -0
  133. rlmagent/plugins.py +327 -0
  134. rlmagent/policy.py +105 -0
  135. rlmagent/prompt_refs.py +57 -0
  136. rlmagent/prompts.py +331 -0
  137. rlmagent/prune.py +60 -0
  138. rlmagent/py.typed +0 -0
  139. rlmagent/reasoning.py +135 -0
  140. rlmagent/refinement/__init__.py +1 -0
  141. rlmagent/refinement/controller.py +448 -0
  142. rlmagent/refinement/engine.py +220 -0
  143. rlmagent/refinement/evidence.py +131 -0
  144. rlmagent/refinement/scoped.py +90 -0
  145. rlmagent/refinement/store.py +193 -0
  146. rlmagent/refresh.py +202 -0
  147. rlmagent/routing.py +52 -0
  148. rlmagent/runtime.py +575 -0
  149. rlmagent/safety.py +267 -0
  150. rlmagent/session_html.py +57 -0
  151. rlmagent/session_import.py +52 -0
  152. rlmagent/session_share.py +55 -0
  153. rlmagent/sessions.py +249 -0
  154. rlmagent/shell_sandbox.py +88 -0
  155. rlmagent/skillset.py +320 -0
  156. rlmagent/steering.py +148 -0
  157. rlmagent/stops.py +62 -0
  158. rlmagent/subscriptions.py +219 -0
  159. rlmagent/tasks.py +98 -0
  160. rlmagent/toolhost.py +310 -0
  161. rlmagent/tools/__init__.py +56 -0
  162. rlmagent/tools/_shared.py +210 -0
  163. rlmagent/tools/files.py +462 -0
  164. rlmagent/tools/kernel.py +93 -0
  165. rlmagent/tools/patch_tool.py +68 -0
  166. rlmagent/tools/search.py +188 -0
  167. rlmagent/trust.py +383 -0
  168. rlmagent/tui/__init__.py +1 -0
  169. rlmagent/tui/adapter.py +506 -0
  170. rlmagent/tui/app.py +711 -0
  171. rlmagent/tui/approval_screen.py +57 -0
  172. rlmagent/tui/choice_screen.py +47 -0
  173. rlmagent/tui/completions.py +116 -0
  174. rlmagent/tui/components.py +25 -0
  175. rlmagent/tui/composer.py +40 -0
  176. rlmagent/tui/factory_view.py +86 -0
  177. rlmagent/tui/inspect.py +193 -0
  178. rlmagent/tui/messages.py +215 -0
  179. rlmagent/tui/model_screen.py +307 -0
  180. rlmagent/tui/prompt.py +98 -0
  181. rlmagent/tui/question_screen.py +52 -0
  182. rlmagent/tui/replay.py +141 -0
  183. rlmagent/tui/rlm_agent.tcss +405 -0
  184. rlmagent/tui/search_box.py +91 -0
  185. rlmagent/tui/sidebar.py +182 -0
  186. rlmagent/tui/status.py +49 -0
  187. rlmagent/tui/steps.py +283 -0
  188. rlmagent/tui/suggest.py +778 -0
  189. rlmagent/tui/transcript.py +172 -0
  190. rlmagent/tui/trust_dialog.py +58 -0
  191. rlmagent/tui/worker_attach.py +141 -0
  192. rlmagent/ui/__init__.py +1 -0
  193. rlmagent/ui/render.py +211 -0
  194. rlmagent/undo.py +109 -0
  195. rlmagent/utils/__init__.py +1 -0
  196. rlmagent/websearch.py +75 -0
  197. rlmagent/worker.py +1275 -0
  198. rlmagent/worker_service.py +203 -0
  199. rlmness_cli-0.2.0.dist-info/METADATA +882 -0
  200. rlmness_cli-0.2.0.dist-info/RECORD +203 -0
  201. rlmness_cli-0.2.0.dist-info/WHEEL +4 -0
  202. rlmness_cli-0.2.0.dist-info/entry_points.txt +2 -0
  203. rlmness_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import deque
4
+ from collections.abc import AsyncIterator, Iterable
5
+
6
+ from rlmagent.harness.contracts.tooling import CancelToken, ToolSpec
7
+ from rlmagent.harness.contracts.transcript import TranscriptEntry
8
+ from rlmagent.harness.provider.wire import WireEvent
9
+
10
+
11
+ class ReplayProvider:
12
+ def __init__(self, streams: Iterable[Iterable[WireEvent]]) -> None:
13
+ self._streams = deque(map(tuple, streams))
14
+ self.calls: list[
15
+ tuple[
16
+ str,
17
+ str,
18
+ list[TranscriptEntry],
19
+ list[ToolSpec],
20
+ ]
21
+ ] = []
22
+
23
+ async def _emit(
24
+ self,
25
+ events: Iterable[WireEvent],
26
+ signal: CancelToken | None,
27
+ ) -> AsyncIterator[WireEvent]:
28
+ for event in events:
29
+ if signal is not None and signal.is_cancelled():
30
+ break
31
+ yield event
32
+
33
+ def stream_response(
34
+ self,
35
+ *,
36
+ model: str,
37
+ system: str,
38
+ messages: list[TranscriptEntry],
39
+ tools: list[ToolSpec],
40
+ signal: CancelToken | None = None,
41
+ cache_key: str | None = None,
42
+ ) -> AsyncIterator[WireEvent]:
43
+
44
+ self.calls.append(
45
+ (
46
+ model,
47
+ system,
48
+ messages.copy(),
49
+ tools.copy(),
50
+ )
51
+ )
52
+
53
+ events = self._streams.popleft() if self._streams else ()
54
+
55
+ return self._emit(events, signal)
@@ -0,0 +1,271 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from dataclasses import dataclass, field
6
+ from typing import Literal, Protocol
7
+
8
+
9
+ class ConfigError(Exception):
10
+ """Raised when provider configuration is invalid or incomplete."""
11
+
12
+
13
+
14
+
15
+ def _read_str(name: str, *, default: str) -> str:
16
+ """Read a string env var, returning *default* when unset or empty."""
17
+ return os.environ.get(name) or default
18
+
19
+
20
+ def _require_str(name: str, *, hint: str = "") -> str:
21
+ """Read a required string env var, raising on absence."""
22
+ value = os.environ.get(name)
23
+ if not value:
24
+ msg = f"{name} is not set."
25
+ if hint:
26
+ msg = f"{msg} {hint}"
27
+ raise ConfigError(msg)
28
+ return value
29
+
30
+
31
+ def _read_int(name: str, *, default: int, minimum: int | None = None) -> int:
32
+ """Read an integer env var with optional lower bound."""
33
+ raw = os.environ.get(name)
34
+ if raw is None:
35
+ return default
36
+ try:
37
+ value = int(raw)
38
+ except ValueError:
39
+ raise ConfigError(f"{name} must be an integer, got {raw!r}.") from None
40
+ if minimum is not None and value < minimum:
41
+ raise ConfigError(f"{name} must be at least {minimum}, got {value}.")
42
+ return value
43
+
44
+
45
+ def _read_int_or_none(name: str, *, minimum: int | None = None) -> int | None:
46
+ """Read an optional integer env var that may be absent."""
47
+ raw = os.environ.get(name)
48
+ if raw is None or not raw.strip():
49
+ return None
50
+ try:
51
+ value = int(raw)
52
+ except ValueError:
53
+ raise ConfigError(f"{name} must be an integer, got {raw!r}.") from None
54
+ if minimum is not None and value < minimum:
55
+ raise ConfigError(f"{name} must be at least {minimum}, got {value}.")
56
+ return value
57
+
58
+
59
+ def _read_float(name: str, *, default: float, positive: bool = False) -> float:
60
+ """Read a float env var with optional positivity constraint."""
61
+ raw = os.environ.get(name)
62
+ if raw is None:
63
+ return default
64
+ try:
65
+ value = float(raw)
66
+ except ValueError:
67
+ raise ConfigError(f"{name} must be a number, got {raw!r}.") from None
68
+ if positive and value <= 0:
69
+ raise ConfigError(f"{name} must be positive, got {value}.")
70
+ return value
71
+
72
+
73
+ def _read_bool(name: str, *, default: bool) -> bool:
74
+ """Read a boolean env var (true/false, 1/0, yes/no, on/off)."""
75
+ raw = os.environ.get(name)
76
+ if raw is None:
77
+ return default
78
+ lower = raw.strip().lower()
79
+ if lower in {"1", "true", "yes", "on"}:
80
+ return True
81
+ if lower in {"0", "false", "no", "off"}:
82
+ return False
83
+ raise ConfigError(f"{name} must be a boolean (true/false/1/0), got {raw!r}.")
84
+
85
+
86
+ def _parse_header_env(name: str) -> tuple[tuple[str, str], ...]:
87
+ """Parse semicolon-delimited ``Key: Value`` pairs from an env var."""
88
+ raw = os.environ.get(name)
89
+ if not raw or not raw.strip():
90
+ return ()
91
+ pairs: list[tuple[str, str]] = []
92
+ for segment in raw.split(";"):
93
+ segment = segment.strip()
94
+ if not segment:
95
+ continue
96
+ if ":" not in segment:
97
+ raise ConfigError(
98
+ f"Each entry in {name} must be 'Key: Value', got {segment!r}."
99
+ )
100
+ key, _, value = segment.partition(":")
101
+ key = key.strip()
102
+ if not key:
103
+ raise ConfigError(f"Empty header name in {name}.")
104
+ pairs.append((key, value.strip()))
105
+ return tuple(pairs)
106
+
107
+
108
+
109
+
110
+ @dataclass(frozen=True, slots=True)
111
+ class Credential:
112
+ """Static authentication material for a provider endpoint."""
113
+
114
+ api_key: str | None
115
+ base_url: str
116
+ extra_headers: tuple[tuple[str, str], ...] = ()
117
+
118
+
119
+ class CredentialResolver(Protocol):
120
+ """Async callable that produces fresh authentication material."""
121
+
122
+ async def __call__(self) -> Credential: ...
123
+
124
+
125
+ @dataclass(frozen=True, slots=True)
126
+ class RetryPolicy:
127
+ """Controls automatic reattempts after transient provider failures."""
128
+
129
+ max_retries: int = 2
130
+ max_delay_seconds: float = 30.0
131
+
132
+
133
+ @dataclass(frozen=True, slots=True)
134
+ class ReasoningPolicy:
135
+ """Controls extended thinking / chain-of-thought budget."""
136
+
137
+ enabled: bool = False
138
+ budget_tokens: int | None = None
139
+ # Effort-style level (``minimal``/``low``/``medium``/``high``…) for
140
+ # providers that take a named effort instead of a token budget.
141
+ effort: str | None = None
142
+
143
+
144
+ @dataclass(frozen=True, slots=True)
145
+ class AnthropicProfile:
146
+ """Fully resolved configuration for an Anthropic provider endpoint."""
147
+
148
+ name: str
149
+ credential: Credential
150
+ timeout_seconds: float = 120.0
151
+ max_tokens: int = 16384
152
+ retry: RetryPolicy = field(default_factory=RetryPolicy)
153
+ reasoning: ReasoningPolicy = field(default_factory=ReasoningPolicy)
154
+ resolver: CredentialResolver | None = None
155
+ # Prompt-cache lifetime: ``none`` sends no cache markers, ``short`` uses the
156
+ # provider's default lifetime, ``long`` asks for the extended one. ``None``
157
+ # decides from the endpoint: first-party hosts get ``short``; gateways that
158
+ # merely speak the protocol get ``none``, since they may reject the markers.
159
+ cache_retention: Literal["none", "short", "long"] | None = None
160
+ idempotency: bool | None = None
161
+
162
+
163
+ @dataclass(frozen=True, slots=True)
164
+ class OpenAIProfile:
165
+ """Fully resolved configuration for an OpenAI-compatible provider endpoint."""
166
+
167
+ name: str
168
+ credential: Credential
169
+ timeout_seconds: float = 120.0
170
+ organization: str | None = None
171
+ retry: RetryPolicy = field(default_factory=RetryPolicy)
172
+ reasoning: ReasoningPolicy = field(default_factory=ReasoningPolicy)
173
+ resolver: CredentialResolver | None = None
174
+ # Send the conversation id as a prompt-cache routing hint. ``None`` enables
175
+ # it only for the first-party endpoint; compatible gateways do not all
176
+ # accept the extra field and header, so they must opt in explicitly.
177
+ cache_affinity: bool | None = None
178
+ service_tier: str | None = None
179
+ endpoint: Literal["chat", "responses"] | None = None
180
+ transport: Literal["http", "websocket"] = "http"
181
+ idempotency: bool | None = None
182
+
183
+
184
+
185
+
186
+ _ANTHROPIC_DEFAULT_URL = "https://api.anthropic.com"
187
+ _OPENAI_DEFAULT_URL = "https://api.openai.com/v1"
188
+
189
+
190
+ def _load_retry_policy() -> RetryPolicy:
191
+ """Build a ``RetryPolicy`` from shared ``RLM_AGENT_`` env vars."""
192
+ return RetryPolicy(
193
+ max_retries=_read_int("RLM_AGENT_MAX_RETRIES", default=2, minimum=0),
194
+ max_delay_seconds=_read_float(
195
+ "RLM_AGENT_MAX_RETRY_DELAY", default=30.0, positive=True
196
+ ),
197
+ )
198
+
199
+
200
+ def _load_reasoning_policy() -> ReasoningPolicy:
201
+ """Build a ``ReasoningPolicy`` from shared ``RLM_AGENT_`` env vars."""
202
+ return ReasoningPolicy(
203
+ enabled=_read_bool("RLM_AGENT_THINKING_ENABLED", default=False),
204
+ budget_tokens=_read_int_or_none("RLM_AGENT_THINKING_BUDGET", minimum=1),
205
+ )
206
+
207
+
208
+
209
+
210
+ def load_anthropic_profile() -> AnthropicProfile:
211
+ credential = Credential(
212
+ api_key=_require_str(
213
+ "ANTHROPIC_API_KEY",
214
+ hint="Get one at https://console.anthropic.com/settings/keys",
215
+ ),
216
+ base_url=_read_str("ANTHROPIC_BASE_URL", default=_ANTHROPIC_DEFAULT_URL),
217
+ extra_headers=_parse_header_env("ANTHROPIC_EXTRA_HEADERS"),
218
+ )
219
+ return AnthropicProfile(
220
+ name="anthropic",
221
+ credential=credential,
222
+ timeout_seconds=_read_float(
223
+ "ANTHROPIC_TIMEOUT", default=120.0, positive=True
224
+ ),
225
+ max_tokens=_read_int("ANTHROPIC_MAX_TOKENS", default=16384, minimum=1),
226
+ retry=_load_retry_policy(),
227
+ reasoning=_load_reasoning_policy(),
228
+ )
229
+
230
+
231
+ def load_openai_profile() -> OpenAIProfile:
232
+
233
+ credential = Credential(
234
+ api_key=_require_str(
235
+ "OPENAI_API_KEY",
236
+ hint="Get one at https://platform.openai.com/api-keys",
237
+ ),
238
+ base_url=_read_str("OPENAI_BASE_URL", default=_OPENAI_DEFAULT_URL),
239
+ extra_headers=_parse_header_env("OPENAI_EXTRA_HEADERS"),
240
+ )
241
+ return OpenAIProfile(
242
+ name="openai",
243
+ credential=credential,
244
+ timeout_seconds=_read_float(
245
+ "OPENAI_TIMEOUT", default=120.0, positive=True
246
+ ),
247
+ organization=os.environ.get("OPENAI_ORG_ID") or None,
248
+ retry=_load_retry_policy(),
249
+ reasoning=_load_reasoning_policy(),
250
+ service_tier=load_service_tier(),
251
+ )
252
+
253
+
254
+ def load_service_tier() -> str | None:
255
+ value = os.environ.get("OPENAI_SERVICE_TIER") or None
256
+ if value not in (None, "auto", "default", "flex", "priority"):
257
+ raise ConfigError("OPENAI_SERVICE_TIER must be auto, default, flex or priority")
258
+ return value
259
+
260
+
261
+ __all__ = [
262
+ "AnthropicProfile",
263
+ "ConfigError",
264
+ "Credential",
265
+ "CredentialResolver",
266
+ "OpenAIProfile",
267
+ "ReasoningPolicy",
268
+ "RetryPolicy",
269
+ "load_anthropic_profile",
270
+ "load_openai_profile",
271
+ ]