# Chat API

`boru/ai` now includes a minimal first-class chat-style callback API on the `AI` facade.

These helpers make chat-oriented apps easier to build without forcing callers to manually inspect raw event types for common cases.

For a broader introduction first, see `getting-started.md`. For the overall docs map, see `index.md`.

## Available fluent callbacks

### Stream callbacks

- `onStreamDelta(function ($delta, $event) {})`
- `onStreamComplete(function ($text, $event) {})`

### Tool lifecycle callbacks

- `onToolStart(function ($toolCallId, $toolName, $arguments, $event) {})`
- `onToolComplete(function ($toolCallId, $toolName, $output, $event) {})`
- `onToolFailed(function ($toolCallId, $toolName, $output, $event) {})`

### Turn lifecycle callbacks

- `onTurnComplete(function ($conversationId, $turnNumber, $event) {})`
- `onTurnFailed(function ($conversationId, $turnNumber, $error, $event) {})`

All of these are thin convenience wrappers over the normalized `StreamEvent` model.

## Example: simple streaming output

```php
$response = AI::provider('gemini')
    ->model('gemini-2.5-pro')
    ->addMessage('Write a short paragraph about the ocean.')
    ->onStreamDelta(function ($delta) {
        echo $delta;
    })
    ->run();
```

## Example: tool lifecycle output

```php
$response = AI::provider('gemini')
    ->model('gemini-2.5-pro')
    ->defineTool(
        'get_current_temperature',
        'Get the current temperature for a city',
        array(
            'type' => 'object',
            'properties' => array(
                'location' => array('type' => 'string'),
            ),
            'required' => array('location'),
        ),
        function (array $arguments) {
            return array(
                'location' => $arguments['location'],
                'temperature' => 69,
                'unit' => 'F',
            );
        }
    )
    ->addMessage('What is the temperature in Atlanta?')
    ->onToolStart(function ($toolCallId, $toolName, $arguments) {
        echo '[tool:start] ' . $toolName . PHP_EOL;
    })
    ->onToolComplete(function ($toolCallId, $toolName, $output) {
        echo '[tool:done] ' . $toolName . PHP_EOL;
    })
    ->onToolFailed(function ($toolCallId, $toolName, $output) {
        echo '[tool:failed] ' . $toolName . PHP_EOL;
    })
    ->run();
```

For more on tool design and runtime tool lifecycle, see `tools.md`.

## Example: full chat-style lifecycle

```php
$response = AI::provider('gemini')
    ->model('gemini-2.5-pro')
    ->addMessage('Answer the user and use tools if needed.')
    ->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";
    })
    ->onTurnComplete(function ($conversationId, $turnNumber) {
        echo "\n[turn:complete] #" . $turnNumber . "\n";
    })
    ->onTurnFailed(function ($conversationId, $turnNumber, $error) {
        echo "\n[turn:failed] #" . $turnNumber . "\n";
    })
    ->run();
```

If your chat flow resumes prior conversations, also review `replay.md`.

## Normalized event mapping

These helpers map to normalized runtime-native event types:

- `onStreamDelta(...)` -> `text.delta`
- `onStreamComplete(...)` -> `text.completed`
- `onToolStart(...)` -> `tool.execution.started`
- `onToolComplete(...)` -> `tool.execution.completed`
- `onToolFailed(...)` -> `tool.execution.failed`
- `onTurnComplete(...)` -> `turn.completed`
- `onTurnFailed(...)` -> `turn.failed`

This means callers can use the convenience API without depending on legacy provider-specific or transitional event names.

## When to use `onEvent(...)`

Use `onEvent(...)` when you need:

- full access to every event type
- custom filtering logic
- direct access to raw event payloads
- integration with event models not covered by the convenience helpers

Example:

```php
use boru\ai\Stream\StreamEvent;

$response = AI::provider('gemini')
    ->model('gemini-2.5-pro')
    ->addMessage('Write a short paragraph about rain.')
    ->onEvent(function (StreamEvent $event) {
        if ($event->type() === StreamEvent::TEXT_DELTA) {
            echo $event->delta();
        }
    })
    ->run();
```

## When to use an event dispatcher

Use `eventDispatcher(...)` or `addEventListener(...)` when you want reusable listener objects such as:

- `OutputBufferListener`
- `CompletionStatusListener`
- `FunctionCallListener`

These are useful when you want to collect output, tool call state, or turn completion status in a reusable way.

## Design intent

This chat API is intentionally minimal.

It is designed to make common chat application flows easier while keeping `boru/ai` provider-agnostic and preserving the lower-level event system underneath.

See also:

- `getting-started.md`
- `tools.md`
- `replay.md`
- `architecture.md`
