deploy
This commit is contained in:
136
agent.py
136
agent.py
@@ -11,6 +11,7 @@ import asyncio
|
||||
import hashlib
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, field
|
||||
@@ -22,7 +23,7 @@ from a2a_pack import A2AAgent, LLMProvisioning, NoAuth, Pricing, RunContext, ski
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
DEFAULT_OUTPUT_DIR = Path("outputs/browser-to-api")
|
||||
DEFAULT_OUTPUT_DIR = Path(os.environ.get("BROWSER_TO_API_OUTPUT_DIR", "/tmp/browser-to-api"))
|
||||
JSONISH_TYPES = (
|
||||
"application/json",
|
||||
"application/problem+json",
|
||||
@@ -166,44 +167,32 @@ class BrowserToApiAgent(A2AAgent[BrowserToApiConfig, NoAuth]):
|
||||
if not url or not str(url).strip().startswith(("http://", "https://")):
|
||||
return {"error": "url must start with http:// or https://"}
|
||||
|
||||
await ctx.emit_progress("launching browser and capturing network traffic")
|
||||
samples, capture_log = await asyncio.to_thread(
|
||||
capture_browser_traffic,
|
||||
url=str(url).strip(),
|
||||
wait_seconds=max(1, min(int(wait_seconds), 60)),
|
||||
max_clicks=max(0, min(int(max_clicks), 20)),
|
||||
search_term=search_term,
|
||||
)
|
||||
try:
|
||||
await ctx.emit_progress("launching browser and capturing network traffic")
|
||||
samples, capture_log = await asyncio.to_thread(
|
||||
capture_browser_traffic,
|
||||
url=str(url).strip(),
|
||||
wait_seconds=max(1, min(int(wait_seconds), 60)),
|
||||
max_clicks=max(0, min(int(max_clicks), 20)),
|
||||
search_term=search_term,
|
||||
)
|
||||
|
||||
await ctx.emit_progress(f"captured {len(samples)} network samples; inferring API surface")
|
||||
bundle = build_openapi_bundle(
|
||||
samples,
|
||||
title=title or _title_from_url(url),
|
||||
origins=origins,
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
min_samples=max(1, int(min_samples)),
|
||||
redact=redact,
|
||||
)
|
||||
output_dir = write_bundle(bundle, DEFAULT_OUTPUT_DIR / safe_run_id(url))
|
||||
await ctx.emit_progress(f"captured {len(samples)} network samples; inferring API surface")
|
||||
bundle = build_openapi_bundle(
|
||||
samples,
|
||||
title=title or _title_from_url(url),
|
||||
origins=origins,
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
min_samples=max(1, int(min_samples)),
|
||||
redact=redact,
|
||||
)
|
||||
output_dir = write_bundle(bundle, DEFAULT_OUTPUT_DIR / safe_run_id(url))
|
||||
|
||||
await ctx.emit_progress(f"wrote OpenAPI bundle to {output_dir}")
|
||||
return {
|
||||
"url": url,
|
||||
"captured_samples": len(samples),
|
||||
"included_samples": bundle["summary"]["included_samples"],
|
||||
"endpoints": bundle["summary"]["endpoints"],
|
||||
"origins": bundle["summary"]["origins"],
|
||||
"output_dir": str(output_dir),
|
||||
"openapi_json": str(output_dir / "openapi.json"),
|
||||
"openapi_yaml": str(output_dir / "openapi.yaml"),
|
||||
"html_report": str(output_dir / "index.html"),
|
||||
"markdown_report": str(output_dir / "report.md"),
|
||||
"client": str(output_dir / "client.mjs"),
|
||||
"confidence": str(output_dir / "confidence.json"),
|
||||
"capture_log": capture_log,
|
||||
"top_endpoints": bundle["summary"]["top_endpoints"],
|
||||
}
|
||||
await ctx.emit_progress(f"wrote OpenAPI bundle to {output_dir}")
|
||||
return _result_payload(url=url, samples=samples, bundle=bundle, output_dir=output_dir, capture_log=capture_log)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return {"error": "browser_to_api_failed", "detail": f"{type(exc).__name__}: {exc}"}
|
||||
|
||||
@skill(
|
||||
name="discover_api_from_trace",
|
||||
@@ -226,33 +215,23 @@ class BrowserToApiAgent(A2AAgent[BrowserToApiConfig, NoAuth]):
|
||||
min_samples: int = 1,
|
||||
redact: list[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
await ctx.emit_progress("pairing replayed request/response trace")
|
||||
samples = parse_trace_jsonl(requests_jsonl=requests_jsonl, responses_jsonl=responses_jsonl)
|
||||
bundle = build_openapi_bundle(
|
||||
samples,
|
||||
title=title,
|
||||
origins=origins,
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
min_samples=max(1, int(min_samples)),
|
||||
redact=redact,
|
||||
)
|
||||
output_dir = write_bundle(bundle, DEFAULT_OUTPUT_DIR / safe_run_id(title))
|
||||
await ctx.emit_progress(f"wrote replayed OpenAPI bundle to {output_dir}")
|
||||
return {
|
||||
"captured_samples": len(samples),
|
||||
"included_samples": bundle["summary"]["included_samples"],
|
||||
"endpoints": bundle["summary"]["endpoints"],
|
||||
"origins": bundle["summary"]["origins"],
|
||||
"output_dir": str(output_dir),
|
||||
"openapi_json": str(output_dir / "openapi.json"),
|
||||
"openapi_yaml": str(output_dir / "openapi.yaml"),
|
||||
"html_report": str(output_dir / "index.html"),
|
||||
"markdown_report": str(output_dir / "report.md"),
|
||||
"client": str(output_dir / "client.mjs"),
|
||||
"confidence": str(output_dir / "confidence.json"),
|
||||
"top_endpoints": bundle["summary"]["top_endpoints"],
|
||||
}
|
||||
try:
|
||||
await ctx.emit_progress("pairing replayed request/response trace")
|
||||
samples = parse_trace_jsonl(requests_jsonl=requests_jsonl, responses_jsonl=responses_jsonl)
|
||||
bundle = build_openapi_bundle(
|
||||
samples,
|
||||
title=title,
|
||||
origins=origins,
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
min_samples=max(1, int(min_samples)),
|
||||
redact=redact,
|
||||
)
|
||||
output_dir = write_bundle(bundle, DEFAULT_OUTPUT_DIR / safe_run_id(title))
|
||||
await ctx.emit_progress(f"wrote replayed OpenAPI bundle to {output_dir}")
|
||||
return _result_payload(samples=samples, bundle=bundle, output_dir=output_dir)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return {"error": "browser_to_api_failed", "detail": f"{type(exc).__name__}: {exc}"}
|
||||
|
||||
|
||||
def capture_browser_traffic(
|
||||
@@ -842,6 +821,35 @@ def write_bundle(bundle: dict[str, Any], output_dir: Path) -> Path:
|
||||
return output_dir
|
||||
|
||||
|
||||
def _result_payload(
|
||||
*,
|
||||
samples: list[TrafficSample],
|
||||
bundle: dict[str, Any],
|
||||
output_dir: Path,
|
||||
url: str | None = None,
|
||||
capture_log: list[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
payload: dict[str, Any] = {
|
||||
"captured_samples": len(samples),
|
||||
"included_samples": bundle["summary"]["included_samples"],
|
||||
"endpoints": bundle["summary"]["endpoints"],
|
||||
"origins": bundle["summary"]["origins"],
|
||||
"output_dir": str(output_dir),
|
||||
"openapi_json": str(output_dir / "openapi.json"),
|
||||
"openapi_yaml": str(output_dir / "openapi.yaml"),
|
||||
"html_report": str(output_dir / "index.html"),
|
||||
"markdown_report": str(output_dir / "report.md"),
|
||||
"client": str(output_dir / "client.mjs"),
|
||||
"confidence": str(output_dir / "confidence.json"),
|
||||
"top_endpoints": bundle["summary"]["top_endpoints"],
|
||||
}
|
||||
if url is not None:
|
||||
payload["url"] = url
|
||||
if capture_log is not None:
|
||||
payload["capture_log"] = capture_log
|
||||
return payload
|
||||
|
||||
|
||||
def infer_schema(values: list[Any]) -> dict[str, Any]:
|
||||
values = [value for value in values if value is not None]
|
||||
if not values:
|
||||
|
||||
Reference in New Issue
Block a user