Skip to main content

WormGPT App SDK

Official Python SDK for building applications, agents, and integrations on top of the WormGPT API. Sync + async clients, SSE streaming, automatic retries, typed responses, function tools, a batteries-included agent loop, webhook verification, pagination, a CLI, a Telegram bot runtime, and a JSON/SSE API server — in one dependency-light package (httpx).

from wormgpt import WormGPT

client = WormGPT()  # reads WORMGPT_API_KEY
reply = client.chat.create(messages="Explain exploit chaining in two lines.")
print(reply.content)

No HTML, no frontend build step, no browser required. Run the SDK directly, run a Telegram bot, or expose an HTTP API for other applications.

Install

From a checkout of this repository:

python -m pip install -e .

Or build and install the distribution artifacts:

python -m build
pip install dist/wormgpt_app_sdk-0.1.0-py3-none-any.whl

Requires Python 3.9+ and httpx.

Configuration

Argument Environment variable Default
api_key WORMGPT_API_KEY — (required)
base_url WORMGPT_BASE_URL https://wormgpt.app/v1
default_model WORMGPT_MODEL wormgpt-agent
timeout WORMGPT_TIMEOUT 60 seconds
max_retries WORMGPT_MAX_RETRIES 2

Explicit arguments always win over environment variables.

client = WormGPT(
    api_key="wg-...",
    base_url="https://wormgpt.app/v1",
    timeout=30,
    max_retries=3,
    organization="org_123",
    default_headers={"X-Trace": "abc"},
)

Use the client as a context manager to close the HTTP pool:

with WormGPT() as client:
    print(client.chat.create(messages="hi").content)

Chat completions

from wormgpt import WormGPT

client = WormGPT()

reply = client.chat.create(
    messages=[
        {"role": "system", "content": "You are terse."},
        {"role": "user", "content": "List three recon steps."},
    ],
    model="wormgpt-agent",
    temperature=0.3,
    max_tokens=256,
    stop=["\n\n"],
)

print(reply.content)
print(reply.finish_reason)
print(reply.usage.total_tokens)
print(reply.message.tool_calls)  # typed ToolCall objects, if any

messages accepts a string, a dict, a Message, or a list of those.

The hosted API is OpenAI-compatible, so the same call works with an OpenAI-style namespace:

reply = client.chat.completions.create(
    model="wormgpt-agent",
    messages=[{"role": "user", "content": "hello"}],
)

The official openai package also works against the same endpoint:

from openai import OpenAI

client = OpenAI(base_url="https://wormgpt.app/v1", api_key="wg-...")

Streaming (SSE)

stream = client.chat.stream(messages="Write a short poem about firewalls.")

with stream:
    for chunk in stream:
        print(chunk.content, end="", flush=True)

print("\nfinish:", chunk.finish_reason)

ChatStream is also a plain iterator; breaking out of the loop closes the connection. Server errors that arrive mid-stream raise StreamError.

Async

import asyncio
from wormgpt import AsyncWormGPT

async def main():
    async with AsyncWormGPT() as client:
        reply = await client.chat.create(messages="hello")
        print(reply.content)

        stream = await client.chat.stream(messages="count to three")
        async for chunk in stream:
            print(chunk.content, end="", flush=True)

asyncio.run(main())

Every resource has an async twin: client.chat, models, apps, sessions, files, usage. List endpoints return AsyncPage with await page.next_page() and async for item in page.auto_paging_iter().

Tools and the agent loop

@tool turns a typed Python function into a model-callable function and derives the JSON Schema from annotations, Annotated[...], Literal[...], dataclasses, enums, and docstrings.

from wormgpt import App, WormGPT

app = App(
    "recon-assistant",
    instructions="You are a precise recon assistant. Use tools when needed.",
    max_tool_rounds=6,
)

@app.tool
def resolve(host: str) -> str:
    """Resolve a hostname to an IP address."""
    import socket
    return socket.gethostbyname(host)

client = WormGPT()
run = app.run(client, "Where does example.com point?")
print(run.text)
print(run.steps, run.usage.total_tokens)

App.run keeps calling the model and executing tools until it gets a final answer, then returns an AppRun (text, messages, steps, usage, completions). App.arun is the async variant.

Need just the schema / dispatch pieces?

from wormgpt import ToolRegistry, tool

@tool(name="add", description="Add two integers")
def add(a: int, b: int) -> int:
    return a + b

registry = ToolRegistry([add])
result = registry.dispatch({
    "id": "call_1",
    "function": {"name": "add", "arguments": '{"a": 2, "b": 3}'},
})
print(result.content, result.is_error)  # 5 False

Tool exceptions are captured and returned to the model as error content by default; pass raise_on_error=True to dispatch if you want them to propagate.

Remote app registry

app = client.apps.create(
    name="recon-assistant",
    instructions="You are a precise recon assistant.",
    model="wormgpt-agent",
)
apps = client.apps.list(limit=20)
client.apps.update(app.id, description="updated")
client.apps.delete(app.id)

Sessions, files, usage

session = client.sessions.create(app_id=app.id, title="night shift")
history = client.sessions.history(session.id)
for message in history:
    print(message.role, message.content)

uploaded = client.files.upload("report.pdf", purpose="assistants")
raw = client.files.content(uploaded.id)

report = client.usage.retrieve(period="month")
print(report.total_tokens)

Pagination

page = client.models.list(limit=10)
for model in page:
    print(model.id)

# Iterate every page transparently.
for model in client.models.list().auto_paging_iter():
    print(model.id)

# Or step manually.
next_page = page.next_page()

Error handling

All errors derive from WormGPTError:

WormGPTError
├── ConfigurationError
├── APIConnectionError
│   └── APITimeoutError
├── APIStatusError
│   ├── BadRequestError           400
│   ├── AuthenticationError       401
│   ├── PermissionDeniedError     403
│   ├── NotFoundError             404
│   ├── ConflictError             409
│   ├── UnprocessableEntityError  422
│   ├── RateLimitError            429  (.retry_after)
│   └── ServerError               5xx
├── StreamError
├── WebhookSignatureError
└── MaxToolRoundsError
from wormgpt import RateLimitError, WormGPTError

try:
    client.chat.create(messages="hi")
except RateLimitError as exc:
    print("retry after", exc.retry_after)
except WormGPTError as exc:
    print(exc.status_code, exc.code, exc.request_id, exc.message)

Retries

The client retries timeouts, connection failures, 408, 409, 425, 429, and 5xx responses with exponential backoff and jitter, up to max_retries extra attempts. Retry-After is honored (capped at 60s). Streams are retried only before the first byte is delivered.

Webhooks

from wormgpt import construct_event, sign, verify

header = sign(raw_body, "whsec_...")            # "t=...,v1=..."

if verify(raw_body, header, "whsec_...", tolerance=300):
    event = construct_event(raw_body, header, "whsec_...")
    print(event.type, event.data)

Signatures are HMAC-SHA256 over f"{timestamp}." + body, compared in constant time. Stale timestamps are rejected by default (tolerance=300); pass tolerance=None to disable. Bare hex digests are also accepted.

CLI

export WORMGPT_API_KEY=wg-...

wormgpt chat "summarize this log" --stream
wormgpt chat "hello" --model wormgpt-agent --json
wormgpt models
wormgpt apps list
wormgpt usage --period month
wormgpt bot
wormgpt serve --host 127.0.0.1 --port 8000
wormgpt version

Telegram bot

Create a bot with @BotFather, then export both secrets and start the bot:

export WORMGPT_API_KEY=wg-...
export TELEGRAM_BOT_TOKEN=123456:ABC...
wormgpt bot

The bot uses Telegram's long-polling API directly (no extra dependency), keeps a small per-chat conversation history in memory, and answers with plain text split into Telegram-safe chunks.

Environment variable Purpose
TELEGRAM_BOT_TOKEN Bot token from BotFather (required)
TELEGRAM_ALLOWED_USERS Comma-separated Telegram user IDs; everyone else is rejected
WORMGPT_SYSTEM_PROMPT Default system prompt for every chat
WORMGPT_MODEL Default model ID

Commands:

Command Action
/start, /help Show the command list
/reset Clear this chat's history
/model / /model <id> Show or switch the model
/models List available models
/system <prompt> Set a per-chat system prompt
/system reset Clear the per-chat system prompt
/settings Show model, history size, and prompt state
/id Show chat and user IDs (useful for the allowlist)

Recommended lockdown:

export TELEGRAM_ALLOWED_USERS=123456789,987654321
wormgpt bot --model wormgpt-v5 --max-history 20

Run it from Python:

from wormgpt import run_bot

run_bot(token="123456:ABC...", model="wormgpt-agent")

run_bot reads WORMGPT_API_KEY, TELEGRAM_ALLOWED_USERS, and WORMGPT_SYSTEM_PROMPT from the environment when arguments are omitted. For custom clients:

from wormgpt import TelegramAPI, TelegramBot, WormGPT

with WormGPT() as client:
    bot = TelegramBot(client, TelegramAPI("123456:ABC..."), allowed_users={123456789})
    bot.run()

Conversation history is in memory only and disappears on restart. The bot token and WormGPT API key never leave the server.

HTTP API server

export WORMGPT_API_KEY=wg-...
wormgpt serve --host 127.0.0.1 --port 8000

The server exposes a small JSON/SSE API for bots, scripts, and other services:

Method Path Purpose
GET / Server name, SDK version, model, auth requirement, endpoints
GET /api/models Model list proxy
POST /api/chat Chat proxy; set "stream": true for SSE
POST /chat App endpoint with the tool loop
GET /health Health check

Non-streaming request:

curl -s http://127.0.0.1:8000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "hello"}]}'

Streaming request (data: {"delta": "..."} frames, then data: [DONE]):

curl -N http://127.0.0.1:8000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "count to five"}], "stream": true}'

POST /api/chat accepts messages, model, temperature, max_tokens, system, and stream. POST /chat accepts message (string) or messages, plus optional instructions, and runs the full agent/tool loop.

When binding to a network address, the server generates an eight-character access token and prints it. Send it as either header:

curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8000/api/models
curl -H "X-Access-Token: $TOKEN" http://127.0.0.1:8000/api/models

Set a custom token with --token or WORMGPT_SERVER_TOKEN, or disable the gate on a trusted network with --no-auth. The /health and / routes stay public.

Run it from Python:

from wormgpt import App
from wormgpt.server import serve

app = App("echo", instructions="Repeat the user's intent briefly.")
serve(app, host="127.0.0.1", port=8000, access_token="change-me")

This is a development-grade threaded server, not a production ASGI stack. Put it behind a reverse proxy with TLS and rate limiting if you expose it beyond a trusted network.

Development

python -m pip install -e ".[dev]"
make test
make lint
make typecheck

Tests run against a local scriptable HTTP server; no API key or network access is required.

License

MIT © WormGPT

Metadata

Release files for wormgpt-app-sdk 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wormgpt-app-sdk 0.1.0
File Size Uploaded Provenance
wormgpt_app_sdk-0.1.0.tar.gz 60.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for wormgpt-app-sdk 0.1.0
File Interpreter ABI Platform
wormgpt_app_sdk-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 114.1 kB

Release files / wormgpt_app_sdk-0.1.0.tar.gz

Download URL wormgpt_app_sdk-0.1.0.tar.gz
Size 60.6 kB
Tags Source
SHA-256 checksum
How to use checksums
517e7956930f6fd8b538cb51c58e378b65a0d270eb0c78ceb43d06f3285e6fe5
BLAKE2b-256 checksum
How to use checksums
30b4ba2136e970bdb5bc007f9e099dd5eabb688de22e15eaf2526ac65e5de70a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release files / wormgpt_app_sdk-0.1.0-py3-none-any.whl

Download URL wormgpt_app_sdk-0.1.0-py3-none-any.whl
Size 53.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dd081f2055bc2eb58e0efe4c94e27e4f9f1697adb0b64303d5d37aa9e0de9d17
BLAKE2b-256 checksum
How to use checksums
2887f910c1b0a9f3b467170b44882515e338ef929071fdde32b7511ee3408413
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Security for wormgpt-app-sdk 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page