Metadata-Version: 2.4
Name: pysaif
Version: 0.1.1
Summary: A local-first library that handles AI provider logistics so you don't have to.
License: MIT License
        
        Copyright (c) 2026 Ahqrk
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Requires-Dist: tomli>=2.0; python_version < "3.11"
Requires-Dist: tomli-w>=1.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Dynamic: license-file

# pysaif

A local-first library that handles AI provider logistics — API calls, config,
memory, quota — so you don't have to think about them every time.

```python
from pysaif import AI

ai = AI()
response = ai.send("Hello!")
print(response)
```

## Install

```bash
pip install pysaif
```

## Setup

On first run, pysaif creates a `Config/` folder next to your project with
`config.toml` (API keys, model, personality — the stuff you'll actually
touch) and `advanced_config.toml` (timeouts, retries, per-model metadata —
the stuff most people never open). Drop your API key into `config.toml` and
you're done.

Each project gets its own config and its own key — pysaif never shares keys
or settings across projects, and never reads them from your environment
implicitly.

## What's implemented right now

- Config: self-healing setup, two-file split, schema versioning and migration hook
- Providers: Google Gemini, OpenAI, Anthropic Claude, OpenRouter (sync and async)
- Provider selection: `manual`, `cheapest`, `fastest`, and `smart` strategies
- Quota-aware daily token limits and automatic provider failover with retries
- Local memory under `Memory/` (rolling window, important, interests) with inject/store toggles
- Usage tracking under `Usage/` (state + optional JSONL logs; message content off by default)
- In-memory response cache (opt-in via `cache.enabled`)
- Prompt assembly from optional context, background, personality, and instructions

## Configuration

pysaif splits config into two files so you only have to look at one of them.

**`Config/config.toml`** — everything you're likely to actually change:

```toml
[provider]
active = "gemini"                              # used when strategy = "manual"
strategy = "manual"                             # manual | cheapest | fastest | smart
fallback_order = ["gemini", "openai", "claude", "openrouter"]

[providers.gemini]
api_key = ""
model = "gemini-2.0-flash"

[providers.openai]
api_key = ""
model = "gpt-4o-mini"

[personality]
traits = ""                                     # see "Personality" below

[prompt]
include_context = false
include_background = false
include_personality = true

[memory]
store = true
inject = true
rolling_window_size = 40
context_window_size = 12
max_important_messages = 50
max_interest_entries = 50

[quota]
enabled = true
daily_token_limit = 0                           # 0 = unlimited

[cache]
enabled = false
max_entries = 128
```

- `provider.strategy` — `manual` uses `provider.active` directly; `cheapest` / `fastest` / `smart` pick a provider automatically each call (see `pysaif/strategy/`)
- `provider.fallback_order` — if the selected provider errors out or is rate-limited, pysaif retries down this list
- `memory.store` — persist each exchange to local memory; `memory.inject` — feed relevant memory back in as context on future calls (these are independent — you can store without injecting, or vice versa)
- `quota.daily_token_limit` — `0` means unlimited; sits on top of whatever the provider's own API limits are

**`Config/advanced_config.toml`** — timeouts, retry/backoff, per-model cost and capability metadata, and logging settings (whether usage logs include the actual message text). Most people never need to open this one.

### Personality

`personality.traits` in `config.toml` is a single free-text field — not a set of named profiles. Whatever you put there gets passed straight into the prompt as-is:

```toml
[personality]
traits = "Blunt, dry humor, no filler, gets to the point."
```

If `prompt.include_personality` is `true` (the default) and `traits` isn't empty, every call sends the provider a line like:

```
Respond with this personality: Blunt, dry humor, no filler, gets to the point.
```

You can override this per call without touching the config file:

```python
response = ai.send("Hello!", personality="Overly formal, addresses you as 'sir'")
```

A per-call `personality` argument always wins over `config.toml`'s `traits` — it's a one-time substitution, not a merge. Pass nothing and it falls back to `traits`; if `traits` is also empty, no personality line gets added to the prompt at all, and `include_personality` has nothing to include.

There's no personality *file*, no "active profile" concept, and no preset library — it's intentionally just a string you write. If you want several distinct personas, keep the different strings somewhere in your own project and pass whichever one you want as `personality=` on each call.

## What's designed but not built yet

Interactive setup wizard, richer capability tools (search/look/make), and
automated config migrations beyond schema backfill — see the module skeleton
under `pysaif/capabilities/` for planned expansion points.

## License

See LICENSE.
