devops-bot-sdk 1.4.125__tar.gz → 1.4.129__tar.gz
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.
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/SOURCES.txt +1 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/__init__.py +2 -2
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/run_auto.py +11 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/api.py +108 -124
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/ip_allowlist.py +15 -0
- devops_bot_sdk-1.4.129/sdk/ui/models.py +366 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/server.py +13 -7
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/service.py +35 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/README.md +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/requires.txt +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/devops_bot_sdk.egg-info/top_level.txt +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/pyproject.toml +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agentd.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/__init__.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/builder.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/chat.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/connectors.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/engines/__init__.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/engines/claude_code.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/engines/http.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/engines/mcp.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/engines/n8n.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/engines/shell.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/integrations.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/inventory.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/library/__init__.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/manifest.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/prompt_studio.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/prompt_template.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/prompt_vars.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/run_log.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/secrets.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/usage.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/agents/varstore.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/cli.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/client.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/collectors/__init__.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/collectors/files.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/collectors/process.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/collectors/screenshot.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/config.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/crucial.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/design_verify/__init__.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/design_verify/assets.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/design_verify/bootstrap.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/design_verify/browser.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/design_verify/compare.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/design_verify/loop.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/exceptions.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/git_ops.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/graphify.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/hooks/__init__.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/hooks/crucial_guard.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ipc/__init__.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ipc/electron_bridge.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ipc/handlers.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/local_exec.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/models/__init__.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/models/envelope.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/models/requests.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/models/responses.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/models/snapshots.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/platform_compat.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/py.typed +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/sse.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/test.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/test_pipeline.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/__init__.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/dashboard.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/jobs.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/runner.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/security.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/setup_manager.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/static/agents.html +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/static/app.js +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/static/custom-agents.html +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/static/default-agents.html +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/static/index.html +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/static/runs.html +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/static/setup.html +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/static/styles.css +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/ui/static/tickets.html +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/sdk/updater.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/setup.cfg +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/tests/test_agent_chat_features.py +0 -0
- {devops_bot_sdk-1.4.125 → devops_bot_sdk-1.4.129}/tests/test_agents_connectors_builder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
|
|
2
2
|
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.129
|
|
4
4
|
|
|
5
5
|
Public surface:
|
|
6
6
|
BackendClient.from_config() — create client from ~/.agentos/config.toml
|
|
@@ -30,7 +30,7 @@ Rules:
|
|
|
30
30
|
- All data egress through submit_webhook only
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
__version__ = "1.4.
|
|
33
|
+
__version__ = "1.4.129" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
|
|
34
34
|
__author__ = "AgentOS" # sdk.client.SDK_VERSION and the UI server version all read this.
|
|
35
35
|
|
|
36
36
|
from sdk.client import BackendClient
|
|
@@ -565,6 +565,17 @@ def main() -> None:
|
|
|
565
565
|
after = current_version()
|
|
566
566
|
if after != before:
|
|
567
567
|
print(f"✓ updated {before} → {after}")
|
|
568
|
+
# Reload the control-panel server so newly-added /api/ui/* routes take
|
|
569
|
+
# effect — a running server keeps the code it imported at boot. Only
|
|
570
|
+
# restarts a managed (systemd/launchd/schtask) service; a manually-run
|
|
571
|
+
# panel is left alone with a hint.
|
|
572
|
+
try:
|
|
573
|
+
from sdk.ui import service as ui_service
|
|
574
|
+
ok, msg = ui_service.restart()
|
|
575
|
+
print(f" control panel: {msg}" if ok
|
|
576
|
+
else f" control panel: not reloaded — {msg}")
|
|
577
|
+
except Exception as exc: # noqa: BLE001 — never fail the update on this
|
|
578
|
+
print(f" control panel: not reloaded ({exc})")
|
|
568
579
|
pid = _daemon_running()
|
|
569
580
|
if pid:
|
|
570
581
|
print("note: a background loop is running the OLD version — "
|
|
@@ -7,10 +7,29 @@ and ``sdk.config`` — no business logic is reimplemented here.
|
|
|
7
7
|
"""
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
|
10
|
+
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Security
|
|
11
11
|
from fastapi.responses import StreamingResponse
|
|
12
|
+
from fastapi.security import APIKeyHeader
|
|
12
13
|
|
|
13
14
|
from sdk.ui import dashboard, runner, security, setup_manager
|
|
15
|
+
from sdk.ui.models import (
|
|
16
|
+
AgentChatRequest,
|
|
17
|
+
AgentKeysSet,
|
|
18
|
+
AgentPromptGenerateRequest,
|
|
19
|
+
AgentRunRequest,
|
|
20
|
+
AllowlistEntry,
|
|
21
|
+
ConfigureRequest,
|
|
22
|
+
PromptRegenerateRequest,
|
|
23
|
+
PromptRunRequest,
|
|
24
|
+
PromptVerifyRequest,
|
|
25
|
+
SetupRequest,
|
|
26
|
+
StoryRequest,
|
|
27
|
+
SystemChatRequest,
|
|
28
|
+
SystemPromptRequest,
|
|
29
|
+
TicketRequest,
|
|
30
|
+
UpdateAgentRequest,
|
|
31
|
+
WorkspaceRequest,
|
|
32
|
+
)
|
|
14
33
|
|
|
15
34
|
router = APIRouter(prefix="/api/ui")
|
|
16
35
|
|
|
@@ -19,10 +38,25 @@ router = APIRouter(prefix="/api/ui")
|
|
|
19
38
|
|
|
20
39
|
_LOOPBACK = {"127.0.0.1", "::1", "localhost"}
|
|
21
40
|
|
|
22
|
-
|
|
23
|
-
|
|
41
|
+
# Declared PURELY so Swagger renders an "Authorize" box — paste your own co_ token
|
|
42
|
+
# there (nothing is ever pre-filled server-side). auto_error=False: loopback needs
|
|
43
|
+
# no token, and the token may also arrive via ?token= (SSE) or X-AgentOS-UI-Token,
|
|
44
|
+
# so the scheme documents the header without owning enforcement — that stays below.
|
|
45
|
+
_token_scheme = APIKeyHeader(
|
|
46
|
+
name="Authorization",
|
|
47
|
+
scheme_name="co_token",
|
|
48
|
+
auto_error=False,
|
|
49
|
+
description="Your DevOpsBot API token — paste the raw co_… value (or 'Bearer co_…').",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def require_token(
|
|
54
|
+
request: Request,
|
|
55
|
+
_scheme: str | None = Security(_token_scheme),
|
|
56
|
+
) -> None:
|
|
24
57
|
# Same-machine (loopback) access is trusted — no token needed. Remote access
|
|
25
|
-
# must present the configured co_ API token.
|
|
58
|
+
# must present the configured co_ API token. (``_scheme`` only wires the
|
|
59
|
+
# Swagger Authorize box; the actual token is read below to cover all forms.)
|
|
26
60
|
host = (request.client.host if request.client else "") or ""
|
|
27
61
|
if host in _LOOPBACK:
|
|
28
62
|
return
|
|
@@ -66,15 +100,6 @@ async def status() -> dict:
|
|
|
66
100
|
# middleware re-reads it per request so a new IP works immediately (no restart).
|
|
67
101
|
# The backend proxies here (POST /api/v1/sdk/whitelist-ip) to open access for an IP.
|
|
68
102
|
|
|
69
|
-
def _valid_ip_or_cidr(value: str) -> bool:
|
|
70
|
-
import ipaddress
|
|
71
|
-
try:
|
|
72
|
-
ipaddress.ip_network(value, strict=False)
|
|
73
|
-
return True
|
|
74
|
-
except ValueError:
|
|
75
|
-
return False
|
|
76
|
-
|
|
77
|
-
|
|
78
103
|
@router.get("/allowlist", dependencies=[_auth])
|
|
79
104
|
async def get_allowlist() -> dict:
|
|
80
105
|
"""This SDK's allowlist, split into permanent vs runtime.
|
|
@@ -93,20 +118,17 @@ async def get_allowlist() -> dict:
|
|
|
93
118
|
|
|
94
119
|
|
|
95
120
|
@router.post("/allowlist", dependencies=[_auth])
|
|
96
|
-
async def add_to_allowlist(body:
|
|
121
|
+
async def add_to_allowlist(body: AllowlistEntry) -> dict:
|
|
97
122
|
"""Whitelist an IP/CIDR for this SDK at runtime. Body: ``{ip}``. Idempotent.
|
|
98
123
|
|
|
99
124
|
Opens the IP gate for that client across every path (all endpoints + UI + docs)
|
|
100
125
|
until it's disabled. Effective immediately (the middleware re-reads per request).
|
|
126
|
+
The ``ip`` field is validated (IPv4/IPv6 address or CIDR) by ``AllowlistEntry``;
|
|
127
|
+
a malformed or missing value returns 422 before this handler runs.
|
|
101
128
|
"""
|
|
102
129
|
from sdk.config import add_allowed_ip
|
|
103
130
|
|
|
104
|
-
|
|
105
|
-
if not ip:
|
|
106
|
-
raise HTTPException(status_code=400, detail="ip is required.")
|
|
107
|
-
if not _valid_ip_or_cidr(ip):
|
|
108
|
-
raise HTTPException(status_code=400, detail=f"invalid IP or CIDR: {ip!r}")
|
|
109
|
-
return {"added": ip, "allowed_ips": add_allowed_ip(ip)}
|
|
131
|
+
return {"added": body.ip, "allowed_ips": add_allowed_ip(body.ip)}
|
|
110
132
|
|
|
111
133
|
|
|
112
134
|
@router.delete("/allowlist", dependencies=[_auth])
|
|
@@ -121,20 +143,17 @@ async def remove_from_allowlist(ip: str = Query(...)) -> dict:
|
|
|
121
143
|
|
|
122
144
|
|
|
123
145
|
@router.post("/configure", dependencies=[_auth])
|
|
124
|
-
async def configure(body:
|
|
146
|
+
async def configure(body: ConfigureRequest) -> dict:
|
|
125
147
|
from sdk import config as sdk_config
|
|
126
148
|
|
|
127
|
-
|
|
128
|
-
if not token.startswith("co_"):
|
|
129
|
-
raise HTTPException(status_code=400, detail="Token must start with 'co_'.")
|
|
130
|
-
sdk_config.save(sdk_config.get_base_url(), token)
|
|
149
|
+
sdk_config.save(sdk_config.get_base_url(), body.token)
|
|
131
150
|
ok, message = await dashboard.validate_token()
|
|
132
151
|
return {"saved": True, "connected": ok, "message": message}
|
|
133
152
|
|
|
134
153
|
|
|
135
154
|
@router.post("/setup", dependencies=[_auth])
|
|
136
|
-
async def setup(body:
|
|
137
|
-
upgrade =
|
|
155
|
+
async def setup(body: SetupRequest | None = None) -> StreamingResponse:
|
|
156
|
+
upgrade = (body or SetupRequest()).upgrade
|
|
138
157
|
|
|
139
158
|
async def gen():
|
|
140
159
|
async for chunk in setup_manager.reinstall_sdk(upgrade=upgrade):
|
|
@@ -168,44 +187,34 @@ async def restart_loop() -> dict:
|
|
|
168
187
|
# ── Execution (one per agentos-auto subcommand) ────────────────────────────
|
|
169
188
|
|
|
170
189
|
@router.post("/run", dependencies=[_auth])
|
|
171
|
-
async def run(body:
|
|
190
|
+
async def run(body: WorkspaceRequest | None = None) -> dict:
|
|
172
191
|
args = ["run"]
|
|
173
|
-
root = (body or
|
|
192
|
+
root = (body or WorkspaceRequest()).workspace_root
|
|
174
193
|
if root:
|
|
175
|
-
args += ["--workspace-root",
|
|
194
|
+
args += ["--workspace-root", root]
|
|
176
195
|
return runner.spawn_auto(*args)
|
|
177
196
|
|
|
178
197
|
|
|
179
198
|
@router.post("/resume", dependencies=[_auth])
|
|
180
|
-
async def resume(body:
|
|
199
|
+
async def resume(body: WorkspaceRequest | None = None) -> dict:
|
|
181
200
|
args = ["resume"]
|
|
182
|
-
root = (body or
|
|
201
|
+
root = (body or WorkspaceRequest()).workspace_root
|
|
183
202
|
if root:
|
|
184
|
-
args += ["--workspace-root",
|
|
203
|
+
args += ["--workspace-root", root]
|
|
185
204
|
return runner.spawn_auto(*args)
|
|
186
205
|
|
|
187
206
|
|
|
188
207
|
@router.post("/ticket", dependencies=[_auth])
|
|
189
|
-
async def ticket(body:
|
|
190
|
-
|
|
191
|
-
if
|
|
192
|
-
|
|
193
|
-
keys = [str(k).strip() for k in keys if str(k).strip()]
|
|
194
|
-
if not keys:
|
|
195
|
-
raise HTTPException(status_code=400, detail="Provide at least one ticket key.")
|
|
196
|
-
args = ["ticket", *keys]
|
|
197
|
-
root = body.get("workspace_root")
|
|
198
|
-
if root:
|
|
199
|
-
args += ["--workspace-root", str(root)]
|
|
208
|
+
async def ticket(body: TicketRequest) -> dict:
|
|
209
|
+
args = ["ticket", *body.keys]
|
|
210
|
+
if body.workspace_root:
|
|
211
|
+
args += ["--workspace-root", body.workspace_root]
|
|
200
212
|
return runner.spawn_auto(*args)
|
|
201
213
|
|
|
202
214
|
|
|
203
215
|
@router.post("/story", dependencies=[_auth])
|
|
204
|
-
async def story(body:
|
|
205
|
-
keys = (body or
|
|
206
|
-
if isinstance(keys, str):
|
|
207
|
-
keys = keys.split()
|
|
208
|
-
keys = [str(k).strip() for k in keys if str(k).strip()]
|
|
216
|
+
async def story(body: StoryRequest | None = None) -> dict:
|
|
217
|
+
keys = (body or StoryRequest()).keys
|
|
209
218
|
return runner.spawn_auto("story", *keys)
|
|
210
219
|
|
|
211
220
|
|
|
@@ -410,7 +419,7 @@ async def agent_keys_list(name: str = Query(...)) -> dict:
|
|
|
410
419
|
|
|
411
420
|
|
|
412
421
|
@router.post("/agents/keys", dependencies=[_auth])
|
|
413
|
-
async def agent_keys_set(body:
|
|
422
|
+
async def agent_keys_set(body: AgentKeysSet) -> dict:
|
|
414
423
|
"""Persist/update a custom agent's API keys (encrypted, local, per-agent).
|
|
415
424
|
|
|
416
425
|
Body: ``name``, ``keys`` ({ENV_VAR: value}). Merge semantics — keys omitted
|
|
@@ -419,14 +428,10 @@ async def agent_keys_set(body: dict) -> dict:
|
|
|
419
428
|
"""
|
|
420
429
|
from sdk.agents import secrets as agent_secrets
|
|
421
430
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
if not isinstance(keys, dict) or not keys:
|
|
427
|
-
raise HTTPException(status_code=400, detail="keys must be a non-empty object.")
|
|
428
|
-
stored = agent_secrets.save_secrets(name, {str(k): str(v) for k, v in keys.items()}, merge=True)
|
|
429
|
-
return {"name": name, "keys": sorted(stored.keys())}
|
|
431
|
+
stored = agent_secrets.save_secrets(
|
|
432
|
+
body.name, {str(k): str(v) for k, v in body.keys.items()}, merge=True
|
|
433
|
+
)
|
|
434
|
+
return {"name": body.name, "keys": sorted(stored.keys())}
|
|
430
435
|
|
|
431
436
|
|
|
432
437
|
@router.delete("/agents/keys", dependencies=[_auth])
|
|
@@ -500,7 +505,7 @@ async def job_cancel(job_id: str) -> dict:
|
|
|
500
505
|
|
|
501
506
|
|
|
502
507
|
@router.patch("/agents", dependencies=[_auth])
|
|
503
|
-
async def update_agent(body:
|
|
508
|
+
async def update_agent(body: UpdateAgentRequest) -> dict:
|
|
504
509
|
"""Update a local custom agent (description / prompt / tools / enabled status).
|
|
505
510
|
|
|
506
511
|
Re-validates and rewrites the local manifest, and best-effort syncs the change
|
|
@@ -510,20 +515,19 @@ async def update_agent(body: dict) -> dict:
|
|
|
510
515
|
|
|
511
516
|
from sdk.agents.manifest import AGENTS_DIR, AgentManifest, load_agents
|
|
512
517
|
|
|
513
|
-
name =
|
|
514
|
-
|
|
515
|
-
raise HTTPException(status_code=400, detail="name is required.")
|
|
518
|
+
name = body.name
|
|
519
|
+
sent = body.model_fields_set # PATCH: only fields the caller sent are applied
|
|
516
520
|
manifest = next((a for a in load_agents() if a.name == name), None)
|
|
517
521
|
if manifest is None:
|
|
518
522
|
raise HTTPException(status_code=404, detail=f"no local agent '{name}'")
|
|
519
523
|
|
|
520
524
|
local_changes: dict = {}
|
|
521
|
-
if "description" in
|
|
522
|
-
local_changes["description"] = str(body.
|
|
523
|
-
if "prompt" in
|
|
524
|
-
local_changes["prompt"] = str(body.
|
|
525
|
-
if "allowed_tools" in
|
|
526
|
-
local_changes["allowed_tools"] = list(body.
|
|
525
|
+
if "description" in sent:
|
|
526
|
+
local_changes["description"] = str(body.description or "")
|
|
527
|
+
if "prompt" in sent:
|
|
528
|
+
local_changes["prompt"] = str(body.prompt or "")
|
|
529
|
+
if "allowed_tools" in sent:
|
|
530
|
+
local_changes["allowed_tools"] = list(body.allowed_tools or [])
|
|
527
531
|
|
|
528
532
|
updated = None
|
|
529
533
|
if local_changes:
|
|
@@ -536,7 +540,7 @@ async def update_agent(body: dict) -> dict:
|
|
|
536
540
|
encoding="utf-8",
|
|
537
541
|
)
|
|
538
542
|
|
|
539
|
-
status =
|
|
543
|
+
status = (body.status or "").strip()
|
|
540
544
|
# Backend sync (best-effort). Agent CREATION/authoring is not owned by the SDK
|
|
541
545
|
# anymore, so a content edit is applied LOCALLY only — no re-register upsert.
|
|
542
546
|
# A status change still syncs to the backend (per-agent enable/disable).
|
|
@@ -577,7 +581,7 @@ async def delete_agent(name: str = Query(...)) -> dict:
|
|
|
577
581
|
|
|
578
582
|
|
|
579
583
|
@router.post("/agents/chat", dependencies=[_auth])
|
|
580
|
-
async def agent_chat(body:
|
|
584
|
+
async def agent_chat(body: AgentChatRequest) -> dict:
|
|
581
585
|
"""Chat with an agent (runs its engine on this machine).
|
|
582
586
|
|
|
583
587
|
Body: ``name``, ``message``, optional ``variables`` ({{var}} values), optional
|
|
@@ -587,13 +591,11 @@ async def agent_chat(body: dict) -> dict:
|
|
|
587
591
|
"""
|
|
588
592
|
from sdk.ui import jobs
|
|
589
593
|
|
|
590
|
-
name =
|
|
591
|
-
message =
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
history = body.get("history") or []
|
|
596
|
-
department_id = str(body.get("department_id", "")).strip()
|
|
594
|
+
name = body.name
|
|
595
|
+
message = body.message
|
|
596
|
+
variables = body.variables
|
|
597
|
+
history = body.history
|
|
598
|
+
department_id = body.department_id
|
|
597
599
|
|
|
598
600
|
async def work() -> dict:
|
|
599
601
|
import time as _time
|
|
@@ -621,7 +623,7 @@ async def agent_chat(body: dict) -> dict:
|
|
|
621
623
|
|
|
622
624
|
|
|
623
625
|
@router.post("/agents/run", dependencies=[_auth])
|
|
624
|
-
async def agent_run(body:
|
|
626
|
+
async def agent_run(body: AgentRunRequest) -> dict:
|
|
625
627
|
"""Run an agent once with fresh ``{{variable}}`` values — no chat.
|
|
626
628
|
|
|
627
629
|
Body: ``name``, ``variables`` ({{var}} → value; may differ every run),
|
|
@@ -633,12 +635,10 @@ async def agent_run(body: dict) -> dict:
|
|
|
633
635
|
"""
|
|
634
636
|
from sdk.ui import jobs
|
|
635
637
|
|
|
636
|
-
name =
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
department_id = str(body.get("department_id", "")).strip()
|
|
641
|
-
scope = str(body.get("scope") or body.get("source") or "custom").strip() or "custom"
|
|
638
|
+
name = body.name
|
|
639
|
+
variables = body.variables
|
|
640
|
+
department_id = body.department_id
|
|
641
|
+
scope = body.scope
|
|
642
642
|
|
|
643
643
|
async def work() -> dict:
|
|
644
644
|
import time as _time
|
|
@@ -672,7 +672,7 @@ async def system_inventory() -> dict:
|
|
|
672
672
|
|
|
673
673
|
|
|
674
674
|
@router.post("/system/chat", dependencies=[_auth])
|
|
675
|
-
async def system_chat(body:
|
|
675
|
+
async def system_chat(body: SystemChatRequest) -> dict:
|
|
676
676
|
"""Ask the System Assistant about agents, subagents, tools, plugins & usage.
|
|
677
677
|
|
|
678
678
|
Grounds the local model in the live inventory JSON and answers the question.
|
|
@@ -681,10 +681,8 @@ async def system_chat(body: dict) -> dict:
|
|
|
681
681
|
"""
|
|
682
682
|
from sdk.ui import jobs
|
|
683
683
|
|
|
684
|
-
message =
|
|
685
|
-
|
|
686
|
-
raise HTTPException(status_code=400, detail="message is required.")
|
|
687
|
-
history = body.get("history") or []
|
|
684
|
+
message = body.message
|
|
685
|
+
history = body.history
|
|
688
686
|
|
|
689
687
|
async def work() -> dict:
|
|
690
688
|
from pathlib import Path
|
|
@@ -732,7 +730,7 @@ async def agent_prompt(name: str = Query(...)) -> dict:
|
|
|
732
730
|
|
|
733
731
|
|
|
734
732
|
@router.post("/prompt/analyze", dependencies=[_auth])
|
|
735
|
-
async def prompt_analyze(body:
|
|
733
|
+
async def prompt_analyze(body: SystemPromptRequest) -> dict:
|
|
736
734
|
"""AI reads a system prompt and infers its input variables + output/format.
|
|
737
735
|
|
|
738
736
|
Body: ``system_prompt``. Runs as a detached job (navigation-safe); poll
|
|
@@ -740,9 +738,7 @@ async def prompt_analyze(body: dict) -> dict:
|
|
|
740
738
|
"""
|
|
741
739
|
from sdk.ui import jobs
|
|
742
740
|
|
|
743
|
-
system_prompt =
|
|
744
|
-
if len(system_prompt) < 10:
|
|
745
|
-
raise HTTPException(status_code=400, detail="Write a system prompt first (a bit longer).")
|
|
741
|
+
system_prompt = body.system_prompt
|
|
746
742
|
|
|
747
743
|
async def work() -> dict:
|
|
748
744
|
from pathlib import Path
|
|
@@ -762,7 +758,7 @@ async def prompt_analyze(body: dict) -> dict:
|
|
|
762
758
|
|
|
763
759
|
|
|
764
760
|
@router.post("/prompt/comments", dependencies=[_auth])
|
|
765
|
-
async def prompt_comments(body:
|
|
761
|
+
async def prompt_comments(body: SystemPromptRequest) -> dict:
|
|
766
762
|
"""AI reviews the system prompt and returns open questions as 'comments'.
|
|
767
763
|
|
|
768
764
|
Resolve these by editing the prompt BEFORE building the agent — so the agent
|
|
@@ -771,9 +767,7 @@ async def prompt_comments(body: dict) -> dict:
|
|
|
771
767
|
"""
|
|
772
768
|
from sdk.ui import jobs
|
|
773
769
|
|
|
774
|
-
system_prompt =
|
|
775
|
-
if len(system_prompt) < 10:
|
|
776
|
-
raise HTTPException(status_code=400, detail="Write a system prompt first (a bit longer).")
|
|
770
|
+
system_prompt = body.system_prompt
|
|
777
771
|
|
|
778
772
|
async def work() -> dict:
|
|
779
773
|
from pathlib import Path
|
|
@@ -793,7 +787,7 @@ async def prompt_comments(body: dict) -> dict:
|
|
|
793
787
|
|
|
794
788
|
|
|
795
789
|
@router.post("/prompt/run", dependencies=[_auth])
|
|
796
|
-
async def prompt_run(body:
|
|
790
|
+
async def prompt_run(body: PromptRunRequest) -> dict:
|
|
797
791
|
"""Run a system prompt with the user-supplied input values → the response.
|
|
798
792
|
|
|
799
793
|
Body: ``system_prompt``, ``inputs`` ({name: value}), optional ``output_format``,
|
|
@@ -804,14 +798,12 @@ async def prompt_run(body: dict) -> dict:
|
|
|
804
798
|
from sdk.agents.prompt_studio import sanitize_tools
|
|
805
799
|
from sdk.ui import jobs
|
|
806
800
|
|
|
807
|
-
system_prompt =
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
inputs = body.get("inputs") or {}
|
|
811
|
-
output_format = str(body.get("output_format", "")).strip()
|
|
801
|
+
system_prompt = body.system_prompt
|
|
802
|
+
inputs = body.inputs
|
|
803
|
+
output_format = body.output_format
|
|
812
804
|
# AI-assessed tools; fall back to a minimal read-only tool so a pure text task
|
|
813
805
|
# never inherits the broad default allowlist.
|
|
814
|
-
tools = sanitize_tools(body.
|
|
806
|
+
tools = sanitize_tools(body.tools) or ["Read"]
|
|
815
807
|
|
|
816
808
|
async def work() -> dict:
|
|
817
809
|
import time as _time
|
|
@@ -858,7 +850,7 @@ async def _run_prompt_engine(prompt: str) -> str:
|
|
|
858
850
|
|
|
859
851
|
|
|
860
852
|
@router.post("/agent-prompt/generate", dependencies=[_auth])
|
|
861
|
-
async def agent_prompt_generate(body:
|
|
853
|
+
async def agent_prompt_generate(body: AgentPromptGenerateRequest) -> dict:
|
|
862
854
|
"""name + type + job → a structured system prompt + {{variables}} (claude_code).
|
|
863
855
|
|
|
864
856
|
Body: ``agent_name``, ``agent_type``, ``job``, optional ``context``.
|
|
@@ -866,14 +858,12 @@ async def agent_prompt_generate(body: dict) -> dict:
|
|
|
866
858
|
"""
|
|
867
859
|
from sdk.agents.prompt_studio import build_generate_prompt, parse_generation
|
|
868
860
|
|
|
869
|
-
agent_name =
|
|
870
|
-
agent_type =
|
|
871
|
-
job =
|
|
872
|
-
if not agent_name or not agent_type or not job:
|
|
873
|
-
raise HTTPException(status_code=400, detail="agent_name, agent_type, and job are required.")
|
|
861
|
+
agent_name = body.agent_name
|
|
862
|
+
agent_type = body.agent_type
|
|
863
|
+
job = body.job
|
|
874
864
|
|
|
875
865
|
text = await _run_prompt_engine(
|
|
876
|
-
build_generate_prompt(agent_name, agent_type, job,
|
|
866
|
+
build_generate_prompt(agent_name, agent_type, job, body.context)
|
|
877
867
|
)
|
|
878
868
|
parsed = parse_generation(text)
|
|
879
869
|
if not parsed["system_prompt"]:
|
|
@@ -882,7 +872,7 @@ async def agent_prompt_generate(body: dict) -> dict:
|
|
|
882
872
|
|
|
883
873
|
|
|
884
874
|
@router.post("/agent-prompt/regenerate", dependencies=[_auth])
|
|
885
|
-
async def agent_prompt_regenerate(body:
|
|
875
|
+
async def agent_prompt_regenerate(body: PromptRegenerateRequest) -> dict:
|
|
886
876
|
"""Improve an existing prompt (optional feedback) → prompt + {{variables}}.
|
|
887
877
|
|
|
888
878
|
Body: ``system_prompt``, optional ``feedback``, ``agent_name``, ``agent_type``,
|
|
@@ -890,24 +880,20 @@ async def agent_prompt_regenerate(body: dict) -> dict:
|
|
|
890
880
|
"""
|
|
891
881
|
from sdk.agents.prompt_studio import build_regenerate_prompt, parse_generation
|
|
892
882
|
|
|
893
|
-
system_prompt = str(body.get("system_prompt", "")).strip()
|
|
894
|
-
if not system_prompt:
|
|
895
|
-
raise HTTPException(status_code=400, detail="system_prompt is required.")
|
|
896
|
-
|
|
897
883
|
text = await _run_prompt_engine(build_regenerate_prompt(
|
|
898
|
-
system_prompt,
|
|
899
|
-
|
|
884
|
+
body.system_prompt, body.feedback,
|
|
885
|
+
body.agent_name, body.agent_type, body.job,
|
|
900
886
|
))
|
|
901
887
|
parsed = parse_generation(text)
|
|
902
888
|
if not parsed["system_prompt"]:
|
|
903
889
|
raise HTTPException(status_code=502, detail="regeneration returned no system_prompt")
|
|
904
|
-
return {"agent_name":
|
|
905
|
-
"agent_type":
|
|
890
|
+
return {"agent_name": body.agent_name,
|
|
891
|
+
"agent_type": body.agent_type,
|
|
906
892
|
"engine": "claude_code", **parsed}
|
|
907
893
|
|
|
908
894
|
|
|
909
895
|
@router.post("/agent-prompt/verify", dependencies=[_auth])
|
|
910
|
-
async def agent_prompt_verify(body:
|
|
896
|
+
async def agent_prompt_verify(body: PromptVerifyRequest) -> dict:
|
|
911
897
|
"""Manual-trigger flow: verify a user-written prompt, then extract vars + secrets.
|
|
912
898
|
|
|
913
899
|
Standards check is deterministic (our Role/Objective/Rules/Workflow/Output
|
|
@@ -919,9 +905,7 @@ async def agent_prompt_verify(body: dict) -> dict:
|
|
|
919
905
|
from sdk.agents.prompt_template import check_system_prompt
|
|
920
906
|
from sdk.agents.prompt_vars import extract_variables
|
|
921
907
|
|
|
922
|
-
system_prompt =
|
|
923
|
-
if len(system_prompt) < 10:
|
|
924
|
-
raise HTTPException(status_code=400, detail="Write a system prompt first (a bit longer).")
|
|
908
|
+
system_prompt = body.system_prompt
|
|
925
909
|
|
|
926
910
|
standards = check_system_prompt(system_prompt)
|
|
927
911
|
|
|
@@ -21,6 +21,17 @@ import os
|
|
|
21
21
|
import socket
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def public_mode() -> bool:
|
|
25
|
+
"""Opt-in PUBLIC mode (``AGENTOS_UI_PUBLIC=1``).
|
|
26
|
+
|
|
27
|
+
When set, the SDK is a public, token-authenticated server: the IP gate is
|
|
28
|
+
lifted so Swagger (``/docs``) is browsable from any IP and every ``/api/ui/*``
|
|
29
|
+
path relies SOLELY on the ``co_`` token (``require_token``) as its credential.
|
|
30
|
+
Off by default — a plain launch stays locked to loopback + allowlisted IPs.
|
|
31
|
+
"""
|
|
32
|
+
return os.environ.get("AGENTOS_UI_PUBLIC", "").strip().lower() in ("1", "true", "yes", "on")
|
|
33
|
+
|
|
34
|
+
|
|
24
35
|
def _loopback(ip: ipaddress._BaseAddress) -> bool:
|
|
25
36
|
return ip.is_loopback
|
|
26
37
|
|
|
@@ -107,6 +118,10 @@ class IPAllowlistMiddleware(BaseHTTPMiddleware):
|
|
|
107
118
|
return any(ip in net for net in (self._local_nets + self._configured_nets()))
|
|
108
119
|
|
|
109
120
|
async def dispatch(self, request: Request, call_next):
|
|
121
|
+
# PUBLIC mode: lift the IP gate entirely — Swagger is browsable by anyone
|
|
122
|
+
# and /api/ui/* is guarded only by the co_ token (require_token). Opt-in.
|
|
123
|
+
if public_mode():
|
|
124
|
+
return await call_next(request)
|
|
110
125
|
# Guard EVERY path — nothing on this server is public.
|
|
111
126
|
if not self._permitted(self._client_ip(request)):
|
|
112
127
|
return JSONResponse(
|