# Pepper — API Agente (v1)

Questo documento definisce la **modalità agente** di Pepper: multi-turno, tool-use
(function calling) e streaming. È un contratto **separato e aggiuntivo** rispetto a
[API.md](?f=api), che resta valido e invariato per tutti gli altri progetti.

| | API classica (`/api.php`) | API agente (`/agent`) |
|---|---|---|
| Cosa manda il chiamante | `progetto` + `encode` + `contenuto` | `system` + `messages` + `tools` |
| Istruzioni (system prompt) | le decide Pepper (le "ricette") | le decide il chiamante |
| Memoria | la tiene Pepper (`conversazione`) | la tiene il chiamante (`messages`) |
| Attrezzi | no | sì |
| Streaming | no | sì (SSE) |

**Principio**: Pepper Plus è il cervello, non le mani. Capisce, pianifica e decide
quale attrezzo chiamare, ma **non esegue niente**: gli attrezzi li esegue il
chiamante, con i propri permessi e le proprie conferme.

## Come si attiva

La modalità agente va abilitata **per progetto** dal pannello di Pepper:
**Progetti → apri il progetto → "Modalità agente"**. Un progetto in modalità "Standard"
che chiama `/agent` riceve `403 agente_non_abilitato`.
Un progetto in modalità agente può comunque continuare a usare `/api.php`.

## Endpoint

```
POST https://gateway.pepperai.it/agent
Content-Type: application/json
Accept: text/event-stream          # quando stream = true
X-Pepper-Key: <chiave del progetto>
X-Pepper-Project: <nome del progetto>
```

Funziona anche `POST /agent.php` (utile se il rewrite non è configurato — vedi
[Configurazione nginx](#configurazione-nginx) in fondo).

Chiave e progetto sono gli stessi di `/api.php`; qui il nome del progetto sta
nell'header `X-Pepper-Project` invece che nel corpo (è accettato anche come campo
`project` del corpo, come ripiego).

## Richiesta

```jsonc
{
  "session_id": "andrea-dashboard-8f3a",   // facoltativo, solo per il log
  "stream": true,                          // true = SSE, false = JSON unico
  "max_tokens": 1024,
  "system": "Sei Pepper, l'assistente di casa di Andrea. Regole: proponi le azioni,
             non darle per fatte; chiedi se ambiguo; sii conciso.",
  "messages": [
    {"role": "user", "content": "prepara casa per stanotte"},
    {"role": "assistant", "content": null,
     "tool_calls": [{"id": "c1", "name": "get_home_status", "arguments": {}}]},
    {"role": "tool", "tool_call_id": "c1",
     "content": "{\"presenza\":\"solo Andrea\",\"cancelletto\":\"aperto\"}"}
  ],
  "tools": [
    {
      "name": "get_home_status",
      "description": "Stato sintetico di casa: presenza, cancelli, clima, allarme.",
      "parameters": {"type": "object", "properties": {}, "required": []}
    },
    {
      "name": "set_climate",
      "description": "Accende o spegne il condizionatore di una stanza o zona.",
      "parameters": {
        "type": "object",
        "properties": {
          "target": {"type": "string", "enum": ["salone", "zona_notte"]},
          "action": {"type": "string", "enum": ["on", "off"]}
        },
        "required": ["target", "action"]
      }
    }
  ]
}
```

| Campo | Tipo | Obbl. | Note |
|---|---|:---:|---|
| `messages` | array | ✅ | La cronologia completa, in ordine. Formato qui sotto. |
| `system` | string | ❌ | Le istruzioni dell'agente. Se manca, valgono le istruzioni del modello Pepper. Se c'è, di serie **prende il posto** di quelle del modello; con la modalità «voce nelle app» invece **si somma** (vedi [Modello, limiti, comportamento](#modello-limiti-comportamento)). |
| `tools` | array | ❌ | Gli attrezzi disponibili. Può cambiare a ogni richiesta. |
| `stream` | bool | ❌ | `true` → SSE (§ Streaming). Predefinito `false`. |
| `max_tokens` | int | ❌ | Predefinito 4096. Limitato fra 256 e 32000. |
| `session_id` | string | ❌ | Serve solo a raggruppare le chiamate nel log di Pepper. **Pepper non tiene stato**: non è una sessione. |

### Formato `messages`

- `{"role": "user", "content": "<testo>"}`
  — dalla 6d `content` può essere anche una **lista di blocchi**, per allegare
  documenti: vedi [Documenti](#documenti-pdf). Chi manda stringhe non cambia niente.
- `{"role": "assistant", "content": "<testo>"}` — risposta testuale del modello.
- `{"role": "assistant", "content": null, "tool_calls": [{"id", "name", "arguments"}]}`
  — il modello ha chiesto uno o più attrezzi. `arguments` è un **oggetto JSON**.
  `content` può contenere anche testo insieme alle `tool_calls`.
- In ogni messaggio `assistant` può esserci anche **`"blocchi": [...]`**: la lista
  che Pepper ha mandato nell'evento `done` di quel turno, **tale e quale**. Serve ai
  modelli che ragionano: vedi [Il ragionamento e i `blocchi`](#il-ragionamento-e-i-blocchi).
  Chi non la manda funziona come prima.
- `{"role": "tool", "tool_call_id": "<id>", "content": "<risultato>"}` — il risultato
  che il chiamante ridà al modello. `content` è una **stringa** (testo o JSON serializzato).

Gli `id` delle `tool_calls` e i `tool_call_id` devono corrispondere: ogni attrezzo
richiesto deve avere il suo risultato prima della chiamata successiva.

### Formato `tools`

Function calling standard: `name`, `description`, `parameters` in **JSON Schema**.
`parameters` deve essere un oggetto (anche vuoto: `{"type":"object","properties":{}}`).

## Risposta

A ogni chiamata il modello può rispondere in testo, chiedere uno o più attrezzi, o
entrambe le cose. Chiude sempre con uno `stop_reason`:

- `"end_turn"` → ha finito, il testo è la risposta per l'utente.
- `"tool_use"` → vuole che il chiamante esegua gli attrezzi e richiami l'endpoint.

### Streaming — `stream: true`

`Content-Type: text/event-stream`, HTTP `200`. Sequenza di eventi tipizzati:

```
event: delta
data: {"text": "sto controllando"}

event: delta
data: {"text": " lo stato di casa…"}

event: tool_call
data: {"id": "toolu_01ABC", "name": "get_home_status", "arguments": {}}

event: done
data: {"stop_reason": "tool_use", "usage": {"input_tokens": 1840, "output_tokens": 96}, "versione": "Pepper 7", "novita": "…", "blocchi": [{"type": "thinking", "thinking": "", "signature": "…"}, {"type": "tool_use", "id": "toolu_01ABC", "name": "get_home_status", "input": {}}]}
```

- **`delta`** — un pezzo incrementale di testo. Concatenati danno il testo completo.
- **`tool_call`** — emesso quando gli argomenti dell'attrezzo sono **completi**
  (già come oggetto JSON, non a pezzi). Possono essercene più d'uno.
- **`done`** — chiude il turno, con `stop_reason` (`end_turn` | `tool_use`), `usage`
  (token consumati da **questa** chiamata, non dalla conversazione), `versione`
  (il **modello** che ha risposto — «Pepper 7» — cioè il cervello scelto per il
  progetto nel pannello: chi lo mostra lo prende da qui invece di tenerne una copia,
  vedi I modelli), `novita` (una riga
  del modello che dice cosa ha in più) e `blocchi` (la risposta com'era, pezzo per
  pezzo: da conservare nel messaggio `assistant` di questo turno, vedi
  [Il ragionamento e i `blocchi`](#il-ragionamento-e-i-blocchi)). `blocchi` c'è con
  i modelli Claude.

  `novita` c'è qui e non su `/api.php` per un motivo preciso: qui dall'altra parte
  c'è una persona che guarda una chat, e quando il numero cambia il
  [kit della chat](#il-kit-della-chat) può scriverglielo da sé. Su `/api.php` le
  risposte se le legge una macchina e finiscono nei log: una riga di novità a ogni
  chiamata sarebbe solo peso. Chi la vuole comunque la trova su `/capacita.php`.
- **`error`** — `{"message": "...", "code": "..."}`; subito dopo lo stream si chiude.
- Ogni ~15 secondi di silenzio Pepper manda un commento di keep-alive (`: ping`),
  così i timeout intermedi non chiudono la connessione.

### Non-streaming — `stream: false`

Risposta unica `application/json`:

```json
{
  "stop_reason": "tool_use",
  "content": "sto controllando lo stato di casa…",
  "tool_calls": [{"id": "toolu_01ABC", "name": "get_home_status", "arguments": {}}],
  "usage": {"input_tokens": 1840, "output_tokens": 96},
  "versione": "Pepper 7",
  "novita": "…",
  "blocchi": [ … ]
}
```

`content` è `null` se il modello non ha prodotto testo; `tool_calls` è `[]` se non
ha chiesto attrezzi. `usage`, `versione`, `novita` e `blocchi` sono gli stessi
dell'evento `done`.

In `usage` ci sono anche `cache_read_input_tokens` (letti dalla cache: costano un
decimo) e `cache_creation_input_tokens` (scritti in cache: un quarto in più).
Pepper mette in cache da sé attrezzi + istruzioni e la cronologia già mandata: la
cache regge solo se quella parte resta **identica** da un giro all'altro. Un
orario al minuto nel `system` la azzera a ogni messaggio: l'ora va nel messaggio.

### Come sommare `usage` senza contare due volte

`usage` è per singola chiamata. In un loop di attrezzi la cronologia viene rimandata
da capo a ogni giro, quindi **sommare gli `input_tokens` dei vari giri conta lo stesso
testo più volte**: cinque giri non costano cinque volte la cronologia, la contano solo
in modo crescente. Per il costo reale di un turno dell'utente:

- `output_tokens`: **somma** di tutti i giri;
- `input_tokens`: prendi **solo l'ultimo** giro (è il più grande e comprende i precedenti).

È un'approssimazione per difetto sull'input, ma è molto più vicina al vero della somma.

## Il loop di tool-use

1. `POST /agent` con `messages` + `tools`.
2. Pepper risponde: eventuale testo + eventuali `tool_call` + `done{stop_reason}`.
3. `stop_reason == "end_turn"` → fine, si mostra il testo. **Stop.**
4. `stop_reason == "tool_use"`:
   - si accoda il turno assistant (testo + `tool_calls` + i `blocchi` del `done`) a `messages`;
   - si eseguono gli attrezzi **in locale**, con permessi e conferme;
   - si accoda un messaggio `{"role": "tool", tool_call_id, content}` per ciascuno
     (anche per quelli non ancora eseguiti: es. `{"status":"in_attesa_conferma"}`);
   - si richiama `POST /agent` con la cronologia aggiornata. Torna al punto 2.
5. Conviene mettere un tetto di sicurezza (es. 8 giri per messaggio utente):
   quello sta al chiamante, Pepper non lo impone.

### Tre regole da non sbagliare

1. **Il testo dei `delta` è esattamente il `content` da rimandare.** I pezzi
   concatenati danno la stringa da mettere nel turno assistant: non va rielaborata.
2. **Testo e attrezzi stanno nello stesso messaggio assistant, non in due.** Se il
   turno ha prodotto sia `delta` sia `tool_call`, si rimanda un solo messaggio:
   `{"role": "assistant", "content": "...", "tool_calls": [...]}`.
3. **Ogni `tool_call` deve avere il suo risultato, senza eccezioni.** Anche quando
   l'azione è in attesa della conferma dell'utente va mandato il messaggio `tool`
   con un contenuto qualsiasi (es. `{"status":"in_attesa_conferma"}`). Se ne manca
   anche uno solo, la chiamata successiva viene **rifiutata dal provider**: è il
   modo più facile di rompere il loop, e capita proprio nel caso più frequente —
   le azioni che passano dalla conferma.

Gli `id` delle `tool_call` li genera Pepper e vanno riecheggiati **identici**, sia in
`assistant.tool_calls[].id` sia nel `tool_call_id` del risultato.

## Errori

Formato: HTTP `4xx`/`5xx` con corpo

```json
{"error": {"code": "agente_non_abilitato", "message": "La modalità agente non è attiva per il progetto 'pepper-home'."}}
```

| HTTP | `code` | Quando |
|---|---|---|
| 400 | `richiesta_non_valida` | JSON malformato, `messages` mancante o messaggi con ruoli/campi errati |
| 401 | `non_autorizzato` | Chiave assente, errata o non abbinata al progetto |
| 403 | `agente_non_abilitato` | Il progetto non ha la modalità agente attiva |
| 405 | `metodo_non_valido` | Non è una POST |
| 413 | `richiesta_troppo_grande` | Corpo oltre 32 MB |
| 429 | `troppe_richieste` | Superato il limite orario del progetto (header `Retry-After`) |
| 429 | `budget_esaurito` | Superato il tetto di spesa giornaliero del progetto (header `Retry-After`) |
| 422 | `richiesta_rifiutata` | Il modello si è rifiutato di rispondere per motivi di sicurezza: riprovare uguale non serve |
| 502 | `errore_ai` | Il provider AI ha risposto con un errore |
| 503 | `servizio_non_disponibile` | Database o configurazione non raggiungibili |

Se l'errore capita **a stream già aperto** non si può più cambiare il codice HTTP:
esce come `event: error` sullo stream, che poi si chiude. Il chiamante deve quindi
gestire l'errore in tutti e due i modi.

## Modello, limiti, comportamento

- **Motore**: quello del **modello Pepper** del progetto (pannello → Progetti, e i
  modelli in pannello → Modelli). Provider Claude o OpenAI secondo il modello: il contratto
  qui sopra non cambia, l'adattatore traduce.
- **Istruzioni**: il `system` lo decide chi chiama. Se non lo manda, valgono le
  istruzioni del modello. Se lo manda, dipende dal modello:
  - di serie il `system` dell'app **prende il posto** delle istruzioni del modello:
    l'app parla con un assistente che non sa di essere Pepper, se non glielo dice lei;
  - con la modalità **«voce nelle app»** accesa si **sommano**: prima le istruzioni
    del modello (chi è Pepper), poi il `system` dell'app. In questo caso l'app manda
    solo le sue regole operative, e la voce resta quella di Pepper.

  Quale dei due vale lo dice `/capacita.php` (`pepper.voce_nelle_app`). La modalità si
  sceglie quando nasce un modello e non cambia più mentre qualcuno lo usa: un
  progetto non si ritrova un comportamento diverso senza aver cambiato modello.
- **Ragionamento**: lo decide il modello, non l'app. Vedi qui sotto.
- **Timeout**: nessun limite imposto da Pepper oltre i 900 secondi di sicurezza dello
  script e i 600 verso il provider. In streaming i ping tengono viva la connessione.
- **Contesto**: il limite è quello del modello (1M token su Opus/Sonnet recenti). Chi
  chiama è responsabile di non mandare cronologie infinite.
- **Dimensione dei risultati degli attrezzi**: Pepper non mette un limite per singolo
  campo. I tetti veri sono tre: il corpo della richiesta (32 MB, come il provider),
  il contesto del modello, e il costo. Un risultato da 100 KB di testo sono circa
  25.000 token, che vengono **rimandati a ogni giro successivo** del loop: conviene che
  gli attrezzi restituiscano già il sunto (le ultime N righe, i totali) invece del
  dump completo, e che sia l'agente a chiedere il dettaglio con un secondo attrezzo
  se gli serve.
- **Log**: ogni chiamata finisce nel monitor di Pepper come encode `agente`, con
  token consumati, durata ed esito.

## Il ragionamento e i `blocchi`

Un modello può **ragionare prima di rispondere** («Pepper 7»). Non lo sceglie l'app:
lo sceglie chi gestisce Pepper, nel pannello, modello per modello. Risponde meglio
sulle cose difficili, ci mette di più e costa di più. Su alcuni motori (Opus 5.5,
Fable 5.1) il ragionamento non si può spegnere affatto.

**Il lavoro in più per l'app, e perché.** Quando Pepper ragiona e poi chiede un
attrezzo, al giro dopo vuole indietro anche i pezzi del suo ragionamento,
**identici e nello stesso ordine**. Per questo `done` porta `blocchi`: la risposta
com'era, pezzo per pezzo. L'app non la guarda e non la tocca — è una scatola chiusa
— e la rimette nel messaggio `assistant` di quel turno:

```json
{"role": "assistant", "content": "Controllo il salone.",
 "tool_calls": [{"id": "toolu_01ABC", "name": "get_home_status", "arguments": {}}],
 "blocchi": [ …esattamente quello arrivato nel `done`… ]}
```

Quando ci sono, Pepper rimanda al provider **quelli** e non ricostruisce niente da
`content` e `tool_calls`.

Tre regole:

1. **I `blocchi` vanno conservati come JSON, non ricostruiti.** Niente campi tolti,
   niente riordinati, e `{}` deve restare `{}` (non `[]`).
2. **La cronologia si allunga soltanto.** Mai togliere, accorciare o riscrivere un
   messaggio vecchio mentre la conversazione è aperta, e mai cambiare `system` o
   `tools` a metà: il ragionamento dopo quel punto non varrebbe più. Pepper in quel
   caso lo scarta invece di dare errore (la risposta è un po' meno ragionata, e nel
   log di Pepper si vede), ma è meglio non succeda.
3. **Chi non manda i `blocchi` non si rompe.** Se in un giro di attrezzi ancora
   aperto un messaggio `assistant` arriva senza, per quel giro risponde la
   **riserva** del modello: un motore che sa andare avanti senza ragionamento. La
   conversazione continua, e nel pannello di Pepper il progetto risulta «app senza
   blocchi» finché non viene aggiornata. Nessuna levetta da accendere: Pepper se ne
   accorge da solo, a ogni chiamata.

Con un modello che non ragiona i `blocchi` arrivano lo stesso (testo e attrezzi):
conservarli da subito non costa niente, e il giorno che il progetto passa a un
modello che ragiona l'app è già pronta.

Chi ragiona ha bisogno di spazio: se il modello ragiona, Pepper alza da solo
`max_tokens` ad almeno 16.000.

## Documenti (PDF)

Dalla 6d un messaggio dell'utente può portarsi dietro dei PDF, senza uscire dal
contratto: `content`, invece di una stringa, diventa una lista di blocchi.

```jsonc
{
  "role": "user",
  "content": [
    {"tipo": "documento", "nome": "verbale.pdf", "dati": "<il PDF in base64>"},
    {"tipo": "testo", "testo": "Di cosa parla? Chi erano i presenti?"}
  ]
}
```

Un blocco è `{"tipo": "testo", "testo": "..."}` oppure
`{"tipo": "documento", "nome": "...", "dati": "<base64>"}` — o `"url"` al posto di
`"dati"`, con un indirizzo `https://` raggiungibile.

Vale tutto quello che è scritto in [API.md → Documenti](?f=api#documenti-pdf):
stessi limiti (5 documenti, 10 MB l'uno, 600 pagine — 100 sul modello economico),
stesso permesso per progetto (**Progetti → Documenti**, `403
documenti_non_abilitati` senza), e solo con Claude.

Le **foto** viaggiano nello stesso blocco, con `tipo_file`:
`{"tipo": "documento", "nome": "foto.jpg", "tipo_file": "image/jpeg", "dati": "<base64>"}`.
Valgono le regole di [API.md → Foto](?f=api#foto): JPEG, PNG, GIF, WebP, 5 MB
l'una, e un permesso loro (**Progetti → Immagini**, `403 immagini_non_abilitate`
senza).

**Le citazioni qui non ci sono.** In modalità agente la risposta esce a pezzi via
streaming e il contratto non ha un posto dove metterle. È la stessa scelta fatta
per il ragionamento esteso: meglio una capacità dichiaratamente assente che una
che si perde per strada. Chi ha bisogno delle citazioni usa `/api.php`.

## Il kit della chat

Se stai costruendo una chat sopra questo endpoint, non serve disegnarla da zero:
Pepper serve lui `pepper-chat.css` e `pepper-chat.js`, e si collegano così:

```html
<link rel="stylesheet" href="https://<pepper>/kit.php?f=css">
<div id="chat"></div>
<script src="https://<pepper>/kit.php?f=js"></script>
```

Sono due file senza librerie e sanno già leggere gli eventi di questa pagina:
bolla che si allunga mentre il testo arriva, pallino dell'attrezzo che Pepper sta
usando, riga di stato, il nome del Pepper con cui si parla sulla chat vuota — e la
riga di novità quando il modello cambia.

Per il nome sulla chat vuota, la pagina dice al kit a che progetto appartiene:
`PepperChat.attacca(el, {indirizzo: '/mio-server/chat', progetto: 'dossiero'})`.
Il kit chiede a `kit.php?progetto=dossiero` e scrive «Pepper 7» sopra «Come posso
aiutarti?». È il **nome** del progetto, non la chiave: `kit.php` restituisce solo il
nome del modello, niente altro.

**Collegati così, si aggiornano da soli.** Il giorno che nel kit si sistema una
cosa, arriva al ricaricamento dopo, in tutte le chat insieme — anche in quelle
che non abbiamo scritto noi. Non serve nessuna chiave: la chat parla col *tuo*
server, non con Pepper. `kit.php` risponde con un `ETag` e `Cache-Control:
public, no-cache`, quindi il browser se lo tiene e quasi sempre riceve un `304`
invece del file; `https://<pepper>/kit.php` senza parametri dice quale versione
del kit c'è adesso, se un deploy vuole saperlo senza scaricarlo.

Nessuna delle chat di casa ne tiene più una copia: il pannello di Pepper,
Dossiero, il pannello workspace e la V5 collegano tutti `kit.php`, e nessuno dei
quattro ha l'indirizzo scritto a mano — se lo ricava da quello dell'API che ha
già in configurazione, così il kit arriva sempre dallo *stesso* Pepper che poi
risponde.

Copiarli si può ancora, e per qualcuno resta obbligatorio: una chat che deve
funzionare con internet giù non può prendere il suo JavaScript da fuori. In quel
caso però la copia non si aggiorna a mano — vedi `scripts/kit-chat.sh` nel
repository di Pepper, che il gancio `post-commit` fa scattare da solo ogni volta
che il kit cambia.

Il kit capisce `delta`, `tool_call`, `done` ed `error` così come sono definiti qui,
più due eventi che **puoi mandare tu**: `tool_done {id, ok}`, per far diventare
verde (o rosso) il pallino dell'attrezzo quando l'hai eseguito, e `pending
{actions}`, per i bottoni di conferma di un'azione che Pepper ha proposto.
Pepper non può mandarli — gli attrezzi li esegui tu, e solo tu sai quando hanno
finito e cosa richiede una conferma.

Dentro ci sono anche allegati, lettura ad alta voce, microfono push-to-talk, i
bottoni di conferma e «Nuova chat» (che vuole `nuova: { quando }`: la cronologia
sta sul tuo server, e la cancella lui): si accendono una per una (`funzioni`), e di fabbrica sono
quasi tutte spente. L'elenco completo è nella pagina **API** del pannello di
Pepper.

Quello che **non** si accende è lo stile: l'a capo dopo ogni frase e il
grassetto valgono sempre, in tutte le chat, e la levetta per spegnerli non c'è
più. Erano due opzioni, e il risultato è stato che la stessa risposta di Pepper
si leggeva in due modi a seconda del progetto.

**Se ti serve qualcosa che non c'è, si aggiunge al kit e non al progetto**: è
l'unico modo perché il progetto dopo se la trovi già pronta. E se è una cosa di
aspetto, si cambia nel kit senza farne un'opzione.

Una raccomandazione che vale più del kit: **la chiave del progetto non deve mai
finire nel browser**. Il giro è sempre *pagina → il tuo server → Pepper*.

## Cosa non c'è nella v1

- **`tool_choice`** (forzare o vietare l'uso degli attrezzi). Attenzione se un
  giorno si aggiunge: sui motori nuovi (Opus 5.5, Fable 5.1) *forzare* un attrezzo
  (`any` o un attrezzo preciso) è un errore del provider; resta possibile solo
  `auto` o `none`.
- **La ricerca sul web di Pepper**: su `/api.php` si accende dal pannello, qui no —
  e non è una dimenticanza. In modalità agente gli attrezzi li dichiari tu: se
  vuoi che Pepper cerchi, la ricerca la metti fra i tuoi `tools` e la esegui tu,
  con le tue regole. È lo stesso principio di tutto il resto: **Pepper è il
  cervello, non le mani**.
- **Memoria lato Pepper**: in modalità agente la cronologia è del chiamante, per scelta.

## Configurazione nginx

L'URL `/agent` (senza `.php`) richiede una riga nel vhost:

```nginx
location = /agent {
    rewrite ^ /agent.php last;
}
```

Senza questa riga funziona comunque `POST /agent.php`.
Va inoltre verificato che il buffering non spezzi lo streaming: Pepper manda già
`X-Accel-Buffering: no`, che nginx rispetta, quindi normalmente non serve altro.

## Esempio — un giro completo

```bash
curl -N https://gateway.pepperai.it/agent \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -H "X-Pepper-Key: <chiave>" \
  -H "X-Pepper-Project: pepper-home" \
  -d '{
    "stream": true,
    "max_tokens": 1024,
    "system": "Sei Pepper, l assistente di casa. Proponi le azioni, non darle per fatte.",
    "messages": [{"role": "user", "content": "che aria fa in salone?"}],
    "tools": [{
      "name": "get_home_status",
      "description": "Stato sintetico di casa.",
      "parameters": {"type": "object", "properties": {}, "required": []}
    }]
  }'
```
