|
|
@@ -0,0 +1,517 @@
|
|
|
+"""Legacy start/stop endpoints and authenticated read-only observation API."""
|
|
|
+
|
|
|
+import asyncio
|
|
|
+import json
|
|
|
+import re
|
|
|
+from collections.abc import Mapping
|
|
|
+from dataclasses import asdict
|
|
|
+from typing import Annotated, Any, Protocol, cast
|
|
|
+from urllib.parse import urlsplit, urlunsplit
|
|
|
+
|
|
|
+from fastapi import APIRouter, Depends, Query, Request, WebSocket, WebSocketDisconnect
|
|
|
+
|
|
|
+from script_build_host.agents.prompts import phase_one_prompt_manifest
|
|
|
+from script_build_host.application.mission_service import (
|
|
|
+ ScriptMissionService,
|
|
|
+ StartScriptBuildCommand,
|
|
|
+)
|
|
|
+from script_build_host.domain.ports import (
|
|
|
+ MissionBindingRepository,
|
|
|
+ PublicationRepository,
|
|
|
+ ScriptBusinessArtifactRepository,
|
|
|
+)
|
|
|
+from script_build_host.domain.records import Principal, PublicationType
|
|
|
+from script_build_host.infrastructure.outbound import OutboundPolicy
|
|
|
+from script_build_host.infrastructure.redaction import redact
|
|
|
+
|
|
|
+from .dependencies import ApiSecurity, problem
|
|
|
+from .schemas import (
|
|
|
+ ParseTopicJsonRequest,
|
|
|
+ ScriptBuildFromTopicJsonRequest,
|
|
|
+ ScriptBuildRequest,
|
|
|
+ ScriptBuildStartResponse,
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
+class UploadedTopicGateway(Protocol):
|
|
|
+ async def parse(self, value: dict[str, Any] | str) -> dict[str, Any]: ...
|
|
|
+
|
|
|
+ async def create(
|
|
|
+ self, parsed: dict[str, Any], *, account_name: str | None
|
|
|
+ ) -> dict[str, Any]: ...
|
|
|
+
|
|
|
+
|
|
|
+def create_script_build_router(
|
|
|
+ *,
|
|
|
+ mission_service: ScriptMissionService,
|
|
|
+ security: ApiSecurity,
|
|
|
+ bindings: MissionBindingRepository,
|
|
|
+ business_artifacts: ScriptBusinessArtifactRepository,
|
|
|
+ publications: PublicationRepository,
|
|
|
+ coordinator: Any,
|
|
|
+ trace_store: Any,
|
|
|
+ uploaded_topics: UploadedTopicGateway | None = None,
|
|
|
+ outbound_policy: OutboundPolicy | None = None,
|
|
|
+ default_model_manifest: Mapping[str, object] | None = None,
|
|
|
+ default_datasource_manifest: Mapping[str, object] | None = None,
|
|
|
+) -> APIRouter:
|
|
|
+ router = APIRouter(tags=["script-build"])
|
|
|
+
|
|
|
+ async def principal(request: Request) -> Principal:
|
|
|
+ return await security.principal(request)
|
|
|
+
|
|
|
+ PrincipalDependency = Annotated[Principal, Depends(principal)]
|
|
|
+
|
|
|
+ async def scope(script_build_id: int, actor: Principal) -> Any:
|
|
|
+ await security.authorize(script_build_id, actor)
|
|
|
+ return await bindings.get_by_build(script_build_id)
|
|
|
+
|
|
|
+ @router.post("/api/pattern/script_builds", response_model=ScriptBuildStartResponse)
|
|
|
+ async def start_script_build(
|
|
|
+ body: ScriptBuildRequest,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> ScriptBuildStartResponse:
|
|
|
+ if body.agent_type != "AigcAgent":
|
|
|
+ raise problem(400, "UNSUPPORTED_AGENT_TYPE", "script build supports AigcAgent only")
|
|
|
+ await security.authorize_source(
|
|
|
+ actor,
|
|
|
+ execution_id=body.execution_id,
|
|
|
+ topic_build_id=body.topic_build_id,
|
|
|
+ topic_id=body.topic_id,
|
|
|
+ )
|
|
|
+ command = await _start_command(
|
|
|
+ body,
|
|
|
+ actor,
|
|
|
+ outbound_policy,
|
|
|
+ default_model_manifest=default_model_manifest,
|
|
|
+ default_datasource_manifest=default_datasource_manifest,
|
|
|
+ )
|
|
|
+ result = await mission_service.start(command)
|
|
|
+ return ScriptBuildStartResponse(
|
|
|
+ script_build_id=result.script_build_id,
|
|
|
+ status=result.status.value,
|
|
|
+ root_trace_id=result.root_trace_id,
|
|
|
+ )
|
|
|
+
|
|
|
+ @router.post("/api/pattern/script_builds/parse_topic_json")
|
|
|
+ async def parse_topic_json(
|
|
|
+ body: ParseTopicJsonRequest,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ if uploaded_topics is None:
|
|
|
+ raise problem(
|
|
|
+ 503, "UPLOAD_TOPIC_NOT_CONFIGURED", "uploaded topic gateway is unavailable"
|
|
|
+ )
|
|
|
+ await security.authorize_source(
|
|
|
+ actor,
|
|
|
+ execution_id=0,
|
|
|
+ topic_build_id=0,
|
|
|
+ topic_id=0,
|
|
|
+ )
|
|
|
+ parsed = await uploaded_topics.parse(body.json_data)
|
|
|
+ points = list(parsed.get("points", []))
|
|
|
+ stats: dict[str, int] = {}
|
|
|
+ for point in points:
|
|
|
+ key = str(point.get("point_type", ""))
|
|
|
+ stats[key] = stats.get(key, 0) + 1
|
|
|
+ return {
|
|
|
+ "success": True,
|
|
|
+ "account_name": parsed.get("account_name"),
|
|
|
+ "topic_fusion": parsed.get("topic_fusion"),
|
|
|
+ "point_count": len(points),
|
|
|
+ "item_count": parsed.get("item_count", 0),
|
|
|
+ "point_type_stats": stats,
|
|
|
+ "points": points,
|
|
|
+ }
|
|
|
+
|
|
|
+ @router.post("/api/pattern/script_builds/from_topic_json")
|
|
|
+ async def start_from_topic_json(
|
|
|
+ body: ScriptBuildFromTopicJsonRequest,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ if uploaded_topics is None:
|
|
|
+ raise problem(
|
|
|
+ 503, "UPLOAD_TOPIC_NOT_CONFIGURED", "uploaded topic gateway is unavailable"
|
|
|
+ )
|
|
|
+ await security.authorize_source(
|
|
|
+ actor,
|
|
|
+ execution_id=0,
|
|
|
+ topic_build_id=0,
|
|
|
+ topic_id=0,
|
|
|
+ )
|
|
|
+ parsed = await uploaded_topics.parse(body.json_data)
|
|
|
+ created = await uploaded_topics.create(parsed, account_name=body.account_name)
|
|
|
+ request = ScriptBuildRequest(
|
|
|
+ execution_id=0,
|
|
|
+ topic_build_id=int(created["topic_build_id"]),
|
|
|
+ topic_id=int(created["topic_id"]),
|
|
|
+ agent_config=body.agent_config,
|
|
|
+ data_source_url=body.data_source_url,
|
|
|
+ strategies_always_on=body.strategies_always_on,
|
|
|
+ strategies_on_demand=body.strategies_on_demand,
|
|
|
+ )
|
|
|
+ await security.authorize_source(
|
|
|
+ actor,
|
|
|
+ execution_id=request.execution_id,
|
|
|
+ topic_build_id=request.topic_build_id,
|
|
|
+ topic_id=request.topic_id,
|
|
|
+ )
|
|
|
+ result = await mission_service.start(
|
|
|
+ await _start_command(
|
|
|
+ request,
|
|
|
+ actor,
|
|
|
+ outbound_policy,
|
|
|
+ default_model_manifest=default_model_manifest,
|
|
|
+ default_datasource_manifest=default_datasource_manifest,
|
|
|
+ )
|
|
|
+ )
|
|
|
+ return {
|
|
|
+ "success": True,
|
|
|
+ "message": "脚本构建任务已提交",
|
|
|
+ "script_build_id": result.script_build_id,
|
|
|
+ "topic_id": created["topic_id"],
|
|
|
+ "topic_build_id": created["topic_build_id"],
|
|
|
+ "account_name": created.get("account_name"),
|
|
|
+ "item_count": created.get("item_count", 0),
|
|
|
+ "point_count": created.get("point_count", 0),
|
|
|
+ "status": result.status.value,
|
|
|
+ "root_trace_id": result.root_trace_id,
|
|
|
+ }
|
|
|
+
|
|
|
+ @router.post("/api/pattern/script_builds/{script_build_id}/stop")
|
|
|
+ async def stop_script_build(
|
|
|
+ script_build_id: int,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ result = await mission_service.stop(script_build_id, actor)
|
|
|
+ return {"success": True, **asdict(result), "status": result.status.value}
|
|
|
+
|
|
|
+ @router.get("/api/pattern/script_builds/{script_build_id}/mission")
|
|
|
+ async def mission_snapshot(
|
|
|
+ script_build_id: int,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ binding = await scope(script_build_id, actor)
|
|
|
+ ledger = await coordinator.task_store.load(binding.root_trace_id)
|
|
|
+ return cast(dict[str, Any], ledger.to_dict())
|
|
|
+
|
|
|
+ @router.get("/api/pattern/script_builds/{script_build_id}/mission/events")
|
|
|
+ async def mission_events(
|
|
|
+ script_build_id: int,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ after: str | None = None,
|
|
|
+ limit: int = Query(100, ge=1, le=500),
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ binding = await scope(script_build_id, actor)
|
|
|
+ page = await coordinator.task_store.list_events(
|
|
|
+ binding.root_trace_id, cursor=after, limit=limit
|
|
|
+ )
|
|
|
+ return {
|
|
|
+ "events": [item.to_dict() for item in page.events],
|
|
|
+ "next_cursor": page.next_cursor,
|
|
|
+ }
|
|
|
+
|
|
|
+ @router.get("/api/pattern/script_builds/{script_build_id}/tasks/{task_id}")
|
|
|
+ async def task_detail(
|
|
|
+ script_build_id: int,
|
|
|
+ task_id: str,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ binding = await scope(script_build_id, actor)
|
|
|
+ ledger = await coordinator.task_store.load(binding.root_trace_id)
|
|
|
+ task = ledger.tasks.get(task_id)
|
|
|
+ if task is None:
|
|
|
+ raise problem(404, "TASK_NOT_FOUND", "task does not exist in this build")
|
|
|
+ return task.to_dict() if hasattr(task, "to_dict") else asdict(task)
|
|
|
+
|
|
|
+ @router.get("/api/pattern/script_builds/{script_build_id}/artifacts/{artifact_version_id}")
|
|
|
+ async def artifact_detail(
|
|
|
+ script_build_id: int,
|
|
|
+ artifact_version_id: int,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ await scope(script_build_id, actor)
|
|
|
+ value = await business_artifacts.get_by_id(
|
|
|
+ artifact_version_id, script_build_id=script_build_id
|
|
|
+ )
|
|
|
+ return _json_values(asdict(value))
|
|
|
+
|
|
|
+ @router.get("/api/pattern/script_builds/{script_build_id}/publication")
|
|
|
+ async def publication_detail(
|
|
|
+ script_build_id: int,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ await scope(script_build_id, actor)
|
|
|
+ value = await publications.get_by_build(
|
|
|
+ script_build_id, publication_type=PublicationType.DIRECTION
|
|
|
+ )
|
|
|
+ if value is None:
|
|
|
+ raise problem(404, "PUBLICATION_NOT_FOUND", "publication does not exist")
|
|
|
+ return _json_values(asdict(value))
|
|
|
+
|
|
|
+ # Authenticated, build-scoped subset of the framework V2 observation API.
|
|
|
+ @router.get("/api/v2/script-builds/{script_build_id}/roots/{root_trace_id}/snapshot")
|
|
|
+ async def v2_snapshot(
|
|
|
+ script_build_id: int,
|
|
|
+ root_trace_id: str,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ binding = await scope(script_build_id, actor)
|
|
|
+ _same_root(binding.root_trace_id, root_trace_id)
|
|
|
+ return cast(dict[str, Any], (await coordinator.task_store.load(root_trace_id)).to_dict())
|
|
|
+
|
|
|
+ @router.get("/api/v2/script-builds/{script_build_id}/roots/{root_trace_id}/completion")
|
|
|
+ async def v2_completion(
|
|
|
+ script_build_id: int,
|
|
|
+ root_trace_id: str,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ binding = await scope(script_build_id, actor)
|
|
|
+ _same_root(binding.root_trace_id, root_trace_id)
|
|
|
+ return cast(dict[str, Any], await coordinator.root_completion(root_trace_id))
|
|
|
+
|
|
|
+ @router.get("/api/v2/script-builds/{script_build_id}/roots/{root_trace_id}/events")
|
|
|
+ async def v2_events(
|
|
|
+ script_build_id: int,
|
|
|
+ root_trace_id: str,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ after: str | None = None,
|
|
|
+ limit: int = Query(100, ge=1, le=500),
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ binding = await scope(script_build_id, actor)
|
|
|
+ _same_root(binding.root_trace_id, root_trace_id)
|
|
|
+ page = await coordinator.task_store.list_events(
|
|
|
+ root_trace_id,
|
|
|
+ cursor=after,
|
|
|
+ limit=limit,
|
|
|
+ )
|
|
|
+ return {
|
|
|
+ "events": [item.to_dict() for item in page.events],
|
|
|
+ "next_cursor": page.next_cursor,
|
|
|
+ }
|
|
|
+
|
|
|
+ @router.get("/api/pattern/script_builds/{script_build_id}/traces")
|
|
|
+ async def list_traces(
|
|
|
+ script_build_id: int,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ binding = await scope(script_build_id, actor)
|
|
|
+ traces = await trace_store.list_traces(root_trace_id=binding.root_trace_id, limit=100)
|
|
|
+ root = await trace_store.get_trace(binding.root_trace_id)
|
|
|
+ values = ([root] if root is not None else []) + list(traces)
|
|
|
+ deduplicated = {item.trace_id: item.to_dict() for item in values}
|
|
|
+ return {"traces": list(deduplicated.values())}
|
|
|
+
|
|
|
+ @router.get("/api/pattern/script_builds/{script_build_id}/traces/{trace_id}")
|
|
|
+ async def trace_detail(
|
|
|
+ script_build_id: int,
|
|
|
+ trace_id: str,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ binding = await scope(script_build_id, actor)
|
|
|
+ trace = await _owned_trace(trace_store, binding.root_trace_id, trace_id)
|
|
|
+ tree = await trace_store.get_goal_tree(trace_id)
|
|
|
+ return {
|
|
|
+ "trace": trace.to_dict(),
|
|
|
+ "goal_tree": tree.to_dict() if tree else None,
|
|
|
+ }
|
|
|
+
|
|
|
+ @router.get("/api/pattern/script_builds/{script_build_id}/traces/{trace_id}/messages")
|
|
|
+ async def trace_messages(
|
|
|
+ script_build_id: int,
|
|
|
+ trace_id: str,
|
|
|
+ actor: PrincipalDependency,
|
|
|
+ ) -> dict[str, Any]:
|
|
|
+ binding = await scope(script_build_id, actor)
|
|
|
+ await _owned_trace(trace_store, binding.root_trace_id, trace_id)
|
|
|
+ messages = await trace_store.get_trace_messages(trace_id)
|
|
|
+ return {"messages": [item.to_dict() for item in messages]}
|
|
|
+
|
|
|
+ @router.websocket("/api/pattern/script_builds/{script_build_id}/traces/{trace_id}/watch")
|
|
|
+ async def watch_trace(
|
|
|
+ websocket: WebSocket,
|
|
|
+ script_build_id: int,
|
|
|
+ trace_id: str,
|
|
|
+ ) -> None:
|
|
|
+ try:
|
|
|
+ security.require_websocket_origin(websocket)
|
|
|
+ actor = await security.principal(websocket)
|
|
|
+ binding = await scope(script_build_id, actor)
|
|
|
+ await _owned_trace(trace_store, binding.root_trace_id, trace_id)
|
|
|
+ except Exception:
|
|
|
+ await websocket.close(code=4404)
|
|
|
+ return
|
|
|
+ await websocket.accept()
|
|
|
+ since = 0
|
|
|
+ try:
|
|
|
+ while True:
|
|
|
+ events = await trace_store.get_events(trace_id, since)
|
|
|
+ for event in events:
|
|
|
+ await websocket.send_json(event)
|
|
|
+ if isinstance(event.get("event_id"), int):
|
|
|
+ since = max(since, event["event_id"])
|
|
|
+ try:
|
|
|
+ value = await asyncio.wait_for(
|
|
|
+ websocket.receive_text(),
|
|
|
+ timeout=0.5,
|
|
|
+ )
|
|
|
+ if value == "ping":
|
|
|
+ await websocket.send_json({"event": "pong"})
|
|
|
+ except TimeoutError:
|
|
|
+ continue
|
|
|
+ except WebSocketDisconnect:
|
|
|
+ return
|
|
|
+ except WebSocketDisconnect:
|
|
|
+ return
|
|
|
+
|
|
|
+ return router
|
|
|
+
|
|
|
+
|
|
|
+async def _start_command(
|
|
|
+ body: ScriptBuildRequest,
|
|
|
+ actor: Principal,
|
|
|
+ outbound_policy: OutboundPolicy | None,
|
|
|
+ *,
|
|
|
+ default_model_manifest: Mapping[str, object] | None,
|
|
|
+ default_datasource_manifest: Mapping[str, object] | None,
|
|
|
+) -> StartScriptBuildCommand:
|
|
|
+ endpoint = body.data_source_url
|
|
|
+ sanitized_datasources = redact(dict(default_datasource_manifest or {}))
|
|
|
+ if not isinstance(sanitized_datasources, dict):
|
|
|
+ raise problem(500, "INVALID_HOST_CONFIG", "datasource manifest must be an object")
|
|
|
+ datasource_manifest: dict[str, Any] = sanitized_datasources
|
|
|
+ safe_endpoint: str | None = None
|
|
|
+ if endpoint:
|
|
|
+ parsed = urlsplit(endpoint)
|
|
|
+ if (
|
|
|
+ parsed.scheme != "https"
|
|
|
+ or not parsed.hostname
|
|
|
+ or parsed.username
|
|
|
+ or parsed.password
|
|
|
+ or parsed.query
|
|
|
+ or parsed.fragment
|
|
|
+ ):
|
|
|
+ raise problem(
|
|
|
+ 400, "UNSAFE_DATA_SOURCE_URL", "data_source_url must be credential-free HTTPS"
|
|
|
+ )
|
|
|
+ if outbound_policy is None:
|
|
|
+ raise problem(
|
|
|
+ 503,
|
|
|
+ "OUTBOUND_POLICY_NOT_CONFIGURED",
|
|
|
+ "data_source_url requires an outbound policy",
|
|
|
+ )
|
|
|
+ validated = await outbound_policy.validate_url(endpoint)
|
|
|
+ safe_endpoint = urlunsplit(urlsplit(validated)._replace(query="", fragment=""))
|
|
|
+ datasource_manifest = {
|
|
|
+ **datasource_manifest,
|
|
|
+ "data_source_host": parsed.hostname.rstrip(".").lower(),
|
|
|
+ "scheme": "https",
|
|
|
+ }
|
|
|
+ raw_config = dict(body.agent_config or {})
|
|
|
+ sanitized_config = redact(raw_config)
|
|
|
+ if not isinstance(sanitized_config, dict):
|
|
|
+ raise problem(400, "INVALID_AGENT_CONFIG", "agent_config must be an object")
|
|
|
+ if isinstance(raw_config.get("temperature"), bool) or isinstance(
|
|
|
+ raw_config.get("max_iterations"), bool
|
|
|
+ ):
|
|
|
+ raise problem(400, "INVALID_AGENT_CONFIG", "model run configuration is invalid")
|
|
|
+ default_presets = (default_model_manifest or {}).get(
|
|
|
+ "presets",
|
|
|
+ default_model_manifest or {},
|
|
|
+ )
|
|
|
+ planner_default = (
|
|
|
+ default_presets.get("script_planner", {}) if isinstance(default_presets, Mapping) else {}
|
|
|
+ )
|
|
|
+ if not isinstance(planner_default, Mapping):
|
|
|
+ planner_default = {}
|
|
|
+ try:
|
|
|
+ model = str(raw_config.get("model_name", planner_default.get("model", "gpt-4o")))
|
|
|
+ temperature = float(raw_config.get("temperature", 0.3))
|
|
|
+ max_iterations = int(raw_config.get("max_iterations", 120))
|
|
|
+ presets: dict[str, dict[str, object]] = {}
|
|
|
+ preset_keys = {
|
|
|
+ "script_planner": "model_name",
|
|
|
+ "script_direction_worker": "model_name",
|
|
|
+ "script_pattern_retrieval_worker": "model_name_means_pattern_relation",
|
|
|
+ "script_decode_retrieval_worker": "model_name_means_case",
|
|
|
+ "script_external_retrieval_worker": "model_name_means_data",
|
|
|
+ "script_knowledge_retrieval_worker": "model_name_means_knowledge",
|
|
|
+ "script_retrieval_validator": "model_name_script_evaluator",
|
|
|
+ "script_candidate_validator": "model_name_script_evaluator",
|
|
|
+ }
|
|
|
+ for name, key in preset_keys.items():
|
|
|
+ preset_default = (
|
|
|
+ default_presets.get(name, {}) if isinstance(default_presets, Mapping) else {}
|
|
|
+ )
|
|
|
+ if not isinstance(preset_default, Mapping):
|
|
|
+ preset_default = {}
|
|
|
+ presets[name] = {
|
|
|
+ "model": str(raw_config.get(key, preset_default.get("model", model))),
|
|
|
+ "temperature": float(
|
|
|
+ cast(
|
|
|
+ Any,
|
|
|
+ raw_config.get(
|
|
|
+ "temperature",
|
|
|
+ preset_default.get(
|
|
|
+ "temperature",
|
|
|
+ 0.0 if "validator" in name else temperature,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ "max_iterations": int(
|
|
|
+ cast(
|
|
|
+ Any,
|
|
|
+ raw_config.get(
|
|
|
+ "max_iterations",
|
|
|
+ preset_default.get("max_iterations", max_iterations),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ }
|
|
|
+ except (TypeError, ValueError, OverflowError) as exc:
|
|
|
+ raise problem(400, "INVALID_AGENT_CONFIG", "model run configuration is invalid") from exc
|
|
|
+ model_pattern = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$")
|
|
|
+ if any(
|
|
|
+ not model_pattern.fullmatch(str(value["model"]))
|
|
|
+ or not 0 <= float(cast(Any, value["temperature"])) <= 2
|
|
|
+ or not 1 <= int(cast(Any, value["max_iterations"])) <= 1000
|
|
|
+ for value in presets.values()
|
|
|
+ ):
|
|
|
+ raise problem(400, "INVALID_AGENT_CONFIG", "model names contain unsafe characters")
|
|
|
+ return StartScriptBuildCommand(
|
|
|
+ execution_id=body.execution_id,
|
|
|
+ topic_build_id=body.topic_build_id,
|
|
|
+ topic_id=body.topic_id,
|
|
|
+ principal=actor,
|
|
|
+ agent_type=body.agent_type,
|
|
|
+ agent_config=sanitized_config,
|
|
|
+ data_source_url=safe_endpoint,
|
|
|
+ strategies_always_on=tuple(body.strategies_always_on or ()),
|
|
|
+ strategies_on_demand=tuple(body.strategies_on_demand or ()),
|
|
|
+ runtime_prompt_manifest=phase_one_prompt_manifest(),
|
|
|
+ datasource_manifest=datasource_manifest,
|
|
|
+ model_manifest={"presets": presets},
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def _same_root(expected: str, supplied: str) -> None:
|
|
|
+ if expected != supplied:
|
|
|
+ raise problem(404, "ROOT_NOT_FOUND", "root does not exist in this build")
|
|
|
+
|
|
|
+
|
|
|
+async def _owned_trace(trace_store: Any, root_trace_id: str, trace_id: str) -> Any:
|
|
|
+ trace = await trace_store.get_trace(trace_id)
|
|
|
+ if trace is None:
|
|
|
+ raise problem(404, "TRACE_NOT_FOUND", "trace does not exist in this build")
|
|
|
+ trace_root = (trace.context or {}).get("root_trace_id")
|
|
|
+ if trace_id != root_trace_id and trace_root != root_trace_id:
|
|
|
+ raise problem(404, "TRACE_NOT_FOUND", "trace does not exist in this build")
|
|
|
+ return trace
|
|
|
+
|
|
|
+
|
|
|
+def _json_values(value: dict[str, Any]) -> dict[str, Any]:
|
|
|
+ return cast(dict[str, Any], json.loads(json.dumps(value, ensure_ascii=False, default=str)))
|
|
|
+
|
|
|
+
|
|
|
+__all__ = ["UploadedTopicGateway", "create_script_build_router"]
|