gpu4agents 0.5.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.
- gpu4agents/__init__.py +5 -0
- gpu4agents/__main__.py +4 -0
- gpu4agents/agent/__init__.py +1 -0
- gpu4agents/agent/install.py +173 -0
- gpu4agents/agent/jobs.py +193 -0
- gpu4agents/agent/mcp.py +646 -0
- gpu4agents/agent/skill/SKILL.md +68 -0
- gpu4agents/cli/__init__.py +0 -0
- gpu4agents/cli/_common.py +131 -0
- gpu4agents/cli/account.py +330 -0
- gpu4agents/cli/agent.py +74 -0
- gpu4agents/cli/inspect.py +207 -0
- gpu4agents/cli/lifecycle.py +127 -0
- gpu4agents/cli/main.py +163 -0
- gpu4agents/cli/providers.py +96 -0
- gpu4agents/cli/run.py +38 -0
- gpu4agents/cli/setup.py +164 -0
- gpu4agents/cli/status.py +224 -0
- gpu4agents/cli/timer.py +143 -0
- gpu4agents/cli/validate.py +57 -0
- gpu4agents/core/__init__.py +0 -0
- gpu4agents/core/beacon.py +133 -0
- gpu4agents/core/config.py +647 -0
- gpu4agents/core/credentials.py +95 -0
- gpu4agents/core/durations.py +47 -0
- gpu4agents/core/engine.py +1335 -0
- gpu4agents/core/errors.py +94 -0
- gpu4agents/core/events.py +117 -0
- gpu4agents/core/execution.py +169 -0
- gpu4agents/core/health.py +62 -0
- gpu4agents/core/hosts.py +58 -0
- gpu4agents/core/model_inspect.py +368 -0
- gpu4agents/core/models.py +313 -0
- gpu4agents/core/paths.py +42 -0
- gpu4agents/core/policy.py +264 -0
- gpu4agents/core/state.py +406 -0
- gpu4agents/core/sweep.py +478 -0
- gpu4agents/core/tool_parsers.py +63 -0
- gpu4agents/providers/__init__.py +4 -0
- gpu4agents/providers/base.py +137 -0
- gpu4agents/providers/registry.py +63 -0
- gpu4agents/providers/vast/__init__.py +3 -0
- gpu4agents/providers/vast/client.py +255 -0
- gpu4agents/providers/vast/config.py +56 -0
- gpu4agents/providers/vast/mapper.py +292 -0
- gpu4agents/providers/vast/provider.py +438 -0
- gpu4agents/runtime/__init__.py +6 -0
- gpu4agents/runtime/base.py +184 -0
- gpu4agents/runtime/vllm.py +295 -0
- gpu4agents/watchdog/__init__.py +3 -0
- gpu4agents/watchdog/agent_body.py +959 -0
- gpu4agents/watchdog/builder.py +248 -0
- gpu4agents-0.5.0.dist-info/METADATA +157 -0
- gpu4agents-0.5.0.dist-info/RECORD +59 -0
- gpu4agents-0.5.0.dist-info/WHEEL +4 -0
- gpu4agents-0.5.0.dist-info/entry_points.txt +2 -0
- gpu4agents-0.5.0.dist-info/licenses/LICENSE +21 -0
- jambu_gpu/__init__.py +9 -0
- jambu_gpu/__main__.py +4 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Credential resolution (spec section 5).
|
|
2
|
+
|
|
3
|
+
Credentials never live in jambu.yaml. They are resolved from, in order:
|
|
4
|
+
environment variables, a project-local ``.env``, then ``~/.gpu/credentials``.
|
|
5
|
+
The resolution strategy can grow (keychain, secret managers, CI secrets)
|
|
6
|
+
without touching jambu.yaml or any adapter.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from .paths import gpu_home
|
|
16
|
+
|
|
17
|
+
_DOTENV_CACHE: dict[Path, dict[str, str]] = {}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _parse_dotenv(path: Path) -> dict[str, str]:
|
|
21
|
+
if path in _DOTENV_CACHE:
|
|
22
|
+
return _DOTENV_CACHE[path]
|
|
23
|
+
values: dict[str, str] = {}
|
|
24
|
+
if path.is_file():
|
|
25
|
+
for raw in path.read_text().splitlines():
|
|
26
|
+
line = raw.strip()
|
|
27
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
28
|
+
continue
|
|
29
|
+
if line.startswith("export "):
|
|
30
|
+
line = line[len("export ") :]
|
|
31
|
+
key, _, value = line.partition("=")
|
|
32
|
+
value = value.strip().strip('"').strip("'")
|
|
33
|
+
values[key.strip()] = value
|
|
34
|
+
_DOTENV_CACHE[path] = values
|
|
35
|
+
return values
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CredentialResolver:
|
|
39
|
+
"""Looks up named credentials without ever writing them to disk."""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
project_dir: Optional[Path] = None,
|
|
44
|
+
profile: str = "default",
|
|
45
|
+
extra_sources: Optional[list[tuple[str, Path]]] = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
self.project_dir = Path(project_dir or Path.cwd())
|
|
48
|
+
self.profile = profile
|
|
49
|
+
self.sources: list[tuple[str, Path]] = [
|
|
50
|
+
("project .env", self.project_dir / ".env"),
|
|
51
|
+
("user credentials", gpu_home() / "credentials"),
|
|
52
|
+
*(extra_sources or []),
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
def get(self, name: str) -> Optional[str]:
|
|
56
|
+
value = os.environ.get(name)
|
|
57
|
+
if value:
|
|
58
|
+
return value.strip()
|
|
59
|
+
# Profile-scoped override: VAST_API_KEY__staging
|
|
60
|
+
for _, path in self.sources:
|
|
61
|
+
values = _parse_dotenv(path)
|
|
62
|
+
scoped = values.get(f"{name}__{self.profile}")
|
|
63
|
+
if scoped:
|
|
64
|
+
return scoped
|
|
65
|
+
if values.get(name):
|
|
66
|
+
return values[name]
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
def file_source(self, name: str) -> Optional[str]:
|
|
70
|
+
"""Which file (not the environment) provides ``name``, if any.
|
|
71
|
+
|
|
72
|
+
A systemd user unit does not inherit the shell's environment, so
|
|
73
|
+
anything scheduled must find its credentials in one of these files.
|
|
74
|
+
"""
|
|
75
|
+
for label, path in self.sources:
|
|
76
|
+
values = _parse_dotenv(path)
|
|
77
|
+
if values.get(f"{name}__{self.profile}") or values.get(name):
|
|
78
|
+
return f"{label} ({path})"
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
def require(self, name: str) -> str:
|
|
82
|
+
value = self.get(name)
|
|
83
|
+
if not value:
|
|
84
|
+
raise KeyError(name)
|
|
85
|
+
return value
|
|
86
|
+
|
|
87
|
+
def missing(self, names: list[str]) -> list[str]:
|
|
88
|
+
return [name for name in names if not self.get(name)]
|
|
89
|
+
|
|
90
|
+
def describe_sources(self) -> list[str]:
|
|
91
|
+
out = ["environment variables"]
|
|
92
|
+
for label, path in self.sources:
|
|
93
|
+
if path.is_file():
|
|
94
|
+
out.append(f"{label} ({path})")
|
|
95
|
+
return out
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Duration parsing/formatting shared by config and CLI flags."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
_UNITS = {"s": 1, "m": 60, "h": 3600, "d": 86400, "w": 604800}
|
|
8
|
+
_TOKEN = re.compile(r"(\d+(?:\.\d+)?)\s*([smhdw])", re.IGNORECASE)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def parse_duration(value: "str | int | float | None") -> "float | None":
|
|
12
|
+
"""Parse ``6h``, ``15m``, ``1h30m``, ``90`` (seconds) into seconds.
|
|
13
|
+
|
|
14
|
+
``None`` and the literal strings ``none``/``never``/``off`` mean "no limit".
|
|
15
|
+
"""
|
|
16
|
+
if value is None:
|
|
17
|
+
return None
|
|
18
|
+
if isinstance(value, (int, float)):
|
|
19
|
+
return float(value)
|
|
20
|
+
|
|
21
|
+
text = str(value).strip().lower()
|
|
22
|
+
if not text or text in {"none", "never", "off", "unlimited", "0s"}:
|
|
23
|
+
return None
|
|
24
|
+
if re.fullmatch(r"\d+(\.\d+)?", text):
|
|
25
|
+
return float(text)
|
|
26
|
+
|
|
27
|
+
matches = _TOKEN.findall(text)
|
|
28
|
+
if not matches or "".join(f"{n}{u}" for n, u in matches).replace(" ", "") != text.replace(" ", ""):
|
|
29
|
+
raise ValueError(f"invalid duration: {value!r} (expected e.g. 30s, 15m, 6h, 1h30m)")
|
|
30
|
+
return float(sum(float(n) * _UNITS[u.lower()] for n, u in matches))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def format_duration(seconds: "float | None") -> str:
|
|
34
|
+
"""Render seconds as a compact human string (``1h30m``)."""
|
|
35
|
+
if seconds is None:
|
|
36
|
+
return "-"
|
|
37
|
+
seconds = int(max(0, round(seconds)))
|
|
38
|
+
if seconds < 60:
|
|
39
|
+
return f"{seconds}s"
|
|
40
|
+
parts: list[str] = []
|
|
41
|
+
for unit, size in (("d", 86400), ("h", 3600), ("m", 60), ("s", 1)):
|
|
42
|
+
if seconds >= size:
|
|
43
|
+
amount, seconds = divmod(seconds, size)
|
|
44
|
+
parts.append(f"{amount}{unit}")
|
|
45
|
+
if len(parts) == 2:
|
|
46
|
+
break
|
|
47
|
+
return "".join(parts)
|