# 3.2.0

**Released:** May 2026

BoxLang AI 3.2.0 introduces **image generation** (`aiImage()`), full **web search integration** (`aiWebSearch()` and `aiWebSearchAsync()`), a **fluent builder API** for all audio BIFs, the **Agent Registry** for centralized agent management, **MCP server pause/resume**, **MCP client stats & observability**, and major **MCP server analytics improvements**.

***

## ✨ New Features

### 🖼️ Image Generation — `aiImage()`

Generate images from text prompts using any provider that supports text-to-image generation.

| BIF                                  | Description                                         |
| ------------------------------------ | --------------------------------------------------- |
| `aiImage( prompt, params, options )` | Generate one or more images from a text description |

**Provider Support:**

| Provider       | Model                                  | Env Var              |
| -------------- | -------------------------------------- | -------------------- |
| **OpenAI**     | `gpt-image-1` (default), DALL-E models | `OPENAI_API_KEY`     |
| **Gemini**     | `imagen-3.0-generate-008`              | `GEMINI_API_KEY`     |
| **Grok / xAI** | `grok-2-image`                         | `GROK_API_KEY`       |
| **OpenRouter** | FLUX Schnell (default), many others    | `OPENROUTER_API_KEY` |

```javascript
// Generate an image and save to file
aiImage( "A futuristic cityscape at sunset" )
    .saveToFile( "/images/cityscape.png" )

// Generate with custom parameters
response = aiImage(
    "A watercolor painting of a mountain lake",
    { n: 2, size: "1024x1024", quality: "hd" },
    { provider: "openai" }
)

// Get image as data URI for embedding in HTML
dataURI = response.toDataURI()
```

**`AiImageResponse` Methods:**

| Method                      | Returns | Description                        |
| --------------------------- | ------- | ---------------------------------- |
| `hasImages()`               | boolean | `true` if images are present       |
| `getCount()`                | numeric | Number of generated images         |
| `getFirstURL()`             | string  | URL of the first image             |
| `getFirstBase64()`          | string  | Base64 of the first image          |
| `getRevisedPrompt()`        | string  | Provider's revised prompt (if any) |
| `saveToFile( path )`        | string  | Save first image to file           |
| `saveAllToDirectory( dir )` | array   | Save all images to directory       |
| `toDataURI()`               | string  | Data URI for HTML embedding        |
| `getMimeType()`             | string  | MIME type of the image             |
| `toStruct()`                | struct  | Metadata struct                    |

**`generateImage@bxai` Agent Tool:** Auto-registered in the global tool registry. Generates an image from a text prompt and returns the absolute file path.

```javascript
agent = aiAgent( tools: [ "generateImage@bxai" ] )
```

**Documentation**: [Image Generation](/main-components/image-generation.md)

***

### 🔍 Web Search Tools — `aiWebSearch()` and `aiWebSearchAsync()`

BoxLang AI now includes a unified web search system with normalized results and provider abstraction.

| BIF                                  | Description                                        |
| ------------------------------------ | -------------------------------------------------- |
| `aiWebSearch( query, options )`      | Synchronous web search across configured providers |
| `aiWebSearchAsync( query, options )` | Async web search returning a `BoxFuture`           |

**Providers:**

| Provider | API Key                                      | Notes                                                |
| -------- | -------------------------------------------- | ---------------------------------------------------- |
| `http`   | none                                         | URL fetching / parsing, no external API key required |
| `brave`  | `BRAVE_API_KEY`                              | Privacy-focused search with country/language filters |
| `google` | `GOOGLE_API_KEY` + `GOOGLE_SEARCH_ENGINE_ID` | Google Custom Search support                         |
| `tavily` | `TAVILY_API_KEY`                             | Retrieval-focused search for AI agents               |
| `exa`    | `EXA_API_KEY`                                | Semantic and neural search modes                     |

**Result format:** every provider returns the same fields: `title`, `url`, `snippet`, `publishedDate`, `domain`, `score`, `thumbnail`, `language`.

```javascript
// Direct usage
results = aiWebSearch( "latest BoxLang AI updates", { provider: "brave", maxResults: 8 } )

// Agent usage via auto-registered tool
agent = aiAgent(
    name: "ResearchAgent",
    tools: [ "webSearch@bxai" ]
)

response = agent.run( "Find and summarize recent BoxLang AI release highlights by running a web search" )
```

**Auto-Registered Tool:** `webSearch@bxai` is registered at module startup and available via `aiToolRegistry()`.

**Documentation**: [Web Search Tools](/main-components/web-search.md)

***

### 🎤 Fluent Builder API for Audio BIFs

`aiSpeak()`, `aiTranscribe()`, and `aiTranslate()` now support a **fluent builder API**. Calling any of these BIFs with no arguments returns the request object for method chaining.

#### `aiSpeak()` Fluent Builder

```javascript
// Traditional syntax
audio = aiSpeak( "Hello!", { voice: "nova" }, { provider: "openai" } )

// Fluent builder — call with no arguments
audio = aiSpeak()
    .of( "Hello!" )
    .voice( "nova" )
    .provider( "openai" )
    .asMP3()
    .speak()

// Gender shortcuts
audio = aiSpeak()
    .of( "Welcome!" )
    .male()
    .speed( 1.2 )
    .speak()

// Format shortcuts
audio = aiSpeak()
    .of( "Alert!" )
    .asWav()
    .outputFile( "/audio/alert.wav" )
    .speak()
```

**Builder Methods:**

| Method                                                           | Description                             |
| ---------------------------------------------------------------- | --------------------------------------- |
| `of( text )`                                                     | Set the text to synthesize              |
| `.text( text )`                                                  | Alias for `of()`                        |
| `.model( name )`                                                 | Set the TTS model                       |
| `.provider( name )`                                              | Set the provider                        |
| `.apiKey( key )`                                                 | Set the API key                         |
| `.voice( name )`                                                 | Set the voice                           |
| `.male()` / `.female()`                                          | Gender shortcut (resolved per provider) |
| `.speed( n )`                                                    | Set playback speed                      |
| `.instructions( text )`                                          | Set voice instructions                  |
| `.outputFile( path )`                                            | Set output file path                    |
| `.asMP3()` / `.asWav()` / `.asFlac()` / `.asOpus()` / `.asPCM()` | Format shortcuts                        |
| `.withParams( struct )`                                          | Set provider params                     |
| `.withOptions( struct )`                                         | Set module options                      |
| `.withLogging()`                                                 | Enable request/response logging         |
| `.speak()`                                                       | Execute and return `AiSpeechResponse`   |

#### `aiTranscribe()` Fluent Builder

```javascript
// Traditional syntax
text = aiTranscribe( "/audio/meeting.mp3" )

// Fluent builder
text = aiTranscribe()
    .file( "/audio/meeting.mp3" )
    .withWordTimestamps()
    .asVerboseJSON()
    .transcribe()

// From URL
text = aiTranscribe()
    .url( "https://example.com/audio.mp3" )
    .language( "es" )
    .transcribe()

// From binary data
text = aiTranscribe()
    .data( audioBinary )
    .diarize( true )
    .transcribe()

// Translate audio to English (dual terminator)
english = aiTranscribe()
    .file( "/audio/french.mp3" )
    .translate()
```

**Builder Methods:**

| Method                                                                   | Description                           |
| ------------------------------------------------------------------------ | ------------------------------------- |
| `of( audio )`                                                            | Static factory — set audio input      |
| `.file( path )`                                                          | Set audio file path                   |
| `.url( url )`                                                            | Set audio URL                         |
| `.data( binary )`                                                        | Set raw binary audio data             |
| `.model( name )`                                                         | Set the STT model                     |
| `.provider( name )`                                                      | Set the provider                      |
| `.apiKey( key )`                                                         | Set the API key                       |
| `.language( code )`                                                      | Set input audio language (BCP-47)     |
| `.inputFormat( fmt )`                                                    | Set input audio format                |
| `.withWordTimestamps()`                                                  | Enable word-level timestamps          |
| `.withSegmentTimestamps()`                                               | Enable segment-level timestamps       |
| `.withTimestamps()`                                                      | Enable all timestamps                 |
| `.diarize( bool )`                                                       | Enable speaker diarization            |
| `.asJSON()` / `.asText()` / `.asVerboseJSON()` / `.asSRT()` / `.asVTT()` | Output format shortcuts               |
| `.withParams( struct )`                                                  | Set provider params                   |
| `.withOptions( struct )`                                                 | Set module options                    |
| `.withLogging()`                                                         | Enable request/response logging       |
| `.transcribe()`                                                          | Execute transcription                 |
| `.translate()`                                                           | Execute translation (audio → English) |

#### `aiTranslate()` Fluent Builder

```javascript
// aiTranslate() also supports the fluent builder
english = aiTranslate()
    .file( "/audio/german.mp3" )
    .asText()
    .translate()
```

**Documentation**: [Audio — Speech & Transcription](/main-components/audio.md)

***

### 🤖 Agent Registry

New `AIAgentRegistry` singleton (access via `aiAgentRegistry()` BIF) for centralized agent discoverability, observability, and analytics.

```javascript
// Register an agent
agent = aiAgent(
    name: "support-agent",
    description: "Customer support agent",
    register: true,
    module: "my-app"
)

// Or register manually
aiAgentRegistry().register( agent, "my-app" )

// List all registered agents
agents = aiAgentRegistry().listAgents()

// Get agent info
info = aiAgentRegistry().getAgentInfo( "support-agent@my-app" )

// Resolve agents from mixed array
resolved = aiAgentRegistry().resolveAgents( [
    "support-agent@my-app",
    anotherAgentInstance
] )

// Unregister
aiAgentRegistry().unregister( "support-agent@my-app" )
aiAgentRegistry().unregisterByModule( "my-app" )
```

**Registry API:**

| Method                         | Description                                                |
| ------------------------------ | ---------------------------------------------------------- |
| `register( agent, module )`    | Register an agent with optional module namespace           |
| `unregister( key )`            | Remove agent by key                                        |
| `unregisterByModule( module )` | Remove all agents from a module                            |
| `resolveAgents( array )`       | Resolve mixed array of string keys and `AiAgent` instances |
| `listAgents()`                 | Return struct of all registered agents                     |
| `getAgentInfo( key )`          | Return `{ name, description, module }` for a key           |

**New Events:** `onAIAgentRegistryRegister`, `onAIAgentRegistryUnregister`

***

### ⏸️ MCP Server Pause/Resume

`MCPServer` now supports pausing and resuming without destroying configuration.

```javascript
server = MCPServer( "my-tools", "Provides custom tools" )
    .registerTool( myTool )

// Pause — rejects all requests except ping
server.pause()

// Check status
if ( server.isPaused() ) {
    println( "Server is paused" )
}

// Resume — normal request handling restored
server.resume()
```

* `pause()` — fires `onMCPServerPause` interception point
* `resume()` — fires `onMCPServerResume` interception point
* `isPaused()` — returns `true` if currently paused
* `getSummary()` now includes `paused` boolean
* New error code: `SERVER_PAUSED: -32005`

***

### 📊 MCP Server Observability & Analytics

Major improvements to MCP server monitoring:

* **Thread-safe counters**: All stats counters now use named locks for concurrent safety
* **Security failure tracking**: Dedicated counters for auth failures, API key rejections, body-size violations
* **Paused-request stats**: Rejected requests now tracked in stats
* **Per-tool error tracking**: `byTool[name].errors` and `errors.byTool` roll-up
* **Active concurrent request counter**: `activeRequests` tracked with increment/decrement
* **Requests-per-minute rate**: `getSummary()` includes `requestsPerMinute`
* **X-Request-ID correlation**: Request IDs echoed in response headers and event payloads
* **`onMCPError` for METHOD\_NOT\_FOUND**: Now fires the interception point

***

### 📈 MCP Client Stats & Observability

`MCPClient` now tracks internal usage and performance metrics.

```javascript
client = MCP( "http://localhost:3000" )

// Make some calls
tools = client.listTools()
result = client.callTool( "search", { query: "BoxLang" } )

// Get stats
stats = client.getStats()
summary = client.getSummary()

// Reset counters
client.resetStats()
```

**Stats API:**

| Method         | Description                                                             |
| -------------- | ----------------------------------------------------------------------- |
| `getStats()`   | Full stats struct with per-operation, per-tool, per-URI breakdowns      |
| `getSummary()` | Lightweight summary with `totalCalls`, `successRate`, `avgResponseTime` |
| `resetStats()` | Reset all counters to zero                                              |

**New Events:** `onMCPClientRequest`, `onMCPClientResponse`, `onMCPClientError`

***

### 🐛 Fixed

* **`ClosureTool.doInvoke()`**: MCP clients sending JSON fields as real objects/arrays (instead of pre-stringified JSON) no longer cause "Can't cast Struct to a string" errors. The fix walks declared parameters and `jsonSerialize()`s any non-simple value whose declared type is `string`.

***

## 📦 Module Configuration Updates

### Image Settings

New `image` settings block in `boxlang.json`:

```json
{
  "modules": {
    "bxai": {
      "settings": {
        "image": {
          "defaultProvider": "openai",
          "defaultApiKey": "",
          "defaultModel": "gpt-image-1",
          "defaultSize": "1024x1024",
          "defaultQuality": "standard",
          "defaultStyle": "vivid",
          "defaultInstructions": ""
        }
      }
    }
  }
}
```

### New Interception Points

40 → **50** total interception points. New events in 3.2.0:

| #  | Event                         | When Fired                      |
| -- | ----------------------------- | ------------------------------- |
| 42 | `beforeAIImageGeneration`     | Before image generation request |
| 43 | `afterAIImageGeneration`      | After image generation response |
| 44 | `onAIImageRequest`            | Image request object created    |
| 45 | `onAIImageResponse`           | Image response received         |
| 46 | `onAIAgentRegistryRegister`   | Agent registered                |
| 47 | `onAIAgentRegistryUnregister` | Agent unregistered              |
| 48 | `onMCPServerPause`            | MCP server paused               |
| 49 | `onMCPServerResume`           | MCP server resumed              |
| 50 | `onMCPClientRequest`          | MCP client HTTP request         |
| 51 | `onMCPClientResponse`         | MCP client HTTP response        |
| 52 | `onMCPClientError`            | MCP client HTTP error           |

***

## 🔄 Migration Guide

### Audio BIFs — Fluent Builder

The traditional `aiSpeak( text, params, options )` syntax continues to work unchanged. The fluent builder is an **additional** option — no migration required.

### Agent Registry

The `aiAgent()` BIF gains two new optional parameters:

* `register: false` (default) — set to `true` to auto-register the agent
* `module: ""` — module namespace for the registry key

Existing agent creation code works unchanged.

### MCP Server

`MCPServer` gains `pause()`, `resume()`, and `isPaused()` methods. Existing server code works unchanged.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ai.ortusbooks.com/readme/release-history/3.2.0.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
