asgard-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. asgard_sdk-0.1.0/.github/workflows/ci.yml +30 -0
  2. asgard_sdk-0.1.0/.github/workflows/publish.yml +40 -0
  3. asgard_sdk-0.1.0/.gitignore +10 -0
  4. asgard_sdk-0.1.0/.python-version +1 -0
  5. asgard_sdk-0.1.0/CLAUDE.md +48 -0
  6. asgard_sdk-0.1.0/PKG-INFO +174 -0
  7. asgard_sdk-0.1.0/README.md +160 -0
  8. asgard_sdk-0.1.0/docs/superpowers/plans/2026-05-07-asgard-cli.md +1397 -0
  9. asgard_sdk-0.1.0/docs/superpowers/plans/2026-05-07-asgard-python-sdk.md +1763 -0
  10. asgard_sdk-0.1.0/docs/superpowers/specs/2026-05-07-asgard-python-sdk-design.md +312 -0
  11. asgard_sdk-0.1.0/docs/superpowers/specs/2026-05-07-cli-design.md +156 -0
  12. asgard_sdk-0.1.0/main.py +6 -0
  13. asgard_sdk-0.1.0/pyproject.toml +38 -0
  14. asgard_sdk-0.1.0/src/asgard/__init__.py +37 -0
  15. asgard_sdk-0.1.0/src/asgard/_http.py +107 -0
  16. asgard_sdk-0.1.0/src/asgard/bot_provider.py +64 -0
  17. asgard_sdk-0.1.0/src/asgard/cli.py +376 -0
  18. asgard_sdk-0.1.0/src/asgard/config.py +7 -0
  19. asgard_sdk-0.1.0/src/asgard/function_agent.py +53 -0
  20. asgard_sdk-0.1.0/src/asgard/models/__init__.py +43 -0
  21. asgard_sdk-0.1.0/src/asgard/models/_base.py +10 -0
  22. asgard_sdk-0.1.0/src/asgard/models/consent.py +28 -0
  23. asgard_sdk-0.1.0/src/asgard/models/errors.py +28 -0
  24. asgard_sdk-0.1.0/src/asgard/models/message.py +46 -0
  25. asgard_sdk-0.1.0/src/asgard/models/sse_event.py +125 -0
  26. asgard_sdk-0.1.0/src/asgard/models/template.py +56 -0
  27. asgard_sdk-0.1.0/src/asgard/streaming.py +33 -0
  28. asgard_sdk-0.1.0/tests/__init__.py +0 -0
  29. asgard_sdk-0.1.0/tests/models/__init__.py +0 -0
  30. asgard_sdk-0.1.0/tests/models/test_consent.py +44 -0
  31. asgard_sdk-0.1.0/tests/models/test_errors.py +36 -0
  32. asgard_sdk-0.1.0/tests/models/test_message.py +50 -0
  33. asgard_sdk-0.1.0/tests/models/test_sse_event.py +115 -0
  34. asgard_sdk-0.1.0/tests/models/test_template.py +28 -0
  35. asgard_sdk-0.1.0/tests/test_bot_provider.py +102 -0
  36. asgard_sdk-0.1.0/tests/test_cli.py +298 -0
  37. asgard_sdk-0.1.0/tests/test_config.py +13 -0
  38. asgard_sdk-0.1.0/tests/test_function_agent.py +46 -0
  39. asgard_sdk-0.1.0/tests/test_http.py +97 -0
  40. asgard_sdk-0.1.0/tests/test_streaming.py +85 -0
  41. asgard_sdk-0.1.0/uv.lock +357 -0
@@ -0,0 +1,30 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ lint-and-test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+
13
+ - name: Install uv
14
+ uses: astral-sh/setup-uv@v5
15
+ with:
16
+ enable-cache: true
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version-file: .python-version
22
+
23
+ - name: Install dependencies
24
+ run: uv pip install -e ".[dev]" --system
25
+
26
+ - name: Lint (ruff)
27
+ run: ruff check .
28
+
29
+ - name: Test
30
+ run: pytest
@@ -0,0 +1,40 @@
1
+ name: publish.yml
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ with:
18
+ fetch-depth: 0
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v5
22
+ with:
23
+ enable-cache: true
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v5
27
+ with:
28
+ python-version-file: .python-version
29
+
30
+ - name: Build package
31
+ run: uv build
32
+
33
+ - name: Publish to PyPI
34
+ uses: pypa/gh-action-pypi-publish@release/v1
35
+
36
+ - name: Create GitHub Release
37
+ uses: softprops/action-gh-release@v2
38
+ with:
39
+ generate_release_notes: true
40
+ files: dist/*
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,48 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ This project uses `uv` for dependency management and `pytest` for testing.
8
+
9
+ ```bash
10
+ # Install dependencies (dev)
11
+ uv pip install -e ".[dev]"
12
+
13
+ # Run all tests
14
+ uv run pytest
15
+
16
+ # Run a single test file
17
+ uv run pytest tests/test_bot_provider.py
18
+
19
+ # Run a single test
20
+ uv run pytest tests/test_bot_provider.py::test_send_message_returns_reply
21
+
22
+ # Run the CLI
23
+ uv run asgard-cli --help
24
+
25
+ # Build distribution
26
+ uv build
27
+ ```
28
+
29
+ There is no linter configured. Python version is pinned to 3.14 (see `.python-version`).
30
+
31
+ ## Architecture
32
+
33
+ The SDK exposes two top-level clients, both using a shared `HttpCore` transport layer:
34
+
35
+ - **`BotProviderClient`** ([src/asgard/bot_provider.py](src/asgard/bot_provider.py)) — conversational bot interface. Supports REST (`send_message`) and SSE streaming (`stream`), plus blob uploads.
36
+ - **`FunctionAgentClient`** ([src/asgard/function_agent.py](src/asgard/function_agent.py)) — one-shot function agent. Supports JSON trigger and multipart form trigger.
37
+
38
+ **Transport layer** ([src/asgard/_http.py](src/asgard/_http.py)): `HttpCore` builds URLs as `{host}/ns/{namespace}/bot-provider/{name}/{endpoint}`, injects `X-API-KEY` auth, and raises `AsgardError` on non-2xx responses.
39
+
40
+ **SSE streaming** ([src/asgard/streaming.py](src/asgard/streaming.py)): `BotProviderStream` is a context manager and iterator that parses `data:` lines from the response, yields `GenericBotSseEvent` objects, stops on `RUN_DONE`, and raises `AsgardStreamError` on `RUN_ERROR`.
41
+
42
+ **Models** ([src/asgard/models/](src/asgard/models/)): All Pydantic models extend `CamelModel` (from `_base.py`), which uses `alias_generator=to_camel` so Python snake_case fields serialize/deserialize as camelCase JSON automatically. Call `.model_dump(by_alias=True)` when sending to the API.
43
+
44
+ **SSE event structure**: Every SSE event is a `GenericBotSseEvent` with an `event_type` (`SseEventType` enum) and a `fact` (`GenericBotSseEventFact`). The fact is a union object — only the field matching the event type is populated (e.g., `event.fact.message_delta` for `MESSAGE_DELTA` events).
45
+
46
+ **CLI** ([src/asgard/cli.py](src/asgard/cli.py)): Interactive REPL (`--agent bot`) or one-shot runner (`--agent function`). `BotSession` dataclass tracks channel ID, transport mode, debug flag, and attached blob IDs across REPL turns. All config can come from env vars (`EDGE_SERVER_HOST`, `NAMESPACE`, `BOT_PROVIDER_NAME`, `BOT_PROVIDER_API_KEY`).
47
+
48
+ **Testing**: Tests use `respx` to mock HTTP requests for REST calls, and `httpx.MockTransport` for SSE streaming tests (since `respx` doesn't support streaming). The `http_client` constructor parameter on both clients accepts an injected `httpx.Client` for test isolation.
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.4
2
+ Name: asgard-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Asgard Core API
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: httpx>=0.27
7
+ Requires-Dist: pydantic>=2.0
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
10
+ Requires-Dist: pytest>=8.0; extra == 'dev'
11
+ Requires-Dist: respx>=0.21; extra == 'dev'
12
+ Requires-Dist: ruff>=0.9; extra == 'dev'
13
+ Description-Content-Type: text/markdown
14
+
15
+ # asgard-sdk-python
16
+
17
+ Python SDK for the Asgard Core API. Sync-first, with async extensibility in mind.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ uv add asgard-sdk-python
23
+ ```
24
+
25
+ Or for development:
26
+
27
+ ```bash
28
+ uv sync --extra dev
29
+ ```
30
+
31
+ ## Requirements
32
+
33
+ Python 3.11+
34
+
35
+ ## Quick Start
36
+
37
+ ### BotProviderClient
38
+
39
+ ```python
40
+ from asgard import BotProviderClient, GenericBotMessage
41
+
42
+ client = BotProviderClient(
43
+ host="http://localhost:8080",
44
+ namespace="my-ns",
45
+ bot_provider_name="my-bot",
46
+ api_key="secret",
47
+ )
48
+
49
+ # Send a message (REST)
50
+ message = GenericBotMessage(custom_channel_id="ch-1", custom_message_id="m-1", text="hello")
51
+ reply = client.send_message(message)
52
+ for m in reply.messages:
53
+ print(m.text)
54
+
55
+ # Stream a message (SSE)
56
+ with client.stream(message) as stream:
57
+ for event in stream:
58
+ if event.event_type.value == "asgard.message.delta":
59
+ print(event.fact.message_delta.text, end="", flush=True)
60
+
61
+ # Upload a blob
62
+ with open("image.png", "rb") as f:
63
+ blobs = client.upload_blob(custom_channel_id="ch-1", file=f, filename="image.png", mime="image/png")
64
+ ```
65
+
66
+ ### FunctionAgentClient
67
+
68
+ ```python
69
+ from asgard import FunctionAgentClient
70
+
71
+ client = FunctionAgentClient(
72
+ host="http://localhost:8080",
73
+ namespace="my-ns",
74
+ bot_provider_name="my-agent",
75
+ api_key="secret",
76
+ )
77
+
78
+ result = client.trigger_json({"key": "value"})
79
+
80
+ with open("data.csv", "rb") as f:
81
+ result = client.trigger_form({"key": "value"}, file=f, filename="data.csv", mime="text/csv")
82
+ ```
83
+
84
+ ## Environment Variables
85
+
86
+ | Variable | Description |
87
+ |----------|-------------|
88
+ | `EDGE_SERVER_HOST` | EdgeServer host URL |
89
+ | `NAMESPACE` | Asgard namespace |
90
+ | `BOT_PROVIDER_NAME` | Bot/agent name |
91
+ | `BOT_PROVIDER_API_KEY` | API key |
92
+
93
+ ## CLI
94
+
95
+ ```bash
96
+ # Bot interactive REPL (SSE by default)
97
+ asgard-cli --host http://localhost:8080 --namespace default --bot my-bot --apikey secret
98
+
99
+ # Switch to REST transport
100
+ asgard-cli --host ... --namespace ... --bot ... --apikey ... --transport rest
101
+
102
+ # Using env vars
103
+ export EDGE_SERVER_HOST=http://localhost:8080
104
+ export NAMESPACE=default
105
+ export BOT_PROVIDER_NAME=my-bot
106
+ export BOT_PROVIDER_API_KEY=secret
107
+ asgard-cli
108
+
109
+ # Function agent — JSON trigger
110
+ asgard-cli --agent function --json-trigger \
111
+ --trigger-payload '{"key": "value"}' \
112
+ --host ... --namespace ... --bot ... --apikey ...
113
+
114
+ # Function agent — Form trigger
115
+ asgard-cli --agent function --form-trigger \
116
+ --trigger-payload '{"key": "value"}' \
117
+ --form-file ./data.csv --form-mime text/csv \
118
+ --host ... --namespace ... --bot ... --apikey ...
119
+ ```
120
+
121
+ ### Bot REPL commands
122
+
123
+ | Command | Description |
124
+ |---------|-------------|
125
+ | `/help` | Show help |
126
+ | `/exit` / `/quit` | Exit |
127
+ | `/transport sse\|rest` | Switch transport |
128
+ | `/debug on\|off` | Toggle debug mode |
129
+ | `/blob <path> [mime]` | Upload and attach a blob |
130
+ | `/blobs` | Show attached blob IDs |
131
+ | `/clear-blobs` | Clear attached blobs |
132
+ | `/channel [id]` | Show or switch channel |
133
+ | `/reset [text]` | Send RESET_CHANNEL action |
134
+
135
+ ## Error Handling
136
+
137
+ ```python
138
+ from asgard import AsgardError, AsgardStreamError
139
+
140
+ try:
141
+ reply = client.send_message(message)
142
+ except AsgardError as e:
143
+ print(e.detail.message) # human-readable error
144
+ print(e.detail.code) # error code
145
+
146
+ try:
147
+ with client.stream(message) as stream:
148
+ for event in stream:
149
+ ...
150
+ except AsgardStreamError as e:
151
+ print(e.detail.message)
152
+ ```
153
+
154
+ ## Tool Call Consent
155
+
156
+ ```python
157
+ from asgard import GenericBotMessage, ToolCallConsentResponseItem, ToolCallConsentResult, PostBackAction
158
+
159
+ # After receiving a tool_call.consent event:
160
+ consent_items = [
161
+ ToolCallConsentResponseItem(
162
+ tool_call_id="tc-1",
163
+ result=ToolCallConsentResult.ALLOW_ONCE,
164
+ )
165
+ ]
166
+
167
+ response = GenericBotMessage(
168
+ custom_channel_id="ch-1",
169
+ custom_message_id="m-2",
170
+ action=PostBackAction.RESPONSE_TOOL_CALL_CONSENT,
171
+ tool_call_consents=consent_items,
172
+ )
173
+ reply = client.send_message(response)
174
+ ```
@@ -0,0 +1,160 @@
1
+ # asgard-sdk-python
2
+
3
+ Python SDK for the Asgard Core API. Sync-first, with async extensibility in mind.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ uv add asgard-sdk-python
9
+ ```
10
+
11
+ Or for development:
12
+
13
+ ```bash
14
+ uv sync --extra dev
15
+ ```
16
+
17
+ ## Requirements
18
+
19
+ Python 3.11+
20
+
21
+ ## Quick Start
22
+
23
+ ### BotProviderClient
24
+
25
+ ```python
26
+ from asgard import BotProviderClient, GenericBotMessage
27
+
28
+ client = BotProviderClient(
29
+ host="http://localhost:8080",
30
+ namespace="my-ns",
31
+ bot_provider_name="my-bot",
32
+ api_key="secret",
33
+ )
34
+
35
+ # Send a message (REST)
36
+ message = GenericBotMessage(custom_channel_id="ch-1", custom_message_id="m-1", text="hello")
37
+ reply = client.send_message(message)
38
+ for m in reply.messages:
39
+ print(m.text)
40
+
41
+ # Stream a message (SSE)
42
+ with client.stream(message) as stream:
43
+ for event in stream:
44
+ if event.event_type.value == "asgard.message.delta":
45
+ print(event.fact.message_delta.text, end="", flush=True)
46
+
47
+ # Upload a blob
48
+ with open("image.png", "rb") as f:
49
+ blobs = client.upload_blob(custom_channel_id="ch-1", file=f, filename="image.png", mime="image/png")
50
+ ```
51
+
52
+ ### FunctionAgentClient
53
+
54
+ ```python
55
+ from asgard import FunctionAgentClient
56
+
57
+ client = FunctionAgentClient(
58
+ host="http://localhost:8080",
59
+ namespace="my-ns",
60
+ bot_provider_name="my-agent",
61
+ api_key="secret",
62
+ )
63
+
64
+ result = client.trigger_json({"key": "value"})
65
+
66
+ with open("data.csv", "rb") as f:
67
+ result = client.trigger_form({"key": "value"}, file=f, filename="data.csv", mime="text/csv")
68
+ ```
69
+
70
+ ## Environment Variables
71
+
72
+ | Variable | Description |
73
+ |----------|-------------|
74
+ | `EDGE_SERVER_HOST` | EdgeServer host URL |
75
+ | `NAMESPACE` | Asgard namespace |
76
+ | `BOT_PROVIDER_NAME` | Bot/agent name |
77
+ | `BOT_PROVIDER_API_KEY` | API key |
78
+
79
+ ## CLI
80
+
81
+ ```bash
82
+ # Bot interactive REPL (SSE by default)
83
+ asgard-cli --host http://localhost:8080 --namespace default --bot my-bot --apikey secret
84
+
85
+ # Switch to REST transport
86
+ asgard-cli --host ... --namespace ... --bot ... --apikey ... --transport rest
87
+
88
+ # Using env vars
89
+ export EDGE_SERVER_HOST=http://localhost:8080
90
+ export NAMESPACE=default
91
+ export BOT_PROVIDER_NAME=my-bot
92
+ export BOT_PROVIDER_API_KEY=secret
93
+ asgard-cli
94
+
95
+ # Function agent — JSON trigger
96
+ asgard-cli --agent function --json-trigger \
97
+ --trigger-payload '{"key": "value"}' \
98
+ --host ... --namespace ... --bot ... --apikey ...
99
+
100
+ # Function agent — Form trigger
101
+ asgard-cli --agent function --form-trigger \
102
+ --trigger-payload '{"key": "value"}' \
103
+ --form-file ./data.csv --form-mime text/csv \
104
+ --host ... --namespace ... --bot ... --apikey ...
105
+ ```
106
+
107
+ ### Bot REPL commands
108
+
109
+ | Command | Description |
110
+ |---------|-------------|
111
+ | `/help` | Show help |
112
+ | `/exit` / `/quit` | Exit |
113
+ | `/transport sse\|rest` | Switch transport |
114
+ | `/debug on\|off` | Toggle debug mode |
115
+ | `/blob <path> [mime]` | Upload and attach a blob |
116
+ | `/blobs` | Show attached blob IDs |
117
+ | `/clear-blobs` | Clear attached blobs |
118
+ | `/channel [id]` | Show or switch channel |
119
+ | `/reset [text]` | Send RESET_CHANNEL action |
120
+
121
+ ## Error Handling
122
+
123
+ ```python
124
+ from asgard import AsgardError, AsgardStreamError
125
+
126
+ try:
127
+ reply = client.send_message(message)
128
+ except AsgardError as e:
129
+ print(e.detail.message) # human-readable error
130
+ print(e.detail.code) # error code
131
+
132
+ try:
133
+ with client.stream(message) as stream:
134
+ for event in stream:
135
+ ...
136
+ except AsgardStreamError as e:
137
+ print(e.detail.message)
138
+ ```
139
+
140
+ ## Tool Call Consent
141
+
142
+ ```python
143
+ from asgard import GenericBotMessage, ToolCallConsentResponseItem, ToolCallConsentResult, PostBackAction
144
+
145
+ # After receiving a tool_call.consent event:
146
+ consent_items = [
147
+ ToolCallConsentResponseItem(
148
+ tool_call_id="tc-1",
149
+ result=ToolCallConsentResult.ALLOW_ONCE,
150
+ )
151
+ ]
152
+
153
+ response = GenericBotMessage(
154
+ custom_channel_id="ch-1",
155
+ custom_message_id="m-2",
156
+ action=PostBackAction.RESPONSE_TOOL_CALL_CONSENT,
157
+ tool_call_consents=consent_items,
158
+ )
159
+ reply = client.send_message(response)
160
+ ```