Metadata-Version: 2.5
Name: nagents-channel-github-issues
Version: 0.1.0
Summary: GitHub Issues REST polling channel for Nagents
Project-URL: Repository, https://github.com/abi-jey/nagents-channel-github-issues
Project-URL: Issues, https://github.com/abi-jey/nagents-channel-github-issues/issues
Author: Abbas Jafari
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: aiohttp<4,>=3.11
Requires-Dist: nagents<0.12,>=0.11.0
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: mypy>=1.15; extra == 'dev'
Requires-Dist: packaging>=24; extra == 'dev'
Requires-Dist: pre-commit>=4; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.25; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.11; extra == 'dev'
Requires-Dist: twine>=6; extra == 'dev'
Description-Content-Type: text/markdown

# nagents-channel-github-issues

An installable, typed GitHub Issues connector for the public **Nagents Channel
API**. It polls the GitHub REST API for issue comments and issue lifecycle
events, and exposes explicit tools to comment on issues and manage them. Works
against GitHub.com and GitHub Enterprise Server. Python 3.11+; MIT licensed.

The connector only receives and sends through the channel API. It never opens a
session database, chooses agent sessions, or auto-replies to events. Session
routing stays with the application hosting the connector.

## Install

```sh
python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
python -m pip install 'nagents>=0.11.0,<0.12' nagents-channel-github-issues
```

Set a token in the environment (never in source files):

```sh
export GITHUB_TOKEN=...  # fine-grained or classic token with repo read/write
```

The connector sends `Authorization: Bearer <token>`, `Accept:
application/vnd.github+json`, `X-GitHub-Api-Version: 2022-11-28`, and a
`User-Agent` on every request. Tokens are read from configuration or the
environment only and are never logged or embedded in errors.

## Use

```python
import asyncio
import os
from pathlib import Path

from nagents import Agent, Provider, ProviderType, SessionManager
from nagents_channel_github_issues import GitHubIssuesChannel


async def main() -> None:
    agent = Agent(
        Provider(
            ProviderType.OPENAI_COMPATIBLE,
            api_key=os.environ["OPENAI_API_KEY"],
            model=os.environ["OPENAI_MODEL"],
        ),
        SessionManager(Path("agent.db")),
        system_prompt="Coordinate my GitHub issues and decide when to reply.",
    )
    agent.add_channel(
        GitHubIssuesChannel(
            os.environ["GITHUB_TOKEN"],
            repos=["abi-jey/nagents"],
            include_self=False,
        )
    )
    try:
        await agent.listen(session_id="github-issues")
    finally:
        await agent.close()


asyncio.run(main())
```

### Configuration-driven discovery

```python
from nagents import load_channel

channel = load_channel(
    "github-issues",
    {
        "name": "project-issues",
        "token_env": "GITHUB_TOKEN",
        "repos": ["abi-jey/nagents"],
        "poll_interval": 30,
        "lookback": 300,
    },
)
```

The `nagents.channels` entry point is `github-issues` and exposes a
`ChannelPlugin` whose `token` property is `writeOnly`, so management clients
store it privately and never echo saved values.

## Received events

Each inbound event becomes one `ChannelMessage`:

| Field | Value |
| --- | --- |
| `conversation_id` | `owner/repo#issue_number` |
| `message_id` | `comment:<id>` or `issue:<number>:<updated_at>` |
| `reply_to` | issue number |
| `sender_id` | `user.login` |
| `text` | comment or issue body |
| `sent_at` | epoch seconds of `created_at` |
| `event_type` | `comment` or `issue` |
| `metadata` | `repo`, `number`, `event`, `html_url`, `state`, `labels`, `comment_id` |

Pull-request entries are skipped. Comments on pull requests are skipped when the
repository's issue polling has identified the number as a pull request. Events
with empty bodies and unknown event types are skipped.

Filters:

- `allowed_users` admits only the listed logins (case-insensitive). Empty admits
  everyone.
- `include_self` (default `false`) skips events authored by the authenticated
  user.

## Actions

| Action | Arguments |
| --- | --- |
| `create_issue` | `repo`, `title`, optional `body`, optional `labels` |
| `close_issue` | `destination` (`owner/repo#number`) |
| `reopen_issue` | `destination` |
| `add_labels` | `destination`, `labels` |
| `edit_comment` | `repo`, `comment_id`, `body` |

Arguments are validated strictly against the advertised JSON Schemas. Writes are
sent once and never retried; a lost response raises `ChannelError` with
`outcome_unknown=True`, and rate limits surface `retry_after`.

## Polling behaviour

- Repo-level issue comments (`/repos/{owner}/{repo}/issues/comments`) and issues
  (`/repos/{owner}/{repo}/issues`) are polled with `sort=created`,
  `direction=asc`, `since`, and `per_page=100`.
- Pagination follows the `Link` header `rel="next"`; page parameters are never
  constructed by hand. Only same-origin next links are followed.
- Conditional requests send `If-None-Match`; a `304 Not Modified` costs no
  primary rate limit and preserves the cursor.
- `retry-after` and `x-ratelimit-reset` drive backoff; transient failures in one
  repository never stop the others.
- In-memory cursors, ETags, and deduplication can be persisted with
  `state_path` for restarts.

### Enterprise Server

Set `base_url` to the enterprise REST base:

```python
GitHubIssuesChannel(token, base_url="https://github.example.com/api/v3", repos=["team/repo"])
```

## Develop

```sh
python -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/python -m pytest
PATH="$PWD/.venv/bin:$PATH" pre-commit run --all-files
```

All tests are offline: they use an aiohttp test server that emulates the REST
endpoints, including pagination, conditional requests, and rate limiting.
