Metadata-Version: 2.5
Name: nodus-a2a-wire
Version: 0.1.0
Summary: A2A 1.0.0 (LF) wire-protocol adapter: agent cards, HTTP+JSON transport, handler dispatch
Project-URL: Homepage, https://github.com/Masterplanner25/nodus-a2a-wire
Project-URL: Repository, https://github.com/Masterplanner25/nodus-a2a-wire
Project-URL: Changelog, https://github.com/Masterplanner25/nodus-a2a-wire/blob/main/CHANGELOG.md
Author: Shawn Knight
License: MIT License
        
        Copyright (c) 2026 Masterplanner25
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: nodus-lang>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: flask
Requires-Dist: flask>=3.0; extra == 'flask'
Description-Content-Type: text/markdown

# nodus-a2a-wire

**A2A 1.0.0 (Linux Foundation) wire-protocol adapter for the [Nodus](https://github.com/Masterplanner25/Nodus) scripting language.**

`nodus-a2a-wire` exposes `std:tool`-registered tools from a Nodus runtime as an
A2A message-only agent over HTTP+JSON/REST — agent cards, transport, and handler
dispatch.

```bash
pip install nodus-a2a-wire
```

## Not to be confused with `nodus-a2a`

Two different packages, and the names are close:

| | What it is |
|---|---|
| **`nodus-a2a-wire`** (this one) | the **wire protocol** — `A2AHttpServer`, agent cards, HTTP+JSON transport |
| [`nodus-a2a`](https://pypi.org/project/nodus-a2a/) | **coordination primitives** — `AgentRegistry`, `AgentCoordinator`, `DeadLetterService`, `StuckRunWatchdog` |

They share no code and neither depends on the other. **Both can be installed
together**: this package's Python module is `nodus_a2a_wire`, the other's is
`nodus_a2a`.

Until 0.1.0 was published this project declared the distribution name
`nodus-a2a` *and* the module name `nodus_a2a`, which is why it could not be
published at all — and why installing it alongside the coordinator would have
silently replaced that package's API. See
[nodus-lang#477](https://github.com/Masterplanner25/Nodus/issues/477).

## Requirements

**No runtime dependencies**, including nodus-lang. A host constructs
`A2AHttpServer` in Python and wires it to their own `NodusRuntime.tool_registry`,
so the runtime arrives by injection rather than by import.

```bash
pip install nodus-a2a-wire[flask]   # optional alternative transport backend
```

---

## Production authentication warning

**Without a `token_validator`, the server runs in dev mode and accepts all requests.**
Production deployments must configure a validator:

```python
config = ServerConfig(
    ...
    token_validator=lambda token: token == os.environ["MY_SECRET_TOKEN"],
)
```

Do not expose a nodus-a2a-wire server to the internet without configuring `token_validator`.

---

## v0.1 scope

This is a message-only implementation (D5 decision). The server **never creates A2A Tasks**.
All task-management operations return HTTP 501. Deferred to v0.2+:
- Task lifecycle and state machine
- SSE streaming (`SendStreamingMessage`)
- Push notification webhooks
- JSON-RPC and gRPC bindings
- OAuth 2.0 / OIDC / mTLS auth

See `docs/design/05-deferred-features.md` for the full inventory.

---

---

## Quick start

```python
from nodus.runtime.embedding import NodusRuntime
from nodus_a2a_wire import A2AHttpServer, ServerConfig

# 1. Create a NodusRuntime and register tools via tool_registry
runtime = NodusRuntime()
runtime.tool_registry.register({
    "name": "myapp.greet",           # must use dotted namespace: "prefix.name"
    "description": "Greet someone by name",
    "handler": lambda args: f"Hello, {args.get('name', 'world')}!",
})

# 2. Configure the server
config = ServerConfig(
    base_url="https://myagent.example.com",
    agent_name="My Greeter Agent",
    agent_description="Greets people by name via A2A",
)

# 3. Build the tool list and start the server
tools = runtime.tool_registry.list_tools()
tool_names = [t["name"] for t in tools if not t.get("deprecated")]

server = A2AHttpServer(
    config=config,
    invoke=runtime.tool_registry.invoke,
    tool_names=tool_names,
    tools=tools,
)
server.serve()  # blocks; use serve_in_thread() for background use
```

### Calling the agent

Send a tool-call envelope in a DataPart:

```http
POST /message:send HTTP/1.1
Content-Type: application/a2a+json
A2A-Version: 1.0

{
  "message": {
    "messageId": "msg-001",
    "role": "ROLE_USER",
    "parts": [{"data": {"tool": "myapp.greet", "args": {"name": "Alice"}}}]
  }
}
```

Response:

```json
{
  "message": {
    "messageId": "...",
    "role": "ROLE_AGENT",
    "contextId": "...",
    "parts": [{"text": "Hello, Alice!", "mediaType": "text/plain"}]
  }
}
```

### Single-tool shortcut

If the agent has exactly one tool registered, any `Message` (even a plain `TextPart`)
dispatches to that tool with empty args:

```json
{"message": {"messageId": "m1", "role": "ROLE_USER", "parts": [{"text": "hello"}]}}
```

### Agent Card discovery

The Agent Card is served at `/.well-known/agent-card.json` (A2A 1.0 URI).
No authentication required.

---

## Authentication

```python
config = ServerConfig(
    ...
    token_validator=lambda token: token == "my-secret-token",
)
```

Without a `token_validator`, the server runs in dev mode and accepts all requests.
**Production deployments must configure a validator.**

---

## Part type dispatch

Tool return values are mapped to A2A Part variants automatically:

| Python type | A2A Part | `mediaType` |
|-------------|----------|-------------|
| `str` | TextPart | `text/plain` |
| `bytes` | RawPart (base64) | `application/octet-stream` |
| dict / list / int / float / bool / None | DataPart | `application/json` |

The `url` Part variant is not used in automatic dispatch (reserved for v0.2+).

---

## Error handling

Tool exceptions are returned as an error DataPart in an HTTP 200 response:

```json
{
  "message": {
    "role": "ROLE_AGENT",
    "parts": [{"data": {"error": "tool exploded", "type": "RuntimeError"},
               "mediaType": "application/json"}]
  }
}
```

Protocol errors (malformed JSON, invalid A2A-Version, missing auth) return
HTTP 4xx with a structured error body.

---

## Design notes

### v0.1 is message-only (D5)

The server never creates or persists A2A Tasks.  All task-management operations
(`GetTask`, `ListTasks`, `CancelTask`, streaming, push notifications) return
HTTP 501 `UnsupportedOperationError`.  The Agent Card declares:

```json
"capabilities": {"streaming": false, "pushNotifications": false, "extendedAgentCard": false}
```

### v0.2 Task lifecycle: D6 inversion warning

A2A `INPUT_REQUIRED` / `AUTH_REQUIRED` are **park-and-resume** states — the
opposite of nodus-mcp's no-thread-parks rule.  When implementing Task lifecycle
in v0.2, do **not** import the nodus-mcp no-park assertion.  See
`docs/design/05-deferred-features.md §2` for the full inversion note.

---

## CLI

```
python -m nodus_a2a_wire --version
python -m nodus_a2a_wire serve --name "My Agent" --description "..." --port 8080
```

The `serve` command starts a server with no tools — useful for smoke-testing
network connectivity and verifying the Agent Card format.

---

## Wire format

- **Spec:** A2A 1.0.0 / `lf.a2a.v1` (proto package)
- **Transport:** HTTP+JSON/REST only (`protocolBinding: "HTTP+JSON"`)
- **Content-Type:** `application/a2a+json`
- **Discovery:** `/.well-known/agent-card.json`
- **Version negotiation:** `A2A-Version` request header; mismatch → HTTP 400
- **Codec:** proto `snake_case` ↔ wire `camelCase` (ProtoJSON)
- **Signing:** unsigned (v0.1); JWS signing planned for v0.2

---

## Deferred features (v0.2+)

Task lifecycle and state machine, streaming (`SendStreamingMessage`),
push notification webhooks, Agent Card signing (JWS/RFC 7515), extended
authenticated card, JSON-RPC binding, gRPC binding, OAuth2/OIDC/mTLS,
tenant routing, 0.3 wire-dialect compatibility.

See `docs/design/05-deferred-features.md` for the full inventory.
