Task: gitlab.list-issues
Goal: Display the list of issues in the kkroening/ffmpeg-python repository that have labels related to questions
Task configuration:
label: questions
namespace: kkroening
project_name: ffmpeg-python
query: label_name[]=question

Validation code:
import re
from typing import Any, Dict, List
from urllib import parse
import os
import json
import requests

def _get_headers():
    return {
        "Content-Type": "application/json",
        "Accept": "application/json",
        **json.loads(os.getenv("GITLAB_EXTRA_HTTP_HEADERS", "{}")),
    }

def _validate_via_url(content, config):
    """Original URL-based validation for browser agents."""
    all_urls = re.findall(r"https?://\S+", content)
    if not all_urls:
        return None  # No URL found, can't validate this way

    expected_page_prefix = (
        os.getenv("GITLAB_BASE_URL", "").rstrip("/")
        + "/"
        + config["namespace"]
        + "/"
        + config["project_name"]
        + "/-/issues"
    )

    for url in all_urls:
        page_url = parse.unquote(url)
        if not page_url.startswith(expected_page_prefix):
            continue
        page_query = parse.urlparse(page_url).query
        if all(part in page_query for part in config["query"].split("&")):
            return 1

    return None

def _validate_via_api(content, config):
    """API-based validation for terminal agents - check if the agent listed relevant issues."""
    base_url = os.getenv("GITLAB_BASE_URL", "").rstrip("/")
    project_id = parse.quote(f"{config['namespace']}/{config['project_name']}", safe="")

    # Build API URL from query params
    api_url = base_url + "/api/v4/projects/" + project_id + "/issues"
    query_params = parse.parse_qs(config["query"])

    # Map URL query params to API params
    params = {"per_page": 20, "state": "opened"}
    for key, values in query_params.items():
        if key == "label_name[]":
            params["labels"] = ",".join(values)
        elif key == "sort":
            params["order_by"] = values[0].replace("created_date", "created_at")
        elif key == "state":
            params["state"] = values[0]
        elif key == "search":
            params["search"] = values[0]

    response = requests.get(url=api_url, headers=_get_headers(), params=params)
    if response.status_code != 200:
        return 0

    issues = response.json()
    if not issues:
        return 0

    # Check if the agent's message mentions at least one issue title
    content_lower = content.lower()
    for issue in issues:
        if issue["title"].lower() in content_lower:
            return 1

    # Also check for issue IIDs (e.g., "#123")
    for issue in issues:
        if f"#{issue['iid']}" in content:
            return 1

    return 0

def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:

    if not messages:
        raise ValueError("No messages provided")

    if not "namespace" in config or not "project_name" in config or not "query" in config:
        raise ValueError("Target must contain 'namespace', 'project_name' and 'query'")

    content = messages[-1].get("content", "")

    # Try URL-based validation first (for browser agents)
    url_result = _validate_via_url(content, config)
    if url_result is not None:
        return url_result

    # Fall back to API-based validation (for terminal agents)
    return _validate_via_api(content, config)