Metadata-Version: 2.5
Name: engini
Version: 0.19.0
Summary: Engini SDK — agent-first ergonomic layer over the Engini Public API
Project-URL: Homepage, https://github.com/engini/engini-sdk
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.9
Requires-Dist: engini-client<0.9.0,>=0.8.0
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.6
Description-Content-Type: text/markdown

# engini

Agent-first Python SDK for the [Engini](https://engini.io) Public API — discover tools,
execute them against your connected apps, and wrap them as LLM tool definitions.

```bash
pip install engini
```

## Quickstart

```python
from engini import Engini

client = Engini(api_key="eng_…")  # or set ENGINI_API_KEY

# Discover canonical tool schemas
tools = client.tools.get(applications=["salesforce"], search="accounts", limit=5)

# Execute a tool against a connection
conn_id = next(c.connection_id for c in client.connections.list(application="salesforce"))
result = client.tools.execute(
    "salesforce_getrecords", {"sobject": "Account"}, connection_id=conn_id
)
print(result.output)
```

JWT auth is the fallback: `Engini(token="<jwt>", company_token="<id>")`, or set
`ENGINI_API_TOKEN` / `ENGINI_COMPANY_TOKEN`. With an API key the company is bound to the key,
so no company token is needed. Point at another host with `Engini(..., base_url=…)`.

Every request carries a `User-Agent` identifying this SDK and its runtime (e.g.
`engini-sdk-python/0.13.0 python/3.11.6 darwin`). If you're embedding the SDK inside your own
product, prepend your own token with `user_agent_prefix`:

```python
client = Engini(api_key="eng_…", user_agent_prefix="my-product/1.4.0")
# -> "my-product/1.4.0 engini-sdk-python/0.13.0 python/3.11.6 darwin"
```

`user_agent_prefix` only *prepends* a token — it can't override or remove the SDK's own identity.

## Use with an LLM

`Provider` adapters wrap canonical schemas into vendor tool definitions **client-side, with no
vendor SDK dependency**. OpenAI is the default; Anthropic is also available.

```python
# Bind applications → connections once, then drive a tool-calling loop
toolset = client.toolset(tools=["salesforce_getrecords"], connections={"salesforce": "Prod"})

openai_tools = client.provider.wrap_tools(toolset.tools())  # plain OpenAI tool-JSON dicts
# … send openai_tools to the model, get a response …
results = toolset.handle_tool_calls(llm_response)  # runs the calls, returns results
```

`client.mcp` inspects and maintains the account's MCP servers — the endpoints Engini
*exposes* over the Model Context Protocol:

```python
servers = client.mcp.list()  # includes deactivated ones
client.mcp.deactivate(servers[0].mcp_server_token)  # reversible; sends only is_active
tools = client.mcp.available_tools(token, connection_id=2139)  # candidates for tool_slugs
```

`update()` replaces `connections`/`workflows` rather than merging them, and every
connection entry needs an explicit `connection_id` (MCP servers are account-wide, so there
is no per-user default). There is no `create`/`delete`: those stay in the engini.io UI.

`client.opa` provisions, inspects, configures and credentials on-premise agents — the
Engini component a customer installs behind their own firewall to reach SQL Server,
Oracle, Priority ERP and file shares:

```python
# Provision one and hand the token to the installer
agent = client.opa.create("Warehouse SQL", pull_period_seconds=30)
print(agent.token)  # a credential - store it, do not log it

# Later: is it healthy?
a = client.opa.get(agent.agent_id)
print(a.status, a.version, a.last_seen_at)

# Change its log level; the agent picks it up on its next poll
r = client.opa.update(agent.agent_id, log_level="Debug")
if not r.applied_to_agent:
    print("stored; the agent is not Online yet")
```

An agent polls Engini, so anything that "talks to" it waits for its next poll. `update()`
**merges** — an omitted keyword leaves that field alone, unlike `connections.update()`,
which replaces its whole field map. Settings you omit take the server's defaults, and the
minimums come from the connector's own declared metadata rather than the client. Agents are
not connections: `client.connections` never returns them.

`client.toolset(...)` builds a local toolset (no I/O until used) or loads a server one via
`toolset_id=…`.

## Files

Tools whose `input_schema` marks a field `"format": "engini/file"` accept files. Wrap a file
with `engini.File` and pass it as the field value — the SDK base64-encodes it into the
`{base64_content, mime_type, filename}` wire shape. A field can take a single file or a list,
per the tool's schema.

```python
from engini import Engini, File

client = Engini(api_key="eng_…")
client.tools.execute(
    "doc_summarize",
    {
        "document": File.from_path("report.pdf"),  # single file
        "attachments": [File.from_path("a.png"), File.from_path("b.png")],  # list of files
    },
    connection_id=conn_id,
)
```

`File.from_path` infers the filename and mime type; `File.from_bytes(data, filename=…,
mime_type=…)` and `File.from_base64(…)` cover in-memory content.

In the LLM loop an agent can't produce base64, so file fields are presented to it as string
fields. Register the files you'll allow and let the model reference one by key:

```python
results = toolset.handle_tool_calls(llm_response, files={"report": File.from_path("report.pdf")})
```

## What this adds over the raw REST client

Built on the autogenerated [`engini-client`](https://pypi.org/project/engini-client/), the SDK
adds what the generated client deliberately lacks: typed errors (the `EnginiError` family),
retry/backoff, auto-pagination, pluggable auth (`ApiKeyAuth` / `BearerAuth`), `Provider`
adapters for OpenAI/Anthropic, and the ergonomic `Toolset` object.

## Command-line interface

The `engini` CLI is npm-only now: `npm install -g @engini/cli`. See
<https://www.npmjs.com/package/@engini/cli> (or `ts/packages/cli/README.md` in
the source repo) for install, commands, and the machine-readable output/exit-code
contract. This package (`engini`, PyPI) is the Python **SDK** only.

Source & docs: <https://github.com/engini/engini-sdk>
