zenefil commited on
Commit
0937ecd
·
verified ·
1 Parent(s): 66a443b

Delete api.py

Browse files
Files changed (1) hide show
  1. api.py +0 -206
api.py DELETED
@@ -1,206 +0,0 @@
1
- \
2
- import asyncio
3
- import os
4
- import uuid
5
- from datetime import datetime
6
- from typing import Dict, List, Optional, Literal
7
-
8
- from fastapi import FastAPI, HTTPException, Request
9
- from fastapi.responses import HTMLResponse, JSONResponse
10
- from pydantic import BaseModel, Field
11
-
12
- # ===== In-Memory Stores =====
13
- agents: Dict[str, dict] = {}
14
- queues: Dict[str, List[dict]] = {}
15
- jobs: Dict[str, dict] = {}
16
- results: Dict[str, dict] = {}
17
- waiters: Dict[str, asyncio.Condition] = {}
18
-
19
- ADMIN_SECRET = os.environ.get("ADMIN_SECRET") # optional
20
-
21
- def now() -> str:
22
- return datetime.utcnow().isoformat() + "Z"
23
-
24
- def cond_for(agent_id: str) -> asyncio.Condition:
25
- if agent_id not in waiters:
26
- waiters[agent_id] = asyncio.Condition()
27
- return waiters[agent_id]
28
-
29
- # ===== Models =====
30
- class RegisterBody(BaseModel):
31
- display_name: str = Field(..., min_length=1, max_length=80)
32
-
33
- class RegisterResp(BaseModel):
34
- agent_id: str
35
- token: str
36
-
37
- class PlanStep(BaseModel):
38
- act: Literal["get","wait","click","type","sleep","screenshot","script","scroll"]
39
- by: Optional[Literal["css","xpath","id","name","tag","link_text","partial_link_text"]] = None
40
- sel: Optional[str] = None
41
- text: Optional[str] = None
42
- timeout: Optional[int] = 20
43
- sec: Optional[float] = None
44
- js: Optional[str] = None
45
- label: Optional[str] = None
46
-
47
- class EnqueuePlanBody(BaseModel):
48
- agent_id: str
49
- plan: List[PlanStep]
50
- admin_secret: Optional[str] = None
51
-
52
- class NextJobQuery(BaseModel):
53
- agent_id: str
54
- token: str
55
- wait: int = 25
56
-
57
- class SubmitResultBody(BaseModel):
58
- agent_id: str
59
- token: str
60
- job_id: str
61
- status: Literal["completed","failed"]
62
- message: Optional[str] = None
63
- artifacts: Optional[dict] = None # can contain screenshots, titles, etc.
64
-
65
- # ===== App =====
66
- app = FastAPI(title="Hybrid Agent Orchestrator (Refactor)", version="1.0")
67
-
68
- def assert_agent(agent_id: str, token: str) -> dict:
69
- a = agents.get(agent_id)
70
- if not a:
71
- raise HTTPException(status_code=404, detail="unknown agent")
72
- if a["token"] != token:
73
- raise HTTPException(status_code=401, detail="bad token")
74
- return a
75
-
76
- def push_job(agent_id: str, payload: dict) -> dict:
77
- if agent_id not in agents:
78
- raise HTTPException(status_code=404, detail="unknown agent")
79
- job_id = str(uuid.uuid4())
80
- job = {"id": job_id, "agent_id": agent_id, "payload": payload, "status":"queued", "created_at": now(), "updated_at": now()}
81
- jobs[job_id] = job
82
- queues.setdefault(agent_id, []).append(job)
83
-
84
- async def notify():
85
- async with cond_for(agent_id):
86
- cond_for(agent_id).notify_all()
87
- asyncio.create_task(notify())
88
-
89
- return job
90
-
91
- # ===== Endpoints =====
92
- @app.post("/register", response_model=RegisterResp)
93
- async def register(body: RegisterBody):
94
- agent_id = str(uuid.uuid4())
95
- token = str(uuid.uuid4())
96
- agents[agent_id] = {"agent_id": agent_id, "token": token, "display_name": body.display_name, "registered_at": now(), "last_seen": now()}
97
- queues[agent_id] = []
98
- return RegisterResp(agent_id=agent_id, token=token)
99
-
100
- @app.get("/agents")
101
- async def list_agents():
102
- return {"agents": list(agents.values())}
103
-
104
- @app.post("/enqueue-plan")
105
- async def enqueue_plan(body: EnqueuePlanBody):
106
- if ADMIN_SECRET and body.admin_secret != ADMIN_SECRET:
107
- raise HTTPException(status_code=401, detail="admin_secret invalid")
108
- job = push_job(body.agent_id, {"type":"plan","steps":[s.model_dump() for s in body.plan]})
109
- return {"job": job}
110
-
111
- @app.get("/next-job")
112
- async def next_job(agent_id: str, token: str, wait: int = 25):
113
- assert_agent(agent_id, token)
114
- agents[agent_id]["last_seen"] = now()
115
-
116
- q = queues.get(agent_id, [])
117
- if q:
118
- job = q.pop(0); job["status"]="taken"; job["updated_at"]=now(); return {"job": job}
119
-
120
- c = cond_for(agent_id)
121
- try:
122
- async with asyncio.timeout(wait):
123
- async with c:
124
- await c.wait()
125
- q = queues.get(agent_id, [])
126
- if q:
127
- job = q.pop(0); job["status"]="taken"; job["updated_at"]=now(); return {"job": job}
128
- except TimeoutError:
129
- pass
130
- return JSONResponse(status_code=204, content={"detail":"no job"})
131
-
132
- @app.get("/jobs/{job_id}")
133
- async def get_job(job_id: str):
134
- return {"job": jobs.get(job_id), "result": results.get(job_id)}
135
-
136
- @app.post("/submit-result")
137
- async def submit_result(body: SubmitResultBody):
138
- assert_agent(body.agent_id, body.token)
139
- job = jobs.get(body.job_id)
140
- if not job: raise HTTPException(status_code=404, detail="unknown job")
141
- job["status"] = body.status; job["updated_at"] = now()
142
- results[body.job_id] = {"status": body.status, "message": body.message, "artifacts": body.artifacts, "submitted_at": now()}
143
- return {"ok": True}
144
-
145
- # ===== Minimal HTML (build plan server-side; secrets live here) =====
146
- HTML = """
147
- <!doctype html><html><head><meta charset='utf-8'/><meta name='viewport' content='width=device-width,initial-scale=1'/>
148
- <title>Hybrid Agent Orchestrator</title>
149
- <style>body{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;margin:20px} .card{border:1px solid #ddd;border-radius:12px;padding:16px;margin-bottom:16px}
150
- input,select{width:100%;padding:8px;border:1px solid #ccc;border-radius:8px} button{padding:10px 14px;border-radius:10px;border:0;background:#111;color:#fff;cursor:pointer}
151
- pre{background:#f7f7f7;padding:8px;border-radius:8px;overflow:auto}
152
- </style></head><body>
153
- <div class="card"><h2>Agents</h2><div id="agents"></div><button onclick="loadAgents()">Refresh</button></div>
154
- <div class="card"><h2>Build & Send Plan (example: login to Google)</h2>
155
- <label>Agent ID</label><input id="agent_id" placeholder="paste agent_id"/>
156
- <label>Email</label><input id="email"/>
157
- <label>Password</label><input id="password" type="password"/>
158
- <label>Target URL</label><input id="url" value="https://accounts.google.com/signin"/>
159
- <button onclick="sendPlan()">Send Plan</button>
160
- <div id="out"></div>
161
- </div>
162
- <div class="card"><h2>Check Job</h2><label>Job ID</label><input id="job_id"/><button onclick="checkJob()">Check</button><div id="status"></div></div>
163
- <script>
164
- async function loadAgents(){ const r=await fetch('/agents'); const j=await r.json(); document.getElementById('agents').innerHTML='<pre>'+JSON.stringify(j,null,2)+'</pre>'; }
165
- loadAgents();
166
- function buildPlan(email,password,url){
167
- // Sensitive step building happens here in the browser for demo; in production, build on server side with a POST that returns steps
168
- return [
169
- {"act":"get","sel":url},
170
- {"act":"wait","by":"css","sel":"input[type=email]", "timeout":25},
171
- {"act":"type","by":"css","sel":"input[type=email]", "text":email},
172
- {"act":"click","by":"css","sel":"#identifierNext"},
173
- {"act":"wait","by":"css","sel":"input[type=password]", "timeout":25},
174
- {"act":"type","by":"css","sel":"input[type=password]","text":password},
175
- {"act":"click","by":"css","sel":"#passwordNext"},
176
- {"act":"wait","by":"css","sel":"body", "timeout":30},
177
- {"act":"screenshot","label":"after_login"}
178
- ];
179
- }
180
- async function sendPlan(){
181
- const agent_id = document.getElementById('agent_id').value;
182
- const email = document.getElementById('email').value;
183
- const password = document.getElementById('password').value;
184
- const url = document.getElementById('url').value;
185
- const plan = buildPlan(email,password,url);
186
- const r = await fetch('/enqueue-plan',{method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({agent_id, plan})});
187
- const j = await r.json();
188
- document.getElementById('out').innerHTML='<pre>'+JSON.stringify(j,null,2)+'</pre>';
189
- document.getElementById('job_id').value = j.job.id;
190
- }
191
- async function checkJob(){
192
- const job_id = document.getElementById('job_id').value;
193
- const r = await fetch('/jobs/'+job_id);
194
- const j = await r.json();
195
- let html = '<pre>'+JSON.stringify(j,null,2)+'</pre>';
196
- if (j.result && j.result.artifacts && j.result.artifacts.screenshots && j.result.artifacts.screenshots.after_login){
197
- html += '<img src="data:image/png;base64,'+j.result.artifacts.screenshots.after_login+'" style="max-width:100%;border:1px solid #ddd;border-radius:8px"/>';
198
- }
199
- document.getElementById('status').innerHTML = html;
200
- }
201
- </script>
202
- </body></html>
203
- """
204
- @app.get("/", response_class=HTMLResponse)
205
- async def home():
206
- return HTMLResponse(HTML)