Metadata-Version: 2.4
Name: agentos-sdk-python
Version: 0.2.0
Summary: Async Python SDK for AgentOS APIs.
Project-URL: Repository, https://gitlab.litevar.com/litevar/agentos/agentos-sdk-python
Project-URL: Issues, https://gitlab.litevar.com/litevar/agentos/agentos-sdk-python/-/issues
Author: LiteVar
License-Expression: MIT
License-File: LICENSE
Keywords: agentkit,agentos,async,modelkit,sdk,toolkit
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: typing-extensions<5,>=4.6
Provides-Extra: dev
Requires-Dist: build<2,>=1.2; extra == 'dev'
Requires-Dist: mypy<2,>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio<1,>=0.23; extra == 'dev'
Requires-Dist: pytest<9,>=8; extra == 'dev'
Requires-Dist: ruff<1,>=0.5; extra == 'dev'
Requires-Dist: twine<7,>=6; extra == 'dev'
Description-Content-Type: text/markdown

# AgentOS SDK for Python

Async Python SDK for AgentOS APIs. The package is a thin client over AgentOS HTTP and SSE endpoints with small compatibility helpers for bearer tokens, stream callbacks, Pythonic method names, and request defaults.

## Install

```bash
pip install agentos-sdk-python
```

For local development:

```bash
python3 -m venv .venv
.venv/bin/python -m pip install -e ".[dev]"
```

## Quickstart

```python
from agentos_sdk import AgentOSSDK


async def main():
    async with AgentOSSDK(base_url="http://localhost:8888") as sdk:
        version = await sdk.agentos.get_version()
        print(version)

        registration = await sdk.agentos.register_bundle(
            {
                "bundleId": "com.demo.app",
                "appGroupId": "com.demo.group",
                "opentoolServers": [
                    {"ref": "websearch", "name": "opentool-server-websearch"}
                ],
            }
        )
        print(registration.get("opentoolServers", []))

        tasks = await sdk.cronkit.list_tasks(limit=10)
        print(tasks)
```

`AgentOSSDK` also exposes TS-style camelCase aliases for common migration paths, such as `registerBundle`, `listTasks`, `chatSimple`, `listModels`, and `loadTool`.

## Transport

The SDK uses `httpx.AsyncClient` and defaults to `http://localhost:8888`.

```python
from agentos_sdk import AgentOSSDK

sdk = AgentOSSDK(
    base_url="http://localhost:8888",
    connect_timeout=5.0,
    receive_timeout=10.0,
)
```

Use `async with AgentOSSDK(...)` or call `await sdk.aclose()` when done.

## Token Handling

`sdk.agentos.register_bundle(...)` stores the active app token. Protected facade methods in `sdk.agentkit` and `sdk.cronkit` use that token automatically.

If a protected call fails with HTTP `401`, HTTP `403`, or recognizable invalid-token text, the SDK re-runs registration for the same bundle once and retries the call. The cached bundle is a defensive copy and retains its `opentoolServers` declarations during refresh.

## AgentOS Events

```python
from agentos_sdk import AgentOSEventHandler, AgentOSSDK


class Handler(AgentOSEventHandler):
    async def on_welcome(self, welcome):
        print("welcome", welcome)

    async def on_app_event(self, event):
        if event["type"] == "toolkit.provision.succeeded":
            print("tool ready", event["data"]["ref"], event["data"]["id"])
        elif event["type"] == "toolkit.provision.failed":
            print("tool failed", event["data"]["error"])

    async def on_done(self):
        print("subscription ended")

    async def on_error(self, error):
        raise error


async with AgentOSSDK() as sdk:
    await sdk.agentos.register_bundle(
        {
            "bundleId": "com.demo.app",
            "opentoolServers": [
                {"ref": "websearch", "name": "opentool-server-websearch"}
            ],
        }
    )
    await sdk.agentos.subscribe(Handler())
```

Registration statuses are `running`, `starting`, and `notFound`; only `running` includes a callable tool `id`. A `starting` declaration becomes callable after `toolkit.provision.succeeded`. Stop using a tool while it is `toolkit.tool.unavailable` and resume after `toolkit.tool.available`.

`on_call_app` / `onCallApp` were removed in 0.2.0. Use `on_app_event` / `onAppEvent`.

## AgentKit

```python
from agentos_sdk import AgentMessageHandler, AgentOSSDK, ToolReturn


class ChatHandler(AgentMessageHandler):
    async def on_message(self, session_id, message):
        print("message", session_id, message)

    async def on_chunk(self, session_id, chunk):
        print("chunk", session_id, chunk)

    async def on_function_call(self, session_id, function_call):
        return ToolReturn(function_call["id"], {"ok": True})

    async def on_done(self):
        print("done")

    async def on_error(self, error):
        raise error


async with AgentOSSDK() as sdk:
    await sdk.agentos.register_bundle({"bundleId": "com.demo.app"})
    session = await sdk.agentkit.init_session()
    await sdk.agentkit.chat(
        session,
        {"content": [{"type": "text", "message": "Hello"}]},
        ChatHandler(),
    )
```

Summary-first history is available without loading every process message:

```python
summaries = await sdk.agentkit.history_summary(session_id=session["sessionId"])
messages = await sdk.agentkit.history_process(
    session_id=session["sessionId"],
    original_task_id=summaries[0]["originalTaskId"],
)
```

`history_summary` defaults to page 1 with 30 items; `history_process` defaults to page 1 with 50 items. Page sizes must be between 1 and 200. CamelCase callers can use `historySummary(sessionId=...)` and `historyProcess(sessionId=..., originalTaskId=...)`.

Message, chunk, and function-call handlers receive a child `sessionId` when the event supplies one, otherwise the root session ID. Function-call failures passed to `on_error` use `FunctionCallLifecycleError` with `stage` equal to `function_call_parse_failed`, `function_call_handler_failed`, or `function_call_callback_failed`.

Direct AgentKit methods include `get_agent`, `create_agent`, `update_agent`, `delete_agent`, `init_session`, `init_simple`, `chat`, `chat_simple`, `history`, `history_summary`, `history_process`, `stop`, `clear`, `callback`, and `stream_callback`.

## CronKit

```python
async with AgentOSSDK() as sdk:
    await sdk.agentos.register_bundle({"bundleId": "com.demo.app"})
    task = await sdk.cronkit.create_task(
        request={
            "name": "Daily summary",
            "schedule": "0 9 * * *",
            "spec": {
                "systemPrompt": "You are a scheduled agent.",
                "content": [{"type": "text", "message": "Summarize today."}],
            },
        }
    )
    await sdk.cronkit.run_task_now(cron_id=task["cronId"])
```

CronKit create requests default `concurrency` to `skipIfRunning`, `enabled` to `True`, and `spec.timeoutSeconds` to `3600`.

## ModelKit

```python
async with AgentOSSDK() as sdk:
    models = await sdk.modelkit.list_models()
    chat_models = await sdk.modelkit.list_models_by_task("chat")
    print(models[0]["capabilities"]["reasoning"])

    completion = await sdk.modelkit.chat.create(
        {
            "model": "qwen3-1.7b",
            "messages": [{"role": "user", "content": "Hello"}],
            "maxTokens": 128,
        }
    )

    async for chunk in sdk.modelkit.chat.create_stream(
        {"model": "qwen3-1.7b", "messages": [{"role": "user", "content": "Stream"}]}
    ):
        print(chunk)
```

Normalized model capabilities include `vision`, `tool_call`, `stream`, and
`reasoning` booleans. `reasoning` reflects only the server-provided
`capabilities.reasoning` value and defaults to `False` when omitted.

ModelKit also supports:

- `sdk.modelkit.embedding.create(...)`
- `sdk.modelkit.asr.transcribe(...)`
- `sdk.modelkit.tts.synthesize(...)`
- `sdk.modelkit.tts.list_voices()`

## ToolKit

```python
async with AgentOSSDK() as sdk:
    registration = await sdk.agentos.register_bundle(
        {
            "bundleId": "com.example.app",
            "opentoolServers": [
                {
                    "ref": "websearch",
                    "name": "opentool-server-websearch",
                }
            ],
        }
    )

    tools = await sdk.toolkit.list_tool()
    websearch_tool = next(
        (
            tool
            for tool in tools
            if tool.get("status") == "running"
            and (tool.get("server") or {}).get("name")
            == "opentool-server-websearch"
        ),
        None,
    )
    if websearch_tool is None:
        raise RuntimeError("The registered websearch tool is not running")

    tool_id = websearch_tool["id"]
    definition = await sdk.toolkit.load_tool(tool_id)
    raw_json = definition.to_json() if definition else None

    result = await sdk.toolkit.call(
        tool_id,
        {
            "id": "call-1",
            "name": "websearch",
            "arguments": {
                "query": "AgentOS",
                "max_results": 5,
            },
        },
    )
    print(result)

    async for event in sdk.toolkit.subscribe_tool_events("daemon-api-key"):
        print(event)
```

`registration["opentoolServers"]` also contains the resolved `id` when its
status is `running`; the example uses `list_tool()` so it can select the running
tool by `server.name`.

`stream_call()` is only appropriate for functions whose tool server explicitly
supports `streamCall`. For such a function, retain the stream and close it when
consumer work ends early:

```python
async def consume_streaming_function(sdk, streaming_tool_id):
    stream = sdk.toolkit.stream_call(
        streaming_tool_id,
        {"id": "call-2", "name": "streaming-function", "arguments": {}},
    )
    try:
        async for event in stream:
            print(event["event"], event["data"])
    finally:
        await stream.aclose()
```

The high-level ToolKit list, load, call, and stream-call methods use the bearer
token returned by `register_bundle()` and refresh it once after a 401/403
response. `load_tool()` returns an `OpenToolDefinition` wrapper that preserves
the raw JSON definition.

## Direct Clients

The low-level clients are available for explicit-token or custom transport usage:

```python
from agentos_sdk.clients import AgentKitClient, CronKitClient, ModelKitClient
from agentos_sdk.transport import ApiClient

api = ApiClient(base_url="http://localhost:8888")
agentkit = AgentKitClient(api)
agent = await agentkit.get_agent("bearer-token", "agent-id")
```

## Development

```bash
.venv/bin/python -m compileall agentos_sdk
.venv/bin/python -m pytest -q
.venv/bin/ruff check .
```
