--- name: hoppscotch_run_request kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "def hoppscotch_run_request(method: str, url: str, *, title: str | None = None, headers: dict | None = None, body: str | None = None, body_type: str | None = None, variables: dict | None = None, access_token: str, backend_url: str = \"http://localhost:3170\", record_history: bool = True, timeout_s: float = 30.0, verify_tls: bool = True) -> dict" description: "Ejecuta una peticion HTTP real (resolviendo placeholders <>/{{var}} con un dict de variables) y la registra en el UserHistory de un Hoppscotch self-hosted via la mutation GraphQL createUserHistory, para que el humano la vea aparecer en vivo en la pestana History de su GUI (subscription userHistoryCreated). La request se ejecuta con las variables resueltas, pero en el History se guarda SIN resolver (con los literales <>) igual que en el editor. resMetadata minimo: statusCode + duration. El access_token va como cookie, no como header Authorization." tags: [hoppscotch, flow-replay, http] uses_functions: [build_hoppscotch_collection_py_infra] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [json, re, requests] params: - name: method desc: "metodo HTTP de la peticion (GET, POST, ...)." - name: url desc: "endpoint de la peticion. Puede contener placeholders <> o {{var}} que se resuelven con `variables` antes de ejecutar." - name: title desc: "nombre visible de la request en el History. None = derivar de method + path via build_hoppscotch_collection." - name: headers desc: "dict name->value de cabeceras. Sus values tambien admiten placeholders <>/{{var}}." - name: body desc: "cuerpo de la peticion como texto ya serializado. Admite placeholders. None = sin cuerpo." - name: body_type desc: "tipo de cuerpo para el HoppRESTRequest del History: 'json' | 'form' | 'raw' | None." - name: variables desc: "dict name->value para resolver los placeholders al EJECUTAR. Una variable que falte deja el literal intacto. None = no se resuelve nada." - name: access_token desc: "JWT de sesion (de hoppscotch_login). Viaja en la cookie access_token, NO en el header Authorization. Necesario para registrar en el History." - name: backend_url desc: "base del backend Hoppscotch self-host sin barra final. La mutation cuelga de {backend_url}/graphql. Default http://localhost:3170." - name: record_history desc: "si True y hay access_token, registra la request ejecutada en el UserHistory via createUserHistory. Default True." - name: timeout_s desc: "timeout en segundos de la peticion HTTP ejecutada (y del POST de History). Default 30.0." - name: verify_tls desc: "verificacion del certificado TLS de la peticion ejecutada. Default True." output: "dict. En exito de la ejecucion HTTP: {status: 'ok', status_code: int, duration_ms: int, response_body: str (truncado a 5000 chars), response_headers: dict, recorded: bool, history_id: str|None}. Si la ejecucion fue ok pero el registro de History fallo, status sigue 'ok', recorded False y se anade history_error. Si la ejecucion HTTP falla (RequestException): {status: 'error', error: str, recorded: False}. Nunca lanza por errores de red esperables." tested: true tests: - "test_ejecuta_resolviendo_variables_angle" - "test_ejecuta_resolviendo_variables_brace" - "test_record_history_registra_request_sin_resolver" - "test_record_history_false_no_llama_create_user_history" - "test_request_exception_status_error" - "test_variable_faltante_conserva_literal" test_file_path: "python/functions/infra/hoppscotch_run_request_test.py" file_path: "python/functions/infra/hoppscotch_run_request.py" --- ## Ejemplo ```python import sys sys.path.insert(0, "python/functions") from infra.hoppscotch_login import hoppscotch_login from infra.hoppscotch_run_request import hoppscotch_run_request # 1) Obtener un JWT de sesion (headless, lee el correo de Mailpit). login = hoppscotch_login("admin@example.com") assert login["status"] == "ok", login["error"] token = login["access_token"] # 2) Ejecutar una request con una variable y dejar rastro en el History de la GUI. result = hoppscotch_run_request( "GET", "<>/api/status", title="Status", variables={"baseURL": "https://registry.organic-machine.com"}, access_token=token, ) print(result["status_code"], result["recorded"], result["history_id"]) # 200 True hist-... # -> aparece en vivo en la pestana History del Hoppscotch self-host. ``` ## Cuando usarla Cuando el agente ejecuta una consulta HTTP y quiere que el humano la vea en el History de su GUI Hoppscotch self-hosted, en vivo. La entry aparece via la subscription `userHistoryCreated` sin que el humano refresque. Util para hacer auditable/observable lo que el agente prueba: cada `hoppscotch_run_request` deja en la pestana History la request (con sus variables sin resolver) y su statusCode + duracion. Encadena con `hoppscotch_login` para obtener el `access_token`. ## Gotchas - **El access_token va como cookie, no como header Authorization.** La mutation `createUserHistory` lee el JWT de la cookie `access_token`. Se manda con `cookies={"access_token": ...}`. Si expira (~24h), re-loguea con `hoppscotch_login`. - **reqData lleva la request SIN resolver.** Lo que se guarda en el History es el HoppRESTRequest con los placeholders `<>`/`{{var}}` literales, igual que en el editor de la GUI, para que el humano vea la plantilla con sus variables y no los valores expandidos. La peticion SI se ejecuta con las variables resueltas. - **Soporta `<<>>` y `{{}}`.** Hoppscotch usa `<>`; muchas plantillas traen `{{var}}`. Ambas sintaxis se resuelven al ejecutar. Una variable que falte en `variables` deja el literal intacto (no rompe). - **resMetadata minimo: statusCode + duration.** Se envia `{"statusCode": ..., "duration": ...}`. Si una version del backend exigiera mas campos, el registro fallaria con `history_error` (la ejecucion HTTP sigue siendo ok). Ajustar el shape si el self-host lo pide. - **El body de respuesta se trunca a 5000 chars** en `response_body` del output, para no devolver payloads enormes. Los `response_headers` van completos. - **duration_ms viene de `resp.elapsed`,** no de `time.time()`: es la latencia que midio `requests` para la peticion ejecutada. - **Degradacion suave del History:** si la ejecucion HTTP fue ok pero el POST de la mutation falla (transporte, no-JSON, errores GraphQL, sin id), `status` sigue "ok", `recorded` es False y se anade `history_error` con el detalle. ## Capability growth log v1.0.0 — version inicial. Ejecucion + registro en UserHistory del self-host; resolucion de placeholders `<<>>`/`{{}}`.