# boru/ai

A provider-agnostic AI runtime library for PHP with native OpenAI and Gemini support.

## Highlights

- provider-agnostic runtime orchestration
- native OpenAI and Gemini providers
- streaming support
- runtime-owned tool execution loop
- replay-aware conversation continuity
- first-class persistence hooks
- provider-agnostic asset/file model with provider-specific bindings
- fluent chat callbacks for stream, tool, and turn lifecycle events

---

## Installation

```bash
composer require boru/ai
```

If you still need legacy BoruAI `FunctionBase` / `ToolDefinition` tool compatibility through `AI::addLegacyTool()`, install the optional bridge package too:

```bash
composer require boru/boruai
```

---

## Quick start

```php
<?php

use boru\ai\AI;
use boru\ai\Bootstrap;

require __DIR__ . '/vendor/autoload.php';

$config = require __DIR__ . '/examples/config.php';
Bootstrap::registerDefaults($config);

$response = AI::provider('gemini')
    ->model('gemini-2.5-pro')
    ->addMessage('Write one short sentence about Atlanta.')
    ->run();

echo $response->text() . PHP_EOL;
```

For a fuller walkthrough, start with `docs/index.md` and `docs/getting-started.md`.

---

## Streaming and chat callbacks

`boru/ai` now includes simple fluent chat-style callback helpers on the `AI` facade:

```php
$response = AI::provider('gemini')
    ->model('gemini-2.5-pro')
    ->addMessage('Write a short paragraph about the ocean.')
    ->onStreamDelta(function ($delta) {
        echo $delta;
    })
    ->onToolStart(function ($toolCallId, $toolName, $arguments) {
        echo "\n[tool:start] " . $toolName . "\n";
    })
    ->onToolComplete(function ($toolCallId, $toolName, $output) {
        echo "\n[tool:done] " . $toolName . "\n";
    })
    ->onToolFailed(function ($toolCallId, $toolName, $output) {
        echo "\n[tool:failed] " . $toolName . "\n";
    })
    ->onTurnComplete(function ($conversationId, $turnNumber) {
        echo "\n[turn:complete] #" . $turnNumber . "\n";
    })
    ->onTurnFailed(function ($conversationId, $turnNumber, $error) {
        echo "\n[turn:failed] #" . $turnNumber . "\n";
    })
    ->run();
```

Available convenience methods:

- `onStreamDelta(function ($delta, $event) {})`
- `onStreamComplete(function ($text, $event) {})`
- `onToolStart(function ($toolCallId, $toolName, $arguments, $event) {})`
- `onToolComplete(function ($toolCallId, $toolName, $output, $event) {})`
- `onToolFailed(function ($toolCallId, $toolName, $output, $event) {})`
- `onTurnComplete(function ($conversationId, $turnNumber, $event) {})`
- `onTurnFailed(function ($conversationId, $turnNumber, $error, $event) {})`

For lower-level control, `onEvent()` and `addEventListener()` remain available.

See also:

- `docs/chat.md`
- `docs/tools.md`
- `docs/replay.md`

---

## Documentation

Start with:

- `docs/index.md`
- `docs/getting-started.md`
- `docs/chat.md`

Focused guides:

- `docs/configuration.md`
- `docs/testing.md`
- `docs/tools.md`
- `docs/conversation-memory.md`
- `docs/replay.md`
- `docs/assets.md`
- `docs/persistence.md`
- `docs/architecture.md`
- `docs/cli.md`

---

## Examples

See the `examples/` directory for working scripts, including:

- simple provider execution
- weatherbot tool-calling
- class-based tool examples
- conversation memory tools enabled through config
- legacy boruai tool compatibility examples via optional `boru/boruai`
- PDF summary examples
- replay with `last_n_turns`
- replay with `summarize_history`

---

## Current status

The library currently includes:

- native OpenAI provider
- native Gemini provider
- streaming support for both providers
- runtime-owned tool loop
- normalized runtime-native tool lifecycle events for started/completed/failed execution
- provider-agnostic conversation/request/response models
- OpenAI resumed conversation continuity through `previous_response_id` when replay is enabled
- Gemini replay currently rebuilds content from normalized persisted turns
- replay strategies including `full`, `last_n_turns`, and `summarize_history`
- optional replay of tool activity with bounded message, args, and output budgets
- provider-agnostic asset model and provider-specific asset bindings
- persistence hooks and DB-backed implementations
- first-class conversation memory with built-in memory tools and policy knobs

---

## Design principles

These principles should remain true as the library evolves:

- runtime remains the orchestration owner
- providers remain adapters
- tools remain runtime-owned
- provider signals remain distinct from runtime-native events
- core assets remain provider-agnostic
- provider-specific file ids/URIs remain binding data, not core asset identity
