Metadata-Version: 2.3
Name: runorca
Version: 0.3.0
Summary: Python client for the Orca Agent Engine API
Project-URL: Homepage, https://github.com/orca-ae/orca-sdk-python
Project-URL: Repository, https://github.com/orca-ae/orca-sdk-python
Author: The Orca Authors
License: Apache-2.0 AND MIT AND BSD-3-Clause AND MPL-2.0
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
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: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: anyio<5,>=3.5.0
Requires-Dist: httpx2<3,>=2.0.0
Requires-Dist: pydantic<3,>=2.0.0
Requires-Dist: sniffio<2,>=1
Requires-Dist: typing-extensions<5,>=4.14
Description-Content-Type: text/markdown

# Orca Python SDK

[![CI](https://github.com/orca-ae/orca-sdk-python/actions/workflows/ci.yml/badge.svg)](https://github.com/orca-ae/orca-sdk-python/actions/workflows/ci.yml)

Python client for the [Orca Agent Engine](https://github.com/orca-ae/orca-agent-engine) API.
Documentation lives at [runorca.ai](https://runorca.ai).

## Installation

Install the SDK from PyPI:

```sh
pip install runorca
```

The distribution is named `runorca`, but the package you import is still `orca`.
Python 3.10 or later is required.

To install unreleased changes straight from this repository:

```sh
pip install "runorca @ git+https://github.com/orca-ae/orca-sdk-python"
```

Append `@<tag or commit>` to the URL to pin a version. If you previously installed
this SDK from Git as `orca-sdk`, uninstall that distribution before installing
`runorca`; both distributions install the same `orca` import package.

> **Note:** do not run `pip install orca-sdk` — that name belongs to an unrelated
> package on public PyPI.

## Usage

```python
import os
from orca import Orca

client = Orca(
    api_key=os.environ.get("ORCA_API_KEY"),
    base_url=os.environ.get("ORCA_BASE_URL"),
)

agent = client.agents.create(model="some-model", name="My First Agent")
print(agent.id)
```

Every method is available on an async client with the same signature:

```python
import asyncio
from orca import AsyncOrca

client = AsyncOrca()


async def main() -> None:
    agent = await client.agents.create(model="some-model", name="My First Agent")
    print(agent.id)


asyncio.run(main())
```

## Configuration

| Option | Environment variable | Default |
|---|---|---|
| `api_key` | `ORCA_API_KEY` | — |
| `base_url` | `ORCA_BASE_URL` | required |
| `timeout` | — | 600 seconds |
| `max_retries` | — | 2 |

`base_url` is the **host root**. The SDK writes the `/v1/...` and `/apis/...` prefixes
itself, so pass `https://orca.example`, not `https://orca.example/v1`. A trailing `/v1`,
`/v1/registry`, or `/api/v1` is stripped with a deprecation warning.

There is no default host: this API is self-hosted, so a missing base URL raises rather
than silently pointing somewhere unexpected.

### Credentials

```python
# A literal token
client = Orca(api_key="sk-...")

# Resolved per request -- the hook for short-lived or rotating tokens
client = Orca(api_key=lambda: read_current_token())

# No Authorization header, for a deployment behind an authenticating proxy
client = Orca(api_key=None)
```

The async client also accepts a coroutine function.

## Pagination

List methods return a page that iterates across page boundaries automatically:

```python
for agent in client.agents.list():
    print(agent.id)
```

```python
async for agent in client.agents.list():
    print(agent.id)
```

To handle pages yourself:

```python
page = client.agents.list(limit=20)
print(page.data, page.next_page)
```

## Streaming

Session events arrive as server-sent events:

```python
session = client.sessions.create(agent="agent_id", environment_id="env_id")

client.sessions.events.send(
    session.id,
    events=[{"type": "user.message", "content": [{"type": "text", "text": "Hello"}]}],
)

for event in client.sessions.events.stream(session.id):
    if event.type == "session.status_idle":
        break
```

Event names are not constrained by the SDK: every well-formed frame is yielded and you
discriminate on the payload's own `type`.

## Working with one session

`client.session(id)` returns a handle that carries the session id for you:

```python
handle = client.session("session_123")

handle.events.send(events=[{"type": "user.message", "content": [...]}])

for thread in handle.threads.list():
    print(thread.id)

response = handle.files.download("file_123")
```

## File uploads

```python
metadata = client.files.upload(file=("hello.txt", b"hello\n", "text/plain"))
```

Anything accepted by `FileTypes` works: bytes, a file object, a path, or a
`(filename, content, content_type)` tuple.

## Errors

```python
from orca import Orca, OrcaError, APIError, NotFoundError, RateLimitError

try:
    client.agents.retrieve("missing_id")
except NotFoundError as err:
    print("not found:", err.status_code)
except RateLimitError as err:
    print("rate limited; retry-after:", err.headers.get("retry-after"))
except APIError as err:
    print(err.status_code, err.message)
except OrcaError as err:
    print("client-side error:", err)
```

| Class | Status |
|---|---|
| `BadRequestError` | 400 |
| `AuthenticationError` | 401 |
| `PermissionDeniedError` | 403 |
| `NotFoundError` | 404 |
| `ConflictError` | 409 |
| `UnprocessableEntityError` | 422 |
| `RateLimitError` | 429 |
| `InternalServerError` | 5xx |
| `APIConnectionError` | network |
| `APIConnectionTimeoutError` | timeout |
| `ExtensionNotAvailableError` | client-side gate |

## Policy and pricing extensions

Guardrail management and effective model pricing are exposed as top-level resources.
Like cloud methods, they check extension discovery first and raise
`ExtensionNotAvailableError` before issuing the business request when unavailable:

```python
guardrail = client.guardrails.create(
    name="Protect production",
    phases=["tool_call"],
    scope="explicit",
    rule={"kind": "builtin", "builtin": "block_tools", "params": {"tools": ["shell"]}},
)

agent = client.agents.create(
    model="some-model",
    name="Guarded agent",
    guardrail_ids=[guardrail.id],
    extra_headers={"orca-beta": "managed-agents-2026-04-01"},
)

for price in client.model_prices.list():
    print(price.provider, price.model_id, price.input_per_million_tokens)
```

`guardrail_ids` is optional on agent create/update and session-local agent
overrides. It is not supported by deployment APIs. The SDK probes the policy
extension only when the field is explicitly supplied.

## Hosted extensions

Methods under `client.cloud.*` are served by the hosted extension group, which only the
hosted service serves; a self-hosted engine does not. On a deployment that does not serve
it, they raise before making any request:

```python
from orca import ExtensionNotAvailableError

try:
    providers = client.cloud.agents.providers.list()
except ExtensionNotAvailableError as err:
    print(f"this deployment has no {err.group!r} extension installed")
```

To check first:

```python
groups = client.discovery.groups()
if any(g.name == "cloud.sn.io" for g in groups.groups):
    ...
```

## Retries and timeouts

Failed requests are retried twice by default, with exponential backoff honouring
`retry-after`. Connection errors, timeouts, 408, 409, 429, and 5xx are retried.

```python
client = Orca(max_retries=3, timeout=30.0)

client.agents.list(timeout=5.0)  # per request
```

## Accessing the raw response

```python
response = client.agents.with_raw_response.list()
print(response.headers.get("request-id"))
agents = response.parse()
```

`with_streaming_response` defers reading the body:

```python
with client.agents.with_streaming_response.list() as response:
    print(response.headers)
    agents = response.parse()
```

## Versioning

This package follows semantic versioning. Internal names prefixed with an underscore are
not part of the public surface.

## Contributing

Contributions are welcome. [CONTRIBUTING.md](CONTRIBUTING.md) covers the workflow,
including the DCO sign-off every commit needs, and [AGENTS.md](AGENTS.md) holds the
conventions every change follows. To get a working checkout:

```sh
./scripts/bootstrap
./scripts/test
./scripts/lint
```

Ask questions and share ideas in [GitHub Discussions](https://github.com/orca-ae/orca-sdk-python/discussions).
Report SDK bugs in this repository's [issues](https://github.com/orca-ae/orca-sdk-python/issues),
and server behavior in the [engine's issues](https://github.com/orca-ae/orca-agent-engine/issues).

## Security

Please don't report security vulnerabilities in public issues. Use GitHub's
[private vulnerability reporting](https://github.com/orca-ae/orca-sdk-python/security/advisories/new),
or email security@runorca.ai.

## License

The SDK's own code is licensed under the [Apache License 2.0](LICENSE). It also
includes code under MIT, BSD-3-Clause, and MPL-2.0: [NOTICE](NOTICE) identifies the
covered code, and [THIRD_PARTY_NOTICES](THIRD_PARTY_NOTICES) contains the license
texts. The MPL-2.0 terms apply to `src/orca/_utils/_utils.py`, which contains copied
MPL code. Both the wheel and source distribution include these notices and that
source file.
