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.
- rlmagent/__init__.py +1 -0
- rlmagent/__main__.py +8 -0
- rlmagent/acp/__init__.py +1 -0
- rlmagent/acp/runtime.py +71 -0
- rlmagent/acp/server.py +598 -0
- rlmagent/acp/updates.py +128 -0
- rlmagent/agentnet.py +237 -0
- rlmagent/agents/__init__.py +1 -0
- rlmagent/agents/approval.py +90 -0
- rlmagent/agents/bridge.py +109 -0
- rlmagent/agents/budget.py +212 -0
- rlmagent/agents/child_handles.py +46 -0
- rlmagent/agents/child_mailbox.py +30 -0
- rlmagent/agents/kernel_api.py +156 -0
- rlmagent/agents/output.py +34 -0
- rlmagent/agents/patch.py +107 -0
- rlmagent/agents/prompts.py +156 -0
- rlmagent/agents/trace.py +290 -0
- rlmagent/agents/tree.py +682 -0
- rlmagent/agents/wasm_api.py +105 -0
- rlmagent/agents/workspace.py +357 -0
- rlmagent/autopilot.py +447 -0
- rlmagent/backend.py +946 -0
- rlmagent/backend_context.py +74 -0
- rlmagent/cli/__init__.py +5 -0
- rlmagent/cli/config_cmd.py +177 -0
- rlmagent/cli/health.py +156 -0
- rlmagent/cli/main.py +1194 -0
- rlmagent/cli/update.py +188 -0
- rlmagent/clipboard.py +63 -0
- rlmagent/config/__init__.py +37 -0
- rlmagent/config/config.py +297 -0
- rlmagent/config/loader.py +119 -0
- rlmagent/config/prompts.py +124 -0
- rlmagent/config/secure_input.py +213 -0
- rlmagent/config/store.py +275 -0
- rlmagent/context/__init__.py +47 -0
- rlmagent/context/budget.py +557 -0
- rlmagent/contract.py +311 -0
- rlmagent/contract_run.py +232 -0
- rlmagent/conversation.py +2547 -0
- rlmagent/cron.py +77 -0
- rlmagent/directives.py +893 -0
- rlmagent/discovery.py +356 -0
- rlmagent/execution_log.py +66 -0
- rlmagent/factory.py +189 -0
- rlmagent/factory_graph.py +413 -0
- rlmagent/harness/__init__.py +63 -0
- rlmagent/harness/contracts/__init__.py +1 -0
- rlmagent/harness/contracts/stream.py +129 -0
- rlmagent/harness/contracts/tooling.py +127 -0
- rlmagent/harness/contracts/transcript/__init__.py +68 -0
- rlmagent/harness/contracts/transcript/_base.py +29 -0
- rlmagent/harness/contracts/transcript/diagnostics.py +52 -0
- rlmagent/harness/contracts/transcript/entries.py +180 -0
- rlmagent/harness/contracts/transcript/segments.py +64 -0
- rlmagent/harness/contracts/values.py +6 -0
- rlmagent/harness/driver.py +397 -0
- rlmagent/harness/engine.py +489 -0
- rlmagent/harness/mending.py +153 -0
- rlmagent/harness/provider/__init__.py +1 -0
- rlmagent/harness/provider/base.py +27 -0
- rlmagent/harness/provider/wire.py +189 -0
- rlmagent/harness/py.typed +0 -0
- rlmagent/harness/session/__init__.py +5 -0
- rlmagent/harness/session/index.py +218 -0
- rlmagent/harness/session/lease.py +66 -0
- rlmagent/harness/session/records.py +118 -0
- rlmagent/harness/session/replay.py +165 -0
- rlmagent/harness/session/store.py +155 -0
- rlmagent/harness/session/summary.py +46 -0
- rlmagent/hints.py +47 -0
- rlmagent/history.py +92 -0
- rlmagent/hooks/__init__.py +1 -0
- rlmagent/hooks/hooks.py +1 -0
- rlmagent/instructions.py +157 -0
- rlmagent/job_runner.py +117 -0
- rlmagent/jobs.py +365 -0
- rlmagent/kept_agents.py +397 -0
- rlmagent/kernel/__init__.py +5 -0
- rlmagent/kernel/session.py +205 -0
- rlmagent/kernel/wasm.py +169 -0
- rlmagent/logs.py +208 -0
- rlmagent/managed_release.py +368 -0
- rlmagent/mcp.py +411 -0
- rlmagent/mcp_oauth.py +213 -0
- rlmagent/model/__init__.py +29 -0
- rlmagent/model/_claude/__init__.py +1 -0
- rlmagent/model/_claude/courier.py +117 -0
- rlmagent/model/_claude/emitter.py +378 -0
- rlmagent/model/_oai/__init__.py +1 -0
- rlmagent/model/_oai/helpers.py +256 -0
- rlmagent/model/_oai/normalize.py +263 -0
- rlmagent/model/_oai/parsers.py +317 -0
- rlmagent/model/_oai/payloads.py +226 -0
- rlmagent/model/_oai/transport.py +127 -0
- rlmagent/model/claude.py +552 -0
- rlmagent/model/copilot.py +142 -0
- rlmagent/model/correlation.py +21 -0
- rlmagent/model/deadline.py +19 -0
- rlmagent/model/discovery.py +181 -0
- rlmagent/model/external/__init__.py +11 -0
- rlmagent/model/external/acp.py +202 -0
- rlmagent/model/external/acp_install.py +109 -0
- rlmagent/model/external/cli.py +248 -0
- rlmagent/model/external/textcalls.py +57 -0
- rlmagent/model/failover.py +129 -0
- rlmagent/model/google.py +199 -0
- rlmagent/model/limits.py +46 -0
- rlmagent/model/oai_compatible.py +414 -0
- rlmagent/model/preferences.py +56 -0
- rlmagent/model/py.typed +0 -0
- rlmagent/model/registered_oauth.py +195 -0
- rlmagent/model/scripted.py +55 -0
- rlmagent/model/settings.py +271 -0
- rlmagent/model/subscription.py +398 -0
- rlmagent/model/token_bounds.py +15 -0
- rlmagent/model/transport/__init__.py +1 -0
- rlmagent/model/transport/backoff.py +87 -0
- rlmagent/model/transport/client.py +87 -0
- rlmagent/model/transport/faults.py +65 -0
- rlmagent/model/transport/responses_socket.py +223 -0
- rlmagent/model/vertex.py +93 -0
- rlmagent/network.py +123 -0
- rlmagent/packages.py +246 -0
- rlmagent/planning.py +84 -0
- rlmagent/playbook/__init__.py +1 -0
- rlmagent/playbook/apply.py +106 -0
- rlmagent/playbook/digest.py +63 -0
- rlmagent/playbook/improver.py +272 -0
- rlmagent/playbook/plan.py +99 -0
- rlmagent/playbook/store.py +67 -0
- rlmagent/plugins.py +327 -0
- rlmagent/policy.py +105 -0
- rlmagent/prompt_refs.py +57 -0
- rlmagent/prompts.py +331 -0
- rlmagent/prune.py +60 -0
- rlmagent/py.typed +0 -0
- rlmagent/reasoning.py +135 -0
- rlmagent/refinement/__init__.py +1 -0
- rlmagent/refinement/controller.py +448 -0
- rlmagent/refinement/engine.py +220 -0
- rlmagent/refinement/evidence.py +131 -0
- rlmagent/refinement/scoped.py +90 -0
- rlmagent/refinement/store.py +193 -0
- rlmagent/refresh.py +202 -0
- rlmagent/routing.py +52 -0
- rlmagent/runtime.py +575 -0
- rlmagent/safety.py +267 -0
- rlmagent/session_html.py +57 -0
- rlmagent/session_import.py +52 -0
- rlmagent/session_share.py +55 -0
- rlmagent/sessions.py +249 -0
- rlmagent/shell_sandbox.py +88 -0
- rlmagent/skillset.py +320 -0
- rlmagent/steering.py +148 -0
- rlmagent/stops.py +62 -0
- rlmagent/subscriptions.py +219 -0
- rlmagent/tasks.py +98 -0
- rlmagent/toolhost.py +310 -0
- rlmagent/tools/__init__.py +56 -0
- rlmagent/tools/_shared.py +210 -0
- rlmagent/tools/files.py +462 -0
- rlmagent/tools/kernel.py +93 -0
- rlmagent/tools/patch_tool.py +68 -0
- rlmagent/tools/search.py +188 -0
- rlmagent/trust.py +383 -0
- rlmagent/tui/__init__.py +1 -0
- rlmagent/tui/adapter.py +506 -0
- rlmagent/tui/app.py +711 -0
- rlmagent/tui/approval_screen.py +57 -0
- rlmagent/tui/choice_screen.py +47 -0
- rlmagent/tui/completions.py +116 -0
- rlmagent/tui/components.py +25 -0
- rlmagent/tui/composer.py +40 -0
- rlmagent/tui/factory_view.py +86 -0
- rlmagent/tui/inspect.py +193 -0
- rlmagent/tui/messages.py +215 -0
- rlmagent/tui/model_screen.py +307 -0
- rlmagent/tui/prompt.py +98 -0
- rlmagent/tui/question_screen.py +52 -0
- rlmagent/tui/replay.py +141 -0
- rlmagent/tui/rlm_agent.tcss +405 -0
- rlmagent/tui/search_box.py +91 -0
- rlmagent/tui/sidebar.py +182 -0
- rlmagent/tui/status.py +49 -0
- rlmagent/tui/steps.py +283 -0
- rlmagent/tui/suggest.py +778 -0
- rlmagent/tui/transcript.py +172 -0
- rlmagent/tui/trust_dialog.py +58 -0
- rlmagent/tui/worker_attach.py +141 -0
- rlmagent/ui/__init__.py +1 -0
- rlmagent/ui/render.py +211 -0
- rlmagent/undo.py +109 -0
- rlmagent/utils/__init__.py +1 -0
- rlmagent/websearch.py +75 -0
- rlmagent/worker.py +1275 -0
- rlmagent/worker_service.py +203 -0
- rlmness_cli-0.2.0.dist-info/METADATA +882 -0
- rlmness_cli-0.2.0.dist-info/RECORD +203 -0
- rlmness_cli-0.2.0.dist-info/WHEEL +4 -0
- rlmness_cli-0.2.0.dist-info/entry_points.txt +2 -0
- 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
|
+
]
|