"""Gitea API tools using per-user access tokens.
Provides repository, issue, PR, commit, file, notification, and star
operations via the Gitea REST API. Supports custom base URLs for
self-hosted instances. Requires set_user_api_key service=gitea.
"""
from __future__ import annotations
import base64
import jsonutil as json
import logging
from typing import Any, TYPE_CHECKING
from urllib.parse import quote
import aiohttp
if TYPE_CHECKING:
from tool_context import ToolContext
logger = logging.getLogger(__name__)
async def _gitea_request(
method: str,
path: str,
token: str,
base_url: str,
*,
params: dict[str, Any] | None = None,
json_body: dict[str, Any] | None = None,
) -> dict[str, Any] | list[Any] | str:
"""Perform a single authenticated HTTP request against the Gitea REST API.
The shared transport behind every Gitea tool handler in this module: it
builds the ``/api/v1`` URL from *base_url* (which must NOT already include
the ``/api/v1`` suffix), attaches the per-user ``token`` as a Gitea
``Authorization`` header, and issues *method* via a freshly opened
``aiohttp.ClientSession``. Decoding is normalised so callers get a uniform
shape: a ``204 No Content`` becomes a small success dict, any ``>= 400``
status becomes an error dict with a truncated ``detail`` body, an empty
body becomes ``{}``, valid JSON is parsed, and anything else is returned as
a truncated raw string. This performs outbound network I/O over HTTP but
touches no Redis, no knowledge graph, and no event bus.
Called by every ``gitea_*`` handler in this module (and by the private
helpers they delegate to); it is not exposed as a tool itself.
Args:
method: HTTP verb such as ``GET``, ``POST``, ``PATCH``, or ``DELETE``.
path: API path beginning with a slash, appended after ``/api/v1``.
token: Per-user Gitea access token sent in the ``Authorization`` header.
base_url: Instance base URL without the ``/api/v1`` suffix; a trailing
slash is stripped.
params: Optional query-string parameters.
json_body: Optional JSON request body.
Returns:
The decoded JSON object or list on success, a success/error dict for
no-content or failing responses, or a truncated raw string when the
body is not JSON.
"""
base = base_url.rstrip("/")
url = f"{base}/api/v1{path}"
headers = {
"Authorization": f"token {token}",
"Accept": "application/json",
}
async with aiohttp.ClientSession() as session:
async with session.request(
method, url, headers=headers, params=params, json=json_body
) as resp:
if resp.status == 204:
return {"status": "success", "code": 204}
body = await resp.text()
if resp.status >= 400:
return {
"error": f"Gitea API error ({resp.status})",
"detail": body[:2000],
}
if not body:
return {}
try:
return json.loads(body)
except json.JSONDecodeError:
return body[:4000]
async def _get_credentials(ctx: ToolContext | None) -> tuple[str, str]:
"""Resolve the calling user's Gitea access token and instance base URL.
The credential gate in front of every Gitea handler: it pulls the
``user_id``, ``redis``, ``channel_id``, and ``config`` off the supplied
``ToolContext`` and delegates to ``get_gitea_credentials`` in
``tools.manage_api_keys``, which looks up the per-user token (set via the
``set_user_api_key`` flow with ``service=gitea``) in Redis and falls back to
a shared pool token when the user has none. When nothing is configured it
raises with the human-readable message from ``missing_api_key_error`` so the
LLM sees actionable guidance rather than an opaque failure. Reads from Redis
through the credential lookup but performs no writes and no other I/O.
Called by every ``gitea_*`` tool handler in this module as their first step.
Args:
ctx: The ``ToolContext`` for the invocation, or ``None``; its
``user_id``, ``redis``, ``channel_id``, and ``config`` attributes
are read defensively via ``getattr``.
Returns:
A ``(token, base_url)`` tuple suitable for passing to
``_gitea_request``.
Raises:
RuntimeError: If no Gitea credentials are configured for the user and
no pool fallback is available.
"""
from tools.manage_api_keys import get_gitea_credentials, missing_api_key_error
user_id = getattr(ctx, "user_id", None) if ctx else None
creds = await get_gitea_credentials(
user_id or "",
redis_client=getattr(ctx, "redis", None) if ctx else None,
channel_id=getattr(ctx, "channel_id", None) if ctx else None,
fallback_to_pool=True,
config=getattr(ctx, "config", None) if ctx else None,
)
if not creds:
raise RuntimeError(missing_api_key_error("gitea"))
return creds
def _fmt_repo(r: dict) -> dict:
"""Condense a raw Gitea repository object into a compact summary dict.
Projects the verbose repository payload returned by the Gitea REST API
down to the handful of fields the LLM actually needs (full name,
description, visibility, fork flag, star/fork counts, default branch,
HTML URL, last-updated timestamp). This keeps tool responses small and
token-efficient. Pure data transformation with no I/O or side effects;
it simply reads keys off the supplied mapping via ``dict.get`` so missing
keys degrade to ``None`` rather than raising.
This is a private formatting helper used by the repository-returning tool
handlers in this module: ``gitea_list_repos``, ``gitea_search_repos``,
``gitea_get_repo``, ``gitea_create_repo``, ``gitea_create_repo_from_template``,
and ``gitea_fork_repo`` all pass each repo dict through here before
serialising. The same helper name also exists independently in
``tools/github_tools.py``; the two are unrelated copies.
Args:
r (dict): A single repository object as decoded from a Gitea API
response. Any missing keys are tolerated.
Returns:
dict: A flat summary containing ``full_name``, ``description``,
``private``, ``fork``, ``stars_count``, ``forks_count``,
``default_branch``, ``html_url``, and ``updated_at``.
"""
return {
"full_name": r.get("full_name"),
"description": r.get("description"),
"private": r.get("private"),
"fork": r.get("fork"),
"stars_count": r.get("stars_count"),
"forks_count": r.get("forks_count"),
"default_branch": r.get("default_branch"),
"html_url": r.get("html_url"),
"updated_at": r.get("updated_at"),
}
def _fmt_issue(i: dict) -> dict:
"""Condense a raw Gitea issue object into a compact summary dict.
Projects the verbose issue payload from the Gitea REST API down to the
fields useful to the LLM: number, title, state, author login, label
names, timestamps, HTML URL, and a body excerpt. The nested ``user``
object is flattened to its ``login`` (guarding against non-dict values),
the ``labels`` list is reduced to label names, and the body is truncated
to the first 1000 characters to bound response size. Pure transformation
with no I/O or side effects.
This is a private formatting helper used by the issue-returning handlers
``gitea_list_issues``, ``gitea_get_issue``, ``gitea_create_issue``, and
``gitea_update_issue`` before they serialise their results to JSON. An
unrelated helper of the same name also exists in ``tools/github_tools.py``.
Args:
i (dict): A single issue object as decoded from a Gitea API response.
Missing keys are tolerated and produce ``None`` or empty values.
Returns:
dict: A flat summary with ``number``, ``title``, ``state``, ``user``,
``labels``, ``created_at``, ``updated_at``, ``html_url``, and a
body excerpt under ``body``.
"""
user = i.get("user") or {}
return {
"number": i.get("number"),
"title": i.get("title"),
"state": i.get("state"),
"user": user.get("login") if isinstance(user, dict) else None,
"labels": [l.get("name") for l in i.get("labels", []) if isinstance(l, dict)],
"created_at": i.get("created_at"),
"updated_at": i.get("updated_at"),
"html_url": i.get("html_url"),
"body": (i.get("body") or "")[:1000],
}
def _fmt_pr(p: dict) -> dict:
"""Condense a raw Gitea pull-request object into a compact summary dict.
Projects the verbose pull-request payload from the Gitea REST API down to
the fields useful to the LLM: number, title, state, author login, draft
and merged flags, timestamps, HTML URL, and diff statistics (additions,
deletions, changed files). The nested ``user`` object is flattened to its
``login`` while guarding against non-dict values. Pure transformation with
no I/O or side effects.
This is a private formatting helper used by the PR-returning handlers
``gitea_list_pull_requests``, ``gitea_get_pull_request``, and
``gitea_create_pull_request`` before they serialise to JSON
(``gitea_get_pull_request`` additionally attaches a truncated body on top
of this summary). An unrelated helper of the same name also exists in
``tools/github_tools.py``.
Args:
p (dict): A single pull-request object as decoded from a Gitea API
response. Missing keys are tolerated and produce ``None``.
Returns:
dict: A flat summary with ``number``, ``title``, ``state``, ``user``,
``draft``, ``merged``, ``created_at``, ``updated_at``, ``html_url``,
``additions``, ``deletions``, and ``changed_files``.
"""
user = p.get("user") or {}
return {
"number": p.get("number"),
"title": p.get("title"),
"state": p.get("state"),
"user": user.get("login") if isinstance(user, dict) else None,
"draft": p.get("draft"),
"merged": p.get("merged"),
"created_at": p.get("created_at"),
"updated_at": p.get("updated_at"),
"html_url": p.get("html_url"),
"additions": p.get("additions"),
"deletions": p.get("deletions"),
"changed_files": p.get("changed_files"),
}
# ---------------------------------------------------------------------------
# Tool handlers
# ---------------------------------------------------------------------------
[docs]
async def gitea_list_repos(
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List the authenticated user's own Gitea repositories.
Backs the ``gitea_list_repos`` tool. Resolves the caller's token via
``_get_credentials`` and pages the ``GET /user/repos`` endpoint through
``_gitea_request``, condensing each repository through ``_fmt_repo`` so the
LLM receives a compact, token-efficient listing rather than the full Gitea
payloads. The ``limit`` is clamped to 50 to match the instance ceiling.
Performs HTTP and a Redis-backed credential read only; no writes.
Registered as the ``gitea_list_repos`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with a ``count`` and a ``repos`` array of compact
repository summaries, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
params = {"page": page, "limit": min(limit, 50)}
data = await _gitea_request("GET", "/user/repos", token, base_url, params=params)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
repos = [_fmt_repo(r) for r in data] if isinstance(data, list) else []
return json.dumps({"count": len(repos), "repos": repos})
[docs]
async def gitea_search_repos(
q: str,
sort: str = "updated",
order: str = "desc",
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""Search public and accessible Gitea repositories by keyword.
Backs the ``gitea_search_repos`` tool. Resolves credentials via
``_get_credentials`` and queries ``GET /repos/search`` through
``_gitea_request`` with the supplied keyword, sort field, direction, and
paging, then condenses each hit through ``_fmt_repo``. Unlike the raw
endpoint this unwraps the Gitea ``data`` envelope before formatting.
Performs HTTP and a Redis-backed credential read only; no writes.
Registered as the ``gitea_search_repos`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
q: Free-text search keyword.
sort: Sort field (e.g. ``updated``, ``created``, ``stars``).
order: Sort direction, ``asc`` or ``desc``.
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with a ``count`` and a ``repos`` array of compact
repository summaries, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
params = {
"q": q,
"sort": sort,
"order": order,
"page": page,
"limit": min(limit, 50),
}
data = await _gitea_request("GET", "/repos/search", token, base_url, params=params)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
items = data.get("data", []) if isinstance(data, dict) else []
repos = [_fmt_repo(r) for r in items]
return json.dumps({"count": len(repos), "repos": repos})
[docs]
async def gitea_get_repo(
owner: str,
repo: str,
ctx: ToolContext | None = None,
) -> str:
"""Fetch the details of a single Gitea repository.
Backs the ``gitea_get_repo`` tool. Resolves credentials via
``_get_credentials`` and requests ``GET /repos/{owner}/{repo}`` through
``_gitea_request``, returning the repository condensed through
``_fmt_repo``. Performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_get_repo`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the compact repository summary, or a JSON error
object on API failure.
"""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request("GET", f"/repos/{owner}/{repo}", token, base_url)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_repo(data) if isinstance(data, dict) else data)
[docs]
async def gitea_create_repo(
name: str,
description: str = "",
private: bool = False,
auto_init: bool = True,
ctx: ToolContext | None = None,
) -> str:
"""Create a new repository owned by the authenticated user.
Backs the ``gitea_create_repo`` tool. Resolves credentials via
``_get_credentials`` and POSTs to ``/user/repos`` through ``_gitea_request``
with the name, optional description, privacy flag, and an ``auto_init`` flag
that seeds a README so the repo has a default branch. This mutates server
state on the Gitea instance; locally it performs HTTP and a Redis-backed
credential read only. The created repo is returned condensed through
``_fmt_repo``.
Registered as the ``gitea_create_repo`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
name: Name for the new repository.
description: Optional repository description; omitted when empty.
private: Whether the repository should be private.
auto_init: Whether to initialise the repository with a README.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the compact summary of the created repository, or a
JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"name": name}
if description:
payload["description"] = description
payload["private"] = private
payload["auto_init"] = auto_init
data = await _gitea_request(
"POST", "/user/repos", token, base_url, json_body=payload
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_repo(data) if isinstance(data, dict) else data)
[docs]
async def gitea_list_issues(
owner: str,
repo: str,
state: str = "open",
labels: str = "",
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List issues on a Gitea repository, optionally filtered by state and labels.
Backs the ``gitea_list_issues`` tool. Resolves credentials via
``_get_credentials`` and pages ``GET /repos/{owner}/{repo}/issues`` through
``_gitea_request``, passing the ``state`` filter (``open``/``closed``/``all``)
and an optional comma-separated ``labels`` filter, then condenses each issue
through ``_fmt_issue``. Performs HTTP and a Redis-backed credential read
only; no writes.
Registered as the ``gitea_list_issues`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
state: Issue state filter: ``open``, ``closed``, or ``all``.
labels: Optional comma-separated label names to filter by.
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with a ``count`` and an ``issues`` array of compact issue
summaries, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
params: dict[str, Any] = {"state": state, "page": page, "limit": min(limit, 50)}
if labels:
params["labels"] = labels
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/issues", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
issues = [_fmt_issue(i) for i in data] if isinstance(data, list) else []
return json.dumps({"count": len(issues), "issues": issues})
[docs]
async def gitea_get_issue(
owner: str,
repo: str,
index: int,
ctx: ToolContext | None = None,
) -> str:
"""Fetch a single issue by its per-repository index number.
Backs the ``gitea_get_issue`` tool. Resolves credentials via
``_get_credentials`` and requests ``GET /repos/{owner}/{repo}/issues/{index}``
through ``_gitea_request``, returning the issue condensed through
``_fmt_issue``. Performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_get_issue`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
index: The issue's per-repository number.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the compact issue summary, or a JSON error object on
API failure.
"""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/issues/{index}", token, base_url
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_issue(data) if isinstance(data, dict) else data)
[docs]
async def gitea_create_issue(
owner: str,
repo: str,
title: str,
body: str = "",
labels: list[int] | None = None,
assignees: list[str] | None = None,
ctx: ToolContext | None = None,
) -> str:
"""Create a new issue on a Gitea repository.
Backs the ``gitea_create_issue`` tool. Resolves credentials via
``_get_credentials`` and POSTs to ``/repos/{owner}/{repo}/issues`` through
``_gitea_request`` with the title and any supplied body, label IDs, and
assignee usernames, returning the created issue condensed through
``_fmt_issue``. This mutates state on the Gitea instance; locally it
performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_create_issue`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop. It is
also imported and awaited directly by ``tools.gitea_integration`` to file
issues from higher-level automation flows.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
title: Issue title.
body: Optional Markdown issue body.
labels: Optional list of numeric label IDs to apply.
assignees: Optional list of assignee usernames.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the compact summary of the created issue, or a JSON
error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"title": title}
if body:
payload["body"] = body
if labels:
payload["labels"] = labels
if assignees:
payload["assignees"] = assignees
data = await _gitea_request(
"POST", f"/repos/{owner}/{repo}/issues", token, base_url, json_body=payload
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_issue(data) if isinstance(data, dict) else data)
[docs]
async def gitea_update_issue(
owner: str,
repo: str,
index: int,
title: str | None = None,
body: str | None = None,
state: str | None = None,
ctx: ToolContext | None = None,
) -> str:
"""Update the title, body, and/or state of an existing issue.
Backs the ``gitea_update_issue`` tool. Resolves credentials via
``_get_credentials`` and PATCHes ``/repos/{owner}/{repo}/issues/{index}``
through ``_gitea_request`` with only the fields that were supplied (each
``None`` argument is omitted), returning the updated issue condensed through
``_fmt_issue``. Setting ``state`` to ``closed`` is how issues are closed.
This mutates state on the Gitea instance; locally it performs HTTP and a
Redis-backed credential read only.
Registered as the ``gitea_update_issue`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop. It is
also imported and awaited directly by ``tools.gitea_integration`` (with
``state="closed"``) to close issues from higher-level automation flows.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
index: The issue's per-repository number.
title: New title, or ``None`` to leave unchanged.
body: New Markdown body, or ``None`` to leave unchanged.
state: New state (``open`` or ``closed``), or ``None`` to leave
unchanged.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the compact summary of the updated issue, or a JSON
error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {}
if title is not None:
payload["title"] = title
if body is not None:
payload["body"] = body
if state is not None:
payload["state"] = state
data = await _gitea_request(
"PATCH",
f"/repos/{owner}/{repo}/issues/{index}",
token,
base_url,
json_body=payload,
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_issue(data) if isinstance(data, dict) else data)
[docs]
async def gitea_list_pull_requests(
owner: str,
repo: str,
state: str = "open",
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List pull requests on a Gitea repository, filtered by state.
Backs the ``gitea_list_pull_requests`` tool. Resolves credentials via
``_get_credentials`` and pages ``GET /repos/{owner}/{repo}/pulls`` through
``_gitea_request`` with the ``state`` filter, condensing each PR through
``_fmt_pr``. Performs HTTP and a Redis-backed credential read only; no
writes.
Registered as the ``gitea_list_pull_requests`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
state: PR state filter: ``open``, ``closed``, or ``all``.
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with a ``count`` and a ``pull_requests`` array of compact
PR summaries, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
params = {"state": state, "page": page, "limit": min(limit, 50)}
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/pulls", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
prs = [_fmt_pr(p) for p in data] if isinstance(data, list) else []
return json.dumps({"count": len(prs), "pull_requests": prs})
[docs]
async def gitea_get_pull_request(
owner: str,
repo: str,
index: int,
ctx: ToolContext | None = None,
) -> str:
"""Fetch a single pull request by index, including a body excerpt.
Backs the ``gitea_get_pull_request`` tool. Resolves credentials via
``_get_credentials`` and requests ``GET /repos/{owner}/{repo}/pulls/{index}``
through ``_gitea_request``, condensing the PR through ``_fmt_pr`` and then
attaching the first 2000 characters of its body on top of that summary so
the LLM can read the description. Performs HTTP and a Redis-backed
credential read only.
Registered as the ``gitea_get_pull_request`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
index: The pull request's per-repository number.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the compact PR summary plus a truncated ``body``, or
a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/pulls/{index}", token, base_url
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
result = _fmt_pr(data) if isinstance(data, dict) else {"raw": data}
if isinstance(data, dict) and data.get("body"):
result["body"] = (data.get("body") or "")[:2000]
return json.dumps(result)
[docs]
async def gitea_create_pull_request(
owner: str,
repo: str,
title: str,
head: str,
base: str = "main",
body: str = "",
ctx: ToolContext | None = None,
) -> str:
"""Open a new pull request from a head branch into a base branch.
Backs the ``gitea_create_pull_request`` tool. Resolves credentials via
``_get_credentials`` and POSTs to ``/repos/{owner}/{repo}/pulls`` through
``_gitea_request`` with the title, head ref (which may be ``owner:branch``
for cross-fork PRs), base branch, and optional body, returning the created
PR condensed through ``_fmt_pr``. This mutates state on the Gitea instance;
locally it performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_create_pull_request`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
title: Pull request title.
head: Source branch, or ``owner:branch`` when the head is on a fork.
base: Target branch to merge into; defaults to ``main``.
body: Optional Markdown description; omitted when empty.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the compact summary of the created pull request, or
a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"title": title, "head": head, "base": base}
if body:
payload["body"] = body
data = await _gitea_request(
"POST", f"/repos/{owner}/{repo}/pulls", token, base_url, json_body=payload
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_pr(data) if isinstance(data, dict) else data)
[docs]
async def gitea_merge_pull_request(
owner: str,
repo: str,
index: int,
merge_style: str = "merge",
delete_branch_after_merge: bool = False,
ctx: ToolContext | None = None,
) -> str:
"""Merge a pull request using the requested merge strategy.
Backs the ``gitea_merge_pull_request`` tool. Resolves credentials via
``_get_credentials`` and POSTs to ``/repos/{owner}/{repo}/pulls/{index}/merge``
through ``_gitea_request`` with the merge strategy under Gitea's ``Do`` key
and an optional flag to delete the head branch afterwards. The supported
``merge_style`` values are ``merge``, ``rebase``, ``rebase-merge``, and
``squash``. On success it returns a fixed status object rather than the raw
API body. This mutates repository state (and may delete a branch); locally
it performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_merge_pull_request`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
index: The pull request's per-repository number.
merge_style: Merge strategy: ``merge``, ``rebase``, ``rebase-merge``, or
``squash``.
delete_branch_after_merge: Whether to delete the head branch once
merged.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON success object on a successful merge, or a JSON error object on
API failure.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"Do": merge_style}
if delete_branch_after_merge:
payload["delete_branch_after_merge"] = True
data = await _gitea_request(
"POST",
f"/repos/{owner}/{repo}/pulls/{index}/merge",
token,
base_url,
json_body=payload,
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps({"status": "success", "message": "Pull request merged"})
[docs]
async def gitea_get_file(
owner: str,
repo: str,
path: str,
ref: str = "",
ctx: ToolContext | None = None,
) -> str:
"""Read a file's contents or list a directory in a Gitea repository.
Backs the ``gitea_get_file`` tool. Resolves credentials via
``_get_credentials`` and requests ``GET /repos/{owner}/{repo}/contents/{path}``
through ``_gitea_request`` at an optional ``ref`` (branch, tag, or SHA). The
Gitea contents endpoint is polymorphic, so this branches on the response:
for a file it base64-decodes the embedded content (falling back to a
"(binary file)" marker when decoding fails) and returns name, path, size,
SHA, a truncated body, and HTML URL; for a directory it returns a list of
name/type/path entries. Performs HTTP and a Redis-backed credential read
only.
Registered as the ``gitea_get_file`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
path: File or directory path within the repository.
ref: Optional branch, tag, or commit SHA; defaults to the default
branch.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string describing either a file (with decoded, truncated
``content``) or a directory listing, or a JSON error object on API
failure.
"""
token, base_url = await _get_credentials(ctx)
params = {}
if ref:
params["ref"] = ref
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/contents/{path}", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
if isinstance(data, dict) and data.get("type") == "file":
content_b64 = data.get("content", "")
try:
content = base64.b64decode(content_b64).decode("utf-8", errors="replace")
except Exception:
content = "(binary file)"
return json.dumps(
{
"name": data.get("name"),
"path": data.get("path"),
"size": data.get("size"),
"sha": data.get("sha"),
"content": content[:16000],
"html_url": data.get("html_url"),
}
)
if isinstance(data, list):
entries = [
{"name": e.get("name"), "type": e.get("type"), "path": e.get("path")}
for e in data
]
return json.dumps({"type": "directory", "entries": entries})
return json.dumps(data if isinstance(data, dict) else {"raw": str(data)[:4000]})
[docs]
async def gitea_list_commits(
owner: str,
repo: str,
sha: str = "",
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List commits in a Gitea repository, optionally from a given ref.
Backs the ``gitea_list_commits`` tool. Resolves credentials via
``_get_credentials`` and pages ``GET /repos/{owner}/{repo}/commits`` through
``_gitea_request``, optionally scoped to a branch or SHA. Each commit is
flattened inline to its SHA, a truncated message, author name and date, and
HTML URL (rather than via a shared formatter). Performs HTTP and a
Redis-backed credential read only; no writes.
Registered as the ``gitea_list_commits`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
sha: Optional branch name or commit SHA to list history from.
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with a ``count`` and a ``commits`` array of compact commit
summaries, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
params: dict[str, Any] = {"page": page, "limit": min(limit, 50)}
if sha:
params["sha"] = sha
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/commits", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
commits = []
for c in (data if isinstance(data, list) else []):
commit = c.get("commit") or {}
author = commit.get("author") or {}
commits.append(
{
"sha": c.get("sha"),
"message": commit.get("message", "")[:500],
"author": author.get("name"),
"date": author.get("date"),
"html_url": c.get("html_url"),
}
)
return json.dumps({"count": len(commits), "commits": commits})
[docs]
async def gitea_get_commit(
owner: str,
repo: str,
sha: str,
ctx: ToolContext | None = None,
) -> str:
"""Fetch a single commit's metadata and stats by SHA or ref.
Backs the ``gitea_get_commit`` tool. Resolves credentials via
``_get_credentials`` and requests ``GET /repos/{owner}/{repo}/git/commits/{sha}``
through ``_gitea_request``, returning the SHA, a truncated commit message,
author name and date, HTML URL, and the diff ``stats`` block. Defensive
``isinstance`` guards keep the projection safe when the API returns an
unexpected shape. Performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_get_commit`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
sha: Commit SHA or ref to fetch.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the compact commit summary including ``stats``, or a
JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/git/commits/{sha}", token, base_url
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
commit = data.get("commit") or {} if isinstance(data, dict) else {}
author = commit.get("author") or {}
return json.dumps(
{
"sha": data.get("sha") if isinstance(data, dict) else None,
"message": commit.get("message", "")[:2000],
"author": author.get("name"),
"date": author.get("date"),
"html_url": data.get("html_url") if isinstance(data, dict) else None,
"stats": data.get("stats") if isinstance(data, dict) else None,
}
)
[docs]
async def gitea_list_notifications(
all_notifications: bool = False,
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List the authenticated user's Gitea notification feed.
Backs the ``gitea_list_notifications`` tool. Resolves credentials via
``_get_credentials`` and pages ``GET /notifications`` through
``_gitea_request``; by default only unread notifications are returned,
while ``all_notifications`` includes read ones. Each entry is flattened
inline to its id, unread flag, subject title and type, owning repository
full name, and update timestamp. Performs HTTP and a Redis-backed
credential read only; no writes.
Registered as the ``gitea_list_notifications`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
all_notifications: Whether to include already-read notifications.
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with a ``count`` and a ``notifications`` array of compact
notification summaries, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
params = {
"all": str(all_notifications).lower(),
"page": page,
"limit": min(limit, 50),
}
data = await _gitea_request("GET", "/notifications", token, base_url, params=params)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
notifs = []
for n in (data if isinstance(data, list) else []):
subject = n.get("subject") or {}
notifs.append(
{
"id": n.get("id"),
"unread": n.get("unread"),
"subject_title": subject.get("title"),
"subject_type": subject.get("type"),
"repository": (
n.get("repository", {}).get("full_name")
if isinstance(n.get("repository"), dict)
else None
),
"updated_at": n.get("updated_at"),
}
)
return json.dumps({"count": len(notifs), "notifications": notifs})
[docs]
async def gitea_star_repo(
owner: str,
repo: str,
star: bool = True,
ctx: ToolContext | None = None,
) -> str:
"""Star or unstar a Gitea repository for the authenticated user.
Backs the ``gitea_star_repo`` tool. Resolves credentials via
``_get_credentials`` and calls ``/user/starred/{owner}/{repo}`` through
``_gitea_request`` using ``PUT`` to star or ``DELETE`` to unstar based on
the ``star`` flag. On success it returns a fixed status object naming the
action taken. This mutates the user's star set on the Gitea instance;
locally it performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_star_repo`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
star: ``True`` to star the repository, ``False`` to unstar it.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON success object describing the ``starred`` or ``unstarred``
action, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
method = "PUT" if star else "DELETE"
data = await _gitea_request(
method, f"/user/starred/{owner}/{repo}", token, base_url
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
action = "starred" if star else "unstarred"
return json.dumps(
{"status": "success", "action": action, "repo": f"{owner}/{repo}"}
)
[docs]
async def gitea_list_branches(
owner: str,
repo: str,
page: int = 1,
limit: int = 30,
ctx: ToolContext | None = None,
) -> str:
"""List the branches of a Gitea repository.
Backs the ``gitea_list_branches`` tool. Resolves credentials via
``_get_credentials`` and pages ``GET /repos/{owner}/{repo}/branches`` through
``_gitea_request``, projecting each branch to its name, protection flag, and
head commit SHA. Performs HTTP and a Redis-backed credential read only; no
writes.
Registered as the ``gitea_list_branches`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with a ``count`` and a ``branches`` array of compact
branch summaries, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
params = {"page": page, "limit": min(limit, 50)}
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/branches", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
branches = [
{
"name": b.get("name"),
"protected": b.get("protected"),
"commit_sha": (b.get("commit") or {}).get("sha"),
}
for b in (data if isinstance(data, list) else [])
]
return json.dumps({"count": len(branches), "branches": branches})
[docs]
async def gitea_create_repo_from_template(
template_owner: str,
template_repo: str,
name: str,
description: str = "",
private: bool = False,
git_content: bool = True,
ctx: ToolContext | None = None,
) -> str:
"""Generate a new repository from an existing template repository.
Backs the ``gitea_create_repo_from_template`` tool. Resolves credentials via
``_get_credentials`` and POSTs to
``/repos/{template_owner}/{template_repo}/generate`` through
``_gitea_request`` with the new repo name, privacy flag, an optional
description, and a ``git_content`` flag that controls whether the template's
git history is copied. The created repo is returned condensed through
``_fmt_repo``. This mutates state on the Gitea instance; locally it performs
HTTP and a Redis-backed credential read only.
Registered as the ``gitea_create_repo_from_template`` handler in this
module's ``TOOLS`` list and dispatched by name from the inference worker's
tool loop; not called directly elsewhere in the repo.
Args:
template_owner: Owner of the template repository.
template_repo: Name of the template repository.
name: Name for the new repository.
description: Optional description for the new repository.
private: Whether the new repository should be private.
git_content: Whether to copy the template's git content.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the compact summary of the generated repository, or
a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {
"name": name,
"private": private,
"git_content": git_content,
}
if description:
payload["description"] = description
data = await _gitea_request(
"POST",
f"/repos/{template_owner}/{template_repo}/generate",
token,
base_url,
json_body=payload,
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
return json.dumps(_fmt_repo(data) if isinstance(data, dict) else data)
[docs]
async def gitea_compare_commits(
owner: str,
repo: str,
basehead: str,
ctx: ToolContext | None = None,
) -> str:
"""Compare two refs or commits and report the diff between them.
Backs the ``gitea_compare_commits`` tool. Resolves credentials via
``_get_credentials`` and requests ``GET /repos/{owner}/{repo}/compare/{basehead}``
through ``_gitea_request``, where ``basehead`` uses Gitea's compare syntax
(e.g. ``main...feature``); the value is URL-quoted while preserving ``:`` and
``.`` so the ``...`` separator survives. Unlike most handlers it returns the
raw API response unconditionally (wrapping non-JSON in a truncated ``raw``
field) rather than reformatting. Performs HTTP and a Redis-backed credential
read only.
Registered as the ``gitea_compare_commits`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
basehead: Compare expression in ``base...head`` form.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the comparison result (commits and changed files),
or a truncated raw payload when the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
path = f"/repos/{owner}/{repo}/compare/{quote(basehead, safe=':.')}"
data = await _gitea_request("GET", path, token, base_url)
return (
json.dumps(data)
if isinstance(data, (dict, list))
else json.dumps({"raw": str(data)[:8000]})
)
[docs]
async def gitea_create_tag(
owner: str,
repo: str,
tag_name: str,
target: str,
message: str = "",
ctx: ToolContext | None = None,
) -> str:
"""Create a tag pointing at a commit or branch.
Backs the ``gitea_create_tag`` tool. Resolves credentials via
``_get_credentials`` and POSTs to ``/repos/{owner}/{repo}/tags`` through
``_gitea_request`` with the tag name and target ref, plus an optional
message that makes it an annotated tag. The Gitea instance generally
requires a ``write:repository`` scope for this. This mutates repository
state; locally it performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_create_tag`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
tag_name: Name of the tag to create.
target: Branch name or commit SHA the tag should point at.
message: Optional annotated-tag message; omitted when empty.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the created tag object, or a truncated raw payload
when the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"tag_name": tag_name, "target": target}
if message:
payload["message"] = message
data = await _gitea_request(
"POST", f"/repos/{owner}/{repo}/tags", token, base_url, json_body=payload
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"raw": str(data)[:4000]})
)
[docs]
async def gitea_list_milestones(
owner: str,
repo: str,
state: str = "open",
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List the milestones of a Gitea repository, filtered by state.
Backs the ``gitea_list_milestones`` tool. Resolves credentials via
``_get_credentials`` and pages ``GET /repos/{owner}/{repo}/milestones``
through ``_gitea_request`` with the ``state`` filter, projecting each
milestone to id, title, a truncated description, state, open/closed issue
counts, and due date. Most Gitea instances require a ``read:issue`` scope
for this. Performs HTTP and a Redis-backed credential read only; no writes.
Registered as the ``gitea_list_milestones`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
state: Milestone state filter: ``open``, ``closed``, or ``all``.
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with a ``count`` and a ``milestones`` array of compact
milestone summaries, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
params: dict[str, Any] = {"state": state, "page": page, "limit": min(limit, 50)}
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/milestones", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
out = []
for m in (data if isinstance(data, list) else []):
out.append(
{
"id": m.get("id"),
"title": m.get("title"),
"description": (m.get("description") or "")[:500],
"state": m.get("state"),
"open_issues": m.get("open_issues"),
"closed_issues": m.get("closed_issues"),
"due_on": m.get("due_on"),
}
)
return json.dumps({"count": len(out), "milestones": out})
[docs]
async def gitea_get_milestone(
owner: str,
repo: str,
milestone_id: int,
ctx: ToolContext | None = None,
) -> str:
"""Fetch a single milestone by its numeric id.
Backs the ``gitea_get_milestone`` tool. Resolves credentials via
``_get_credentials`` and requests
``GET /repos/{owner}/{repo}/milestones/{milestone_id}`` through
``_gitea_request``, returning the raw milestone object (wrapping a non-JSON
response in a truncated ``raw`` field). Performs HTTP and a Redis-backed
credential read only.
Registered as the ``gitea_get_milestone`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
milestone_id: Numeric milestone id.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the milestone object, or a truncated raw payload
when the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/milestones/{milestone_id}", token, base_url
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"raw": str(data)[:4000]})
)
[docs]
async def gitea_create_milestone(
owner: str,
repo: str,
title: str,
description: str = "",
due_on: str = "",
ctx: ToolContext | None = None,
) -> str:
"""Create a new milestone on a Gitea repository.
Backs the ``gitea_create_milestone`` tool. Resolves credentials via
``_get_credentials`` and POSTs to ``/repos/{owner}/{repo}/milestones``
through ``_gitea_request`` with the title and any supplied description and
due date. The ``due_on`` value, when set, must be an ISO 8601 datetime.
Creating milestones typically requires a ``write:issue`` scope on the
instance. This mutates repository state; locally it performs HTTP and a
Redis-backed credential read only.
Registered as the ``gitea_create_milestone`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
title: Milestone title.
description: Optional milestone description; omitted when empty.
due_on: Optional ISO 8601 due date; omitted when empty.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the created milestone object, or a truncated raw
payload when the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"title": title}
if description:
payload["description"] = description
if due_on:
payload["due_on"] = due_on
data = await _gitea_request(
"POST", f"/repos/{owner}/{repo}/milestones", token, base_url, json_body=payload
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"raw": str(data)[:4000]})
)
[docs]
async def gitea_update_milestone(
owner: str,
repo: str,
milestone_id: int,
title: str | None = None,
description: str | None = None,
state: str | None = None,
due_on: str | None = None,
ctx: ToolContext | None = None,
) -> str:
"""Update the fields of an existing milestone.
Backs the ``gitea_update_milestone`` tool. Resolves credentials via
``_get_credentials`` and PATCHes
``/repos/{owner}/{repo}/milestones/{milestone_id}`` through ``_gitea_request``
with only the fields that were supplied (each ``None`` argument is omitted),
so it can change the title, description, state, and/or due date in one call.
This mutates repository state; locally it performs HTTP and a Redis-backed
credential read only.
Registered as the ``gitea_update_milestone`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
milestone_id: Numeric milestone id.
title: New title, or ``None`` to leave unchanged.
description: New description, or ``None`` to leave unchanged.
state: New state (``open`` or ``closed``), or ``None`` to leave
unchanged.
due_on: New ISO 8601 due date, or ``None`` to leave unchanged.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the updated milestone object, or a truncated raw
payload when the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {}
if title is not None:
payload["title"] = title
if description is not None:
payload["description"] = description
if state is not None:
payload["state"] = state
if due_on is not None:
payload["due_on"] = due_on
data = await _gitea_request(
"PATCH",
f"/repos/{owner}/{repo}/milestones/{milestone_id}",
token,
base_url,
json_body=payload,
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"raw": str(data)[:4000]})
)
[docs]
async def gitea_delete_milestone(
owner: str,
repo: str,
milestone_id: int,
ctx: ToolContext | None = None,
) -> str:
"""Delete a milestone from a Gitea repository by id.
Backs the ``gitea_delete_milestone`` tool. Resolves credentials via
``_get_credentials`` and issues
``DELETE /repos/{owner}/{repo}/milestones/{milestone_id}`` through
``_gitea_request``; on a no-content success it returns a small status object
carrying the API detail. This permanently removes the milestone on the
Gitea instance; locally it performs HTTP and a Redis-backed credential read
only.
Registered as the ``gitea_delete_milestone`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
milestone_id: Numeric milestone id to delete.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the API response, or an ``ok`` status object when the
delete returns no content.
"""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"DELETE", f"/repos/{owner}/{repo}/milestones/{milestone_id}", token, base_url
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"status": "ok", "detail": str(data)})
)
[docs]
async def gitea_list_labels(
owner: str,
repo: str,
page: int = 1,
limit: int = 30,
ctx: ToolContext | None = None,
) -> str:
"""List the issue labels defined on a Gitea repository.
Backs the ``gitea_list_labels`` tool. Resolves credentials via
``_get_credentials`` and pages ``GET /repos/{owner}/{repo}/labels`` through
``_gitea_request``, projecting each label to its id, name, hex color, and
description. Many instances require a ``read:issue`` scope for this.
Performs HTTP and a Redis-backed credential read only; no writes.
Registered as the ``gitea_list_labels`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with a ``count`` and a ``labels`` array of compact label
summaries, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
params = {"page": page, "limit": min(limit, 50)}
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/labels", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
labels = []
for lb in (data if isinstance(data, list) else []):
labels.append(
{
"id": lb.get("id"),
"name": lb.get("name"),
"color": lb.get("color"),
"description": lb.get("description"),
}
)
return json.dumps({"count": len(labels), "labels": labels})
[docs]
async def gitea_create_label(
owner: str,
repo: str,
name: str,
color: str,
description: str = "",
ctx: ToolContext | None = None,
) -> str:
"""Create a new issue label on a Gitea repository.
Backs the ``gitea_create_label`` tool. Resolves credentials via
``_get_credentials`` and POSTs to ``/repos/{owner}/{repo}/labels`` through
``_gitea_request`` with the label name, a hex color (any leading ``#`` is
stripped so callers may pass either form), and an optional description.
Creating labels typically requires a ``write:issue`` scope on the instance.
This mutates repository state; locally it performs HTTP and a Redis-backed
credential read only.
Registered as the ``gitea_create_label`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
name: Label name.
color: Hex color, with or without a leading ``#`` (e.g. ``e11d21``).
description: Optional label description; omitted when empty.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the created label object, or a truncated raw payload
when the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"name": name, "color": color.lstrip("#")}
if description:
payload["description"] = description
data = await _gitea_request(
"POST", f"/repos/{owner}/{repo}/labels", token, base_url, json_body=payload
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"raw": str(data)[:4000]})
)
[docs]
async def gitea_list_releases(
owner: str,
repo: str,
draft: bool = False,
pre_release: bool = False,
page: int = 1,
limit: int = 10,
ctx: ToolContext | None = None,
) -> str:
"""List the releases of a Gitea repository.
Backs the ``gitea_list_releases`` tool. Resolves credentials via
``_get_credentials`` and pages ``GET /repos/{owner}/{repo}/releases`` through
``_gitea_request``, optionally filtering by draft and pre-release status, and
projects each release to its id, tag name, name, draft/prerelease flags,
timestamps, and HTML URL. Performs HTTP and a Redis-backed credential read
only; no writes.
Registered as the ``gitea_list_releases`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
draft: Whether to include draft releases in the listing.
pre_release: Whether to include pre-releases in the listing.
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with a ``count`` and a ``releases`` array of compact
release summaries, or a JSON error object on API failure.
"""
token, base_url = await _get_credentials(ctx)
params: dict[str, Any] = {
"draft": str(draft).lower(),
"pre-release": str(pre_release).lower(),
"page": page,
"limit": min(limit, 50),
}
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/releases", token, base_url, params=params
)
if isinstance(data, dict) and "error" in data:
return json.dumps(data)
rels = []
for r in (data if isinstance(data, list) else []):
rels.append(
{
"id": r.get("id"),
"tag_name": r.get("tag_name"),
"name": r.get("name"),
"draft": r.get("draft"),
"prerelease": r.get("prerelease"),
"created_at": r.get("created_at"),
"published_at": r.get("published_at"),
"html_url": r.get("html_url"),
}
)
return json.dumps({"count": len(rels), "releases": rels})
[docs]
async def gitea_get_release(
owner: str,
repo: str,
release_id: int,
ctx: ToolContext | None = None,
) -> str:
"""Fetch a single release by its numeric id.
Backs the ``gitea_get_release`` tool. Resolves credentials via
``_get_credentials`` and requests
``GET /repos/{owner}/{repo}/releases/{release_id}`` through ``_gitea_request``,
returning the raw release object (wrapping a non-JSON response in a
truncated ``raw`` field). Performs HTTP and a Redis-backed credential read
only.
Registered as the ``gitea_get_release`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
release_id: Numeric release id.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the release object, or a truncated raw payload when
the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/releases/{release_id}", token, base_url
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"raw": str(data)[:8000]})
)
[docs]
async def gitea_create_release(
owner: str,
repo: str,
tag_name: str,
name: str = "",
body: str = "",
target_commitish: str = "",
draft: bool = False,
prerelease: bool = False,
ctx: ToolContext | None = None,
) -> str:
"""Create a release for a Gitea repository from a tag.
Backs the ``gitea_create_release`` tool. Resolves credentials via
``_get_credentials`` and POSTs to ``/repos/{owner}/{repo}/releases`` through
``_gitea_request`` with the tag name, draft/prerelease flags, and any
supplied display name, body, and target commitish (which lets Gitea create
the tag on the fly when it does not yet exist). The instance generally
requires a ``write:repository`` or release scope. This mutates repository
state; locally it performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_create_release`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
tag_name: Tag the release is associated with.
name: Optional display name for the release; omitted when empty.
body: Optional Markdown release notes; omitted when empty.
target_commitish: Optional branch or SHA to tag if the tag does not yet
exist; omitted when empty.
draft: Whether to create the release as a draft.
prerelease: Whether to mark the release as a pre-release.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the created release object, or a truncated raw
payload when the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {
"tag_name": tag_name,
"draft": draft,
"prerelease": prerelease,
}
if name:
payload["name"] = name
if body:
payload["body"] = body
if target_commitish:
payload["target_commitish"] = target_commitish
data = await _gitea_request(
"POST", f"/repos/{owner}/{repo}/releases", token, base_url, json_body=payload
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"raw": str(data)[:4000]})
)
[docs]
async def gitea_delete_release(
owner: str,
repo: str,
release_id: int,
ctx: ToolContext | None = None,
) -> str:
"""Delete a release from a Gitea repository by id.
Backs the ``gitea_delete_release`` tool. Resolves credentials via
``_get_credentials`` and issues
``DELETE /repos/{owner}/{repo}/releases/{release_id}`` through
``_gitea_request``; on a no-content success it returns a small status object
carrying the API detail. This permanently removes the release on the Gitea
instance; locally it performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_delete_release`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
release_id: Numeric release id to delete.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the API response, or an ``ok`` status object when the
delete returns no content.
"""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"DELETE", f"/repos/{owner}/{repo}/releases/{release_id}", token, base_url
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"status": "ok", "detail": str(data)})
)
[docs]
async def gitea_fork_repo(
owner: str,
repo: str,
organization: str = "",
name: str = "",
ctx: ToolContext | None = None,
) -> str:
"""Fork a repository into the user's account or a chosen organization.
Backs the ``gitea_fork_repo`` tool. Resolves credentials via
``_get_credentials`` and POSTs to ``/repos/{owner}/{repo}/forks`` through
``_gitea_request`` with an optional target organization and an optional new
name (an empty payload, sent as ``None``, forks into the user's own
account). On success the new fork is condensed through ``_fmt_repo``, while
error responses are passed through unchanged. This mutates state on the
Gitea instance; locally it performs HTTP and a Redis-backed credential read
only.
Registered as the ``gitea_fork_repo`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Owner of the repository to fork.
repo: Name of the repository to fork.
organization: Optional organization to fork into instead of the user.
name: Optional name for the fork; defaults to the source name.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the compact summary of the new fork, or a JSON error
object on API failure.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {}
if organization:
payload["organization"] = organization
if name:
payload["name"] = name
data = await _gitea_request(
"POST",
f"/repos/{owner}/{repo}/forks",
token,
base_url,
json_body=payload or None,
)
return json.dumps(
_fmt_repo(data) if isinstance(data, dict) and "error" not in data else data
)
[docs]
async def gitea_create_branch(
owner: str,
repo: str,
new_branch_name: str,
old_branch_name: str = "",
old_ref_name: str = "",
ctx: ToolContext | None = None,
) -> str:
"""Create a new branch from an existing branch or ref.
Backs the ``gitea_create_branch`` tool. Resolves credentials via
``_get_credentials`` and POSTs to ``/repos/{owner}/{repo}/branches`` through
``_gitea_request`` with the new branch name and, when provided, a source
branch (``old_branch_name``) or arbitrary source ref (``old_ref_name``);
when neither is given the instance branches from the default branch. This
typically requires a ``write:repository`` scope and mutates repository
state; locally it performs HTTP and a Redis-backed credential read only.
Registered as the ``gitea_create_branch`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
new_branch_name: Name for the branch to create.
old_branch_name: Optional source branch to branch from; omitted when
empty.
old_ref_name: Optional source ref to branch from, as an alternative to
``old_branch_name``; omitted when empty.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the created branch object, or a truncated raw payload
when the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
payload: dict[str, Any] = {"new_branch_name": new_branch_name}
if old_branch_name:
payload["old_branch_name"] = old_branch_name
if old_ref_name:
payload["old_ref_name"] = old_ref_name
data = await _gitea_request(
"POST", f"/repos/{owner}/{repo}/branches", token, base_url, json_body=payload
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"raw": str(data)[:4000]})
)
[docs]
async def gitea_delete_branch(
owner: str,
repo: str,
branch: str,
ctx: ToolContext | None = None,
) -> str:
"""Delete a branch from a Gitea repository.
Backs the ``gitea_delete_branch`` tool. Resolves credentials via
``_get_credentials``, URL-encodes the branch name (so names containing
slashes survive), and issues
``DELETE /repos/{owner}/{repo}/branches/{branch}`` through ``_gitea_request``;
on a no-content success it returns a small status object carrying the API
detail. This typically requires a ``write:repository`` scope and removes the
branch on the Gitea instance; locally it performs HTTP and a Redis-backed
credential read only.
Registered as the ``gitea_delete_branch`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
branch: Name of the branch to delete.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the API response, or an ``ok`` status object when the
delete returns no content.
"""
token, base_url = await _get_credentials(ctx)
enc = quote(branch, safe="")
data = await _gitea_request(
"DELETE", f"/repos/{owner}/{repo}/branches/{enc}", token, base_url
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"status": "ok", "detail": str(data)})
)
[docs]
async def gitea_list_wiki_pages(
owner: str,
repo: str,
ctx: ToolContext | None = None,
) -> str:
"""List the wiki pages of a Gitea repository.
Backs the ``gitea_list_wiki_pages`` tool. Resolves credentials via
``_get_credentials`` and requests ``GET /repos/{owner}/{repo}/wiki/pages``
through ``_gitea_request``, returning the raw page index (wrapping a
non-structured response in a truncated ``raw`` field). When the repository
has its wiki disabled the underlying request yields a ``404`` error.
Performs HTTP and a Redis-backed credential read only; no writes.
Registered as the ``gitea_list_wiki_pages`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the wiki page list, or a truncated raw payload when
the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/wiki/pages", token, base_url
)
return (
json.dumps(data)
if isinstance(data, (dict, list))
else json.dumps({"raw": str(data)[:4000]})
)
[docs]
async def gitea_get_wiki_page(
owner: str,
repo: str,
page_name: str,
ctx: ToolContext | None = None,
) -> str:
"""Fetch the content of a single wiki page by title or slug.
Backs the ``gitea_get_wiki_page`` tool. Resolves credentials via
``_get_credentials``, URL-encodes the page name (so titles with spaces or
slashes survive), and requests ``GET /repos/{owner}/{repo}/wiki/page/{name}``
through ``_gitea_request``, returning the raw page object (wrapping a
non-JSON response in a truncated ``raw`` field). Performs HTTP and a
Redis-backed credential read only.
Registered as the ``gitea_get_wiki_page`` handler in this module's ``TOOLS``
list and dispatched by name from the inference worker's tool loop; not
called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
page_name: Wiki page title or slug to fetch.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the wiki page object, or a truncated raw payload when
the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
enc = quote(page_name, safe="")
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/wiki/page/{enc}", token, base_url
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"raw": str(data)[:8000]})
)
[docs]
async def gitea_list_actions_tasks(
owner: str,
repo: str,
page: int = 1,
limit: int = 20,
ctx: ToolContext | None = None,
) -> str:
"""List Gitea Actions workflow runs (tasks) for a repository.
Backs the ``gitea_list_actions_tasks`` tool. Resolves credentials via
``_get_credentials`` and pages ``GET /repos/{owner}/{repo}/actions/tasks``
through ``_gitea_request``. Because the exact response shape varies across
Gitea versions, this returns the raw payload as-is (wrapping a non-dict
response in a truncated ``raw`` field) rather than projecting fixed fields.
Performs HTTP and a Redis-backed credential read only; no writes.
Registered as the ``gitea_list_actions_tasks`` handler in this module's
``TOOLS`` list and dispatched by name from the inference worker's tool loop;
not called directly elsewhere in the repo.
Args:
owner: Repository owner (user or organization).
repo: Repository name.
page: 1-based page number to fetch.
limit: Results per page; values above 50 are clamped to 50.
ctx: The active ``ToolContext`` supplying user credentials.
Returns:
A JSON string with the raw Actions tasks payload, or a truncated raw
payload when the response is not structured.
"""
token, base_url = await _get_credentials(ctx)
params = {"page": page, "limit": min(limit, 50)}
data = await _gitea_request(
"GET", f"/repos/{owner}/{repo}/actions/tasks", token, base_url, params=params
)
return (
json.dumps(data)
if isinstance(data, dict)
else json.dumps({"raw": str(data)[:8000]})
)
# ---------------------------------------------------------------------------
# Tool registration (multi-tool format)
# ---------------------------------------------------------------------------
TOOLS = [
{
"name": "gitea_list_repos",
"description": "List the authenticated user's Gitea repositories. Supports custom base URLs for self-hosted instances.",
"parameters": {
"type": "object",
"properties": {
"page": {"type": "integer", "description": "Page number"},
"limit": {
"type": "integer",
"description": "Results per page (max 50)",
},
},
},
"handler": gitea_list_repos,
},
{
"name": "gitea_search_repos",
"description": "Search Gitea repositories by keyword.",
"parameters": {
"type": "object",
"properties": {
"q": {"type": "string", "description": "Search keyword"},
"sort": {
"type": "string",
"enum": [
"alpha",
"created",
"updated",
"size",
"stars",
"forks",
"id",
],
"description": "Sort order",
},
"order": {
"type": "string",
"enum": ["asc", "desc"],
"description": "Sort direction",
},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
"required": ["q"],
},
"handler": gitea_search_repos,
},
{
"name": "gitea_get_repo",
"description": "Get details of a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
},
"required": ["owner", "repo"],
},
"handler": gitea_get_repo,
},
{
"name": "gitea_create_repo",
"description": "Create a new Gitea repository for the authenticated user.",
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Repository name"},
"description": {
"type": "string",
"description": "Repository description",
},
"private": {
"type": "boolean",
"description": "Whether the repo is private",
},
"auto_init": {
"type": "boolean",
"description": "Initialize with README",
},
},
"required": ["name"],
},
"handler": gitea_create_repo,
},
{
"name": "gitea_list_issues",
"description": "List issues on a Gitea repository with optional filters.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"state": {
"type": "string",
"enum": ["open", "closed", "all"],
"description": "Filter by state",
},
"labels": {
"type": "string",
"description": "Comma-separated label names",
},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_issues,
},
{
"name": "gitea_get_issue",
"description": "Get a single issue by index.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"index": {"type": "integer", "description": "Issue number"},
},
"required": ["owner", "repo", "index"],
},
"handler": gitea_get_issue,
},
{
"name": "gitea_create_issue",
"description": "Create a new issue on a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"title": {"type": "string", "description": "Issue title"},
"body": {"type": "string", "description": "Issue body (Markdown)"},
"labels": {
"type": "array",
"items": {"type": "integer"},
"description": "Label IDs",
},
"assignees": {
"type": "array",
"items": {"type": "string"},
"description": "Assignee usernames",
},
},
"required": ["owner", "repo", "title"],
},
"handler": gitea_create_issue,
},
{
"name": "gitea_update_issue",
"description": "Update an existing issue (title, body, state).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"index": {"type": "integer", "description": "Issue number"},
"title": {"type": "string", "description": "New title"},
"body": {"type": "string", "description": "New body"},
"state": {
"type": "string",
"enum": ["open", "closed"],
"description": "New state",
},
},
"required": ["owner", "repo", "index"],
},
"handler": gitea_update_issue,
},
{
"name": "gitea_list_pull_requests",
"description": "List pull requests on a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"state": {
"type": "string",
"enum": ["open", "closed", "all"],
"description": "Filter by state",
},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_pull_requests,
},
{
"name": "gitea_get_pull_request",
"description": "Get details of a specific pull request.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"index": {"type": "integer", "description": "Pull request number"},
},
"required": ["owner", "repo", "index"],
},
"handler": gitea_get_pull_request,
},
{
"name": "gitea_create_pull_request",
"description": "Create a new pull request.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"title": {"type": "string", "description": "PR title"},
"head": {
"type": "string",
"description": "Head branch (or owner:branch for forks)",
},
"base": {
"type": "string",
"description": "Base branch (default: main)",
},
"body": {"type": "string", "description": "PR body (Markdown)"},
},
"required": ["owner", "repo", "title", "head"],
},
"handler": gitea_create_pull_request,
},
{
"name": "gitea_merge_pull_request",
"description": "Merge a pull request. merge_style: merge, rebase, rebase-merge, squash.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"index": {"type": "integer", "description": "Pull request number"},
"merge_style": {
"type": "string",
"enum": ["merge", "rebase", "rebase-merge", "squash"],
"description": "Merge method",
},
"delete_branch_after_merge": {
"type": "boolean",
"description": "Delete head branch after merge",
},
},
"required": ["owner", "repo", "index"],
},
"handler": gitea_merge_pull_request,
},
{
"name": "gitea_get_file",
"description": "Get the contents of a file or directory listing from a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"path": {"type": "string", "description": "File or directory path"},
"ref": {"type": "string", "description": "Branch, tag, or commit SHA"},
},
"required": ["owner", "repo", "path"],
},
"handler": gitea_get_file,
},
{
"name": "gitea_list_commits",
"description": "List commits in a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"sha": {"type": "string", "description": "Branch or SHA to list from"},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_commits,
},
{
"name": "gitea_get_commit",
"description": "Get a single commit by SHA.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"sha": {"type": "string", "description": "Commit SHA or ref"},
},
"required": ["owner", "repo", "sha"],
},
"handler": gitea_get_commit,
},
{
"name": "gitea_list_notifications",
"description": "List the user's Gitea notifications (issues, PRs, etc.).",
"parameters": {
"type": "object",
"properties": {
"all_notifications": {
"type": "boolean",
"description": "Include read notifications",
},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
},
"handler": gitea_list_notifications,
},
{
"name": "gitea_star_repo",
"description": "Star or unstar a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"star": {
"type": "boolean",
"description": "True to star, false to unstar",
},
},
"required": ["owner", "repo"],
},
"handler": gitea_star_repo,
},
{
"name": "gitea_list_branches",
"description": "List branches in a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"page": {"type": "integer", "description": "Page number"},
"limit": {"type": "integer", "description": "Results per page"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_branches,
},
{
"name": "gitea_create_repo_from_template",
"description": "Create a new repository from a template repository.",
"parameters": {
"type": "object",
"properties": {
"template_owner": {
"type": "string",
"description": "Template repo owner",
},
"template_repo": {
"type": "string",
"description": "Template repo name",
},
"name": {"type": "string", "description": "Name for the new repo"},
"description": {"type": "string", "description": "Description"},
"private": {"type": "boolean", "description": "Whether private"},
"git_content": {
"type": "boolean",
"description": "Include git content from template",
},
},
"required": ["template_owner", "template_repo", "name"],
},
"handler": gitea_create_repo_from_template,
},
{
"name": "gitea_compare_commits",
"description": "Compare two refs or commits (e.g. main...feature) and list commits/files.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string", "description": "Repository owner"},
"repo": {"type": "string", "description": "Repository name"},
"basehead": {
"type": "string",
"description": "base...head (Gitea compare syntax)",
},
},
"required": ["owner", "repo", "basehead"],
},
"handler": gitea_compare_commits,
},
{
"name": "gitea_list_tags",
"description": "List tags in a Gitea repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"page": {"type": "integer"},
"limit": {"type": "integer"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_tags,
},
{
"name": "gitea_create_tag",
"description": "Create a tag on a ref (requires write:repository on the instance).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"tag_name": {"type": "string"},
"target": {"type": "string", "description": "Branch or commit SHA"},
"message": {
"type": "string",
"description": "Optional annotated tag message",
},
},
"required": ["owner", "repo", "tag_name", "target"],
},
"handler": gitea_create_tag,
},
{
"name": "gitea_list_milestones",
"description": "List milestones (requires read:issue on many Gitea servers).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"state": {"type": "string", "enum": ["open", "closed", "all"]},
"page": {"type": "integer"},
"limit": {"type": "integer"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_milestones,
},
{
"name": "gitea_get_milestone",
"description": "Get a milestone by id.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"milestone_id": {"type": "integer"},
},
"required": ["owner", "repo", "milestone_id"],
},
"handler": gitea_get_milestone,
},
{
"name": "gitea_create_milestone",
"description": "Create a milestone.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"title": {"type": "string"},
"description": {"type": "string"},
"due_on": {"type": "string", "description": "ISO 8601 datetime"},
},
"required": ["owner", "repo", "title"],
},
"handler": gitea_create_milestone,
},
{
"name": "gitea_update_milestone",
"description": "Update milestone fields.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"milestone_id": {"type": "integer"},
"title": {"type": "string"},
"description": {"type": "string"},
"state": {"type": "string", "enum": ["open", "closed"]},
"due_on": {"type": "string"},
},
"required": ["owner", "repo", "milestone_id"],
},
"handler": gitea_update_milestone,
},
{
"name": "gitea_delete_milestone",
"description": "Delete a milestone by id.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"milestone_id": {"type": "integer"},
},
"required": ["owner", "repo", "milestone_id"],
},
"handler": gitea_delete_milestone,
},
{
"name": "gitea_list_labels",
"description": "List repository labels (requires read:issue on many servers).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"page": {"type": "integer"},
"limit": {"type": "integer"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_labels,
},
{
"name": "gitea_create_label",
"description": "Create a label (write:issue typically).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"name": {"type": "string"},
"color": {"type": "string", "description": "Hex color without #"},
"description": {"type": "string"},
},
"required": ["owner", "repo", "name", "color"],
},
"handler": gitea_create_label,
},
{
"name": "gitea_list_issue_comments",
"description": "List comments on an issue or PR (same index in Gitea).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"index": {"type": "integer"},
"page": {"type": "integer"},
"limit": {"type": "integer"},
},
"required": ["owner", "repo", "index"],
},
"handler": gitea_list_issue_comments,
},
{
"name": "gitea_create_issue_comment",
"description": "Post a comment on an issue or PR.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"index": {"type": "integer"},
"body": {"type": "string"},
},
"required": ["owner", "repo", "index", "body"],
},
"handler": gitea_create_issue_comment,
},
{
"name": "gitea_list_pull_comments",
"description": "Alias of issue comments listing for pull request threads.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"index": {"type": "integer", "description": "Pull request number"},
"page": {"type": "integer"},
"limit": {"type": "integer"},
},
"required": ["owner", "repo", "index"],
},
"handler": gitea_list_pull_comments,
},
{
"name": "gitea_list_releases",
"description": "List releases for a repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"draft": {"type": "boolean"},
"pre_release": {"type": "boolean"},
"page": {"type": "integer"},
"limit": {"type": "integer"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_releases,
},
{
"name": "gitea_get_release",
"description": "Get a release by numeric id.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"release_id": {"type": "integer"},
},
"required": ["owner", "repo", "release_id"],
},
"handler": gitea_get_release,
},
{
"name": "gitea_create_release",
"description": "Create a release from a tag (requires write permissions).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"tag_name": {"type": "string"},
"name": {"type": "string"},
"body": {"type": "string"},
"target_commitish": {"type": "string"},
"draft": {"type": "boolean"},
"prerelease": {"type": "boolean"},
},
"required": ["owner", "repo", "tag_name"],
},
"handler": gitea_create_release,
},
{
"name": "gitea_delete_release",
"description": "Delete a release by id.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"release_id": {"type": "integer"},
},
"required": ["owner", "repo", "release_id"],
},
"handler": gitea_delete_release,
},
{
"name": "gitea_fork_repo",
"description": "Fork a repository into the user account or an organization.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"organization": {
"type": "string",
"description": "Optional org to fork into",
},
"name": {"type": "string", "description": "Optional new repo name"},
},
"required": ["owner", "repo"],
},
"handler": gitea_fork_repo,
},
{
"name": "gitea_create_branch",
"description": "Create a new branch from an existing branch or ref (write:repository).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"new_branch_name": {"type": "string"},
"old_branch_name": {"type": "string"},
"old_ref_name": {
"type": "string",
"description": "Alternative to old_branch_name",
},
},
"required": ["owner", "repo", "new_branch_name"],
},
"handler": gitea_create_branch,
},
{
"name": "gitea_delete_branch",
"description": "Delete a branch (write:repository).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"branch": {"type": "string"},
},
"required": ["owner", "repo", "branch"],
},
"handler": gitea_delete_branch,
},
{
"name": "gitea_list_wiki_pages",
"description": "List wiki pages (404 if wiki disabled).",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_wiki_pages,
},
{
"name": "gitea_get_wiki_page",
"description": "Get wiki page content by page name.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"page_name": {"type": "string"},
},
"required": ["owner", "repo", "page_name"],
},
"handler": gitea_get_wiki_page,
},
{
"name": "gitea_list_actions_tasks",
"description": "List Gitea Actions workflow runs for a repository.",
"parameters": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"page": {"type": "integer"},
"limit": {"type": "integer"},
},
"required": ["owner", "repo"],
},
"handler": gitea_list_actions_tasks,
},
]