Blog

Luis Majano

May 14, 2026

Spread the word


Share your thoughts

BoxLang AI 3.2.0 is here, and it's a landmark release. We're shipping five major features: image generation, web search, a fluent audio builder API, a centralized agent registry, and deep MCP observability along with a suite of analytics improvements and a critical bug fix. Let's dig in. πŸŽ‰


πŸ–ΌοΈ Image Generation β€” aiImage()

You can now generate images directly from BoxLang using any provider that supports text-to-image generation. The aiImage() BIF follows the same fluent, chainable philosophy as the rest of bx-ai then act on the result with expressive method calls.

// Generate and save in one fluent chain
aiImage( "A futuristic cityscape at sunset" )
    .saveToFile( "/images/cityscape.png" )

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

// Embed directly in HTML output
dataURI = response.toDataURI()

The returned AiImageResponse object gives you everything you need: hasImages(), getCount(), getFirstURL(), getFirstBase64(), saveToFile(), saveAllToDirectory(), toDataURI(), getMimeType(), and toStruct().

Supported providers out of the box:

ProviderModelEnv Var
OpenAIgpt-image-1 (default), DALL-E modelsOPENAI_API_KEY
Geminiimagen-3.0-generate-008GEMINI_API_KEY
Grok / xAIgrok-2-imageGROK_API_KEY
OpenRouterFLUX Schnell (default), many othersOPENROUTER_API_KEY

A generateImage@bxai agent tool is auto-registered in the global tool registry at module startup, so your agents can generate images without any manual wiring:

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

πŸ“š Image Generation Docs


πŸ” Web Search β€” aiWebSearch() & aiWebSearchAsync()

BoxLang AI now ships a unified web search system with provider abstraction and normalized results. Every provider returns the same fields β€” title, url, snippet, publishedDate, domain, score, thumbnail, language β€” so you can swap providers without touching your code.

// Synchronous search
results = aiWebSearch( "latest BoxLang AI updates", { provider: "brave", maxResults: 8 } )

// Async β€” returns a BoxFuture
future = aiWebSearchAsync( "BoxLang release highlights", { provider: "tavily" } )
results = future.get()

Supported providers:

ProviderNotes
httpURL fetching & parsing β€” no API key required
bravePrivacy-focused; country/language filters
googleGoogle Custom Search
tavilyRetrieval-focused, great for AI agents
exaSemantic and neural search modes

The webSearch@bxai tool is auto-registered globally, so any agent can search the web immediately:

agent = aiAgent(
    name: "ResearchAgent",
    tools: [ "webSearch@bxai" ]
)

response = agent.run( "Find and summarize recent BoxLang AI release highlights" )

πŸ“š Web Search Docs


🎀 Fluent Builder API for Audio BIFs

aiSpeak(), aiTranscribe(), and aiTranslate() now support a full fluent builder API. Call any of them with no arguments to get the request object back, then chain your configuration before executing. The traditional positional-argument syntax continues to work exactly as before β€” the fluent builder is purely additive.

aiSpeak()

// Traditional syntax β€” still works
audio = aiSpeak( "Hello!", { voice: "nova" }, { provider: "openai" } )

// Fluent builder β€” expressive and self-documenting
audio = aiSpeak()
    .of( "Hello, world!" )
    .voice( "nova" )
    .provider( "openai" )
    .asMP3()
    .speak()

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

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

Key builder methods: .of(), .voice(), .male() / .female(), .speed(), .instructions(), .outputFile(), .asMP3() / .asWav() / .asFlac() / .asOpus() / .asPCM(), .provider(), .speak().

aiTranscribe()

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

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

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

Key builder methods: .file(), .url(), .data(), .language(), .withWordTimestamps(), .withSegmentTimestamps(), .diarize(), .asJSON() / .asText() / .asVerboseJSON() / .asSRT() / .asVTT(), .transcribe(), .translate().

aiTranslate()

english = aiTranslate()
    .file( "/audio/german.mp3" )
    .asText()
    .translate()

πŸ“š Audio Docs


πŸ€– Agent Registry β€” aiAgentRegistry()

3.2.0 introduces the AIAgentRegistry β€” a global singleton that gives you centralized discoverability, observability, and lifecycle management for all agents running in your BoxLang application.

// Auto-register at creation time
agent = aiAgent(
    name: "support-agent",
    description: "Customer support agent",
    register: true,
    module: "my-app"
)

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

// Discover what's running
agents = aiAgentRegistry().listAgents()
info   = aiAgentRegistry().getAgentInfo( "support-agent@my-app" )

// Resolve a mixed array of string keys and live instances
resolved = aiAgentRegistry().resolveAgents( [
    "support-agent@my-app",
    anotherAgentInstance
] )

// Clean up
aiAgentRegistry().unregister( "support-agent@my-app" )
aiAgentRegistry().unregisterByModule( "my-app" )

Module Authors: First-Class Agent & Tool Registration 🎯

This is a big deal for the BoxLang ecosystem. Developers building BoxLang modules can now ship agents and tools that auto-register themselves globally when the module loads β€” no manual wiring by the application developer required.

  • Define your aiAgent() instances with register: true and a module namespace
  • Define your tools, scan them via aiToolRegistry().scan( new MyTools(), "my-module" ), and they appear globally as toolName@my-module
  • Application developers can consume your agents and tools by name, from any part of their app, the moment your module is installed

This makes bx-ai a genuine platform for building composable, discoverable AI ecosystems β€” publish a module to ForgeBox, and your agents and tools show up ready to use. πŸš€

Two new interception points fire on registry changes: onAIAgentRegistryRegister and onAIAgentRegistryUnregister.


⏸️ MCP Server Pause/Resume

MCPServer now supports pausing and resuming without tearing down configuration or losing registered tools. Ideal for maintenance windows, graceful degradation, or controlled rollouts.

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

server.pause()

if ( server.isPaused() ) {
    println( "Server is paused β€” rejecting all non-ping requests" )
}

server.resume()
  • pause() β€” fires onMCPServerPause; all non-ping requests receive error code -32005
  • resume() β€” fires onMCPServerResume; normal handling restored
  • getSummary() now includes a paused boolean

πŸ“Š MCP Server & Client Observability

Server Analytics

MCP server monitoring gets a major overhaul in 3.2.0:

  • Thread-safe counters using named locks across all stat operations
  • Security failure tracking β€” auth failures, API key rejections, body-size violations all get dedicated counters
  • Per-tool error tracking β€” byTool[name].errors with errors.byTool roll-up
  • Active concurrent request counter β€” activeRequests increments and decrements in real time
  • Requests-per-minute rate β€” exposed in getSummary()
  • X-Request-ID correlation β€” request IDs echoed in response headers and event payloads
  • Paused-request stats β€” rejected requests tracked when server is paused
  • onMCPError now fires for METHOD_NOT_FOUND

Client Stats β€” MCPClient

MCPClient gains full internal usage and performance tracking:

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

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

// Inspect what's happening
stats   = client.getStats()   // per-operation, per-tool, per-URI breakdowns
summary = client.getSummary() // totalCalls, successRate, avgResponseTime

// Reset when needed
client.resetStats()

Three new interception points cover the full client lifecycle: onMCPClientRequest, onMCPClientResponse, onMCPClientError.


πŸ”§ Type-Aware Tool Argument Support

Tool schemas in bx-ai are now generated directly from callable parameter metadata, so LLMs finally receive accurate JSON Schema types for every argument instead of a flat bag of strings. ClosureTool.getArgumentsSchema() maps BoxLang types naturally β€” numeric, integer, float, and double become "number", boolean becomes "boolean", array becomes "array" with "items": {}, and struct becomes "object" β€” meaning LLMs can send native JSON values for non-string arguments and tools behave exactly as their signatures declare. On the output side, BaseTool.invoke() continues to serialize results consistently for provider compatibility, converting simple values via toString() and complex values via JSON serialization, keeping the tool contract clean in both directions. 🎯

// Tool with numeric and boolean arguments
// LLM sends { "quantity": 3, "applyDiscount": true } β€” no casting needed
calculateTotal = aiTool(
    name: "calculateTotal",
    description: "Calculate order total with optional discount",
    tool: ( numeric price, numeric quantity, boolean applyDiscount = false ) -> {
        total = price * quantity
        if ( applyDiscount ) total *= 0.9
        return { summary: "Order total calculated", total: total }
    }
)

// Tool with an array argument
// LLM sends { "tags": ["boxlang", "ai", "tools"] } β€” native array
tagContent = aiTool(
    name: "tagContent",
    description: "Apply a list of tags to a content item",
    tool: ( string contentId, array tags ) -> {
        // tags arrives as a real BoxLang array
        return {
            summary : "Tags applied to #contentId#",
            applied : tags.len(),
            tags    : tags
        }
    }
)

// Tool with a struct argument
// LLM sends { "filter": { "status": "active", "minAge": 18 } } β€” native struct
queryUsers = aiTool(
    name: "queryUsers",
    description: "Query users by filter criteria",
    tool: ( struct filter, numeric limit = 10 ) -> {
        results = userService.query( filter, limit )
        return {
            summary : "Found #results.len()# users",
            count   : results.len(),
            data    : results
        }
    }
)

agent = aiAgent(
    tools: [ calculateTotal, tagContent, queryUsers ]
)

πŸ› Bug Fix β€” ClosureTool.doInvoke() JSON Struct Handling

MCP clients that send JSON fields as real objects or arrays (rather than pre-stringified JSON) no longer cause "Can't cast Struct to a string" errors. doInvoke() now inspects declared parameters and calls jsonSerialize() on any non-simple value whose declared type is string. Silent, automatic, no code changes required.


πŸ“¦ Module Configuration

New image Settings Block

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

New Interception Points

3.2.0 brings bx-ai to 50 total interception points, adding 10 new events:

EventWhen Fired
beforeAIImageGenerationBefore image generation request
afterAIImageGenerationAfter image generation response
onAIImageRequestImage request object created
onAIImageResponseImage response received
onAIAgentRegistryRegisterAgent registered
onAIAgentRegistryUnregisterAgent unregistered
onMCPServerPauseMCP server paused
onMCPServerResumeMCP server resumed
onMCPClientRequestMCP client HTTP request
onMCPClientResponseMCP client HTTP response
onMCPClientErrorMCP client HTTP error

πŸš€ Upgrade Now

# CommandBox
box install bx-ai

# OS
install-bx-module bx-ai

πŸ“š Full Docs: ai.ortusbooks.com πŸ’¬ Community: community.ortussolutions.com ⭐ GitHub: github.com/ortus-boxlang/bx-ai

BoxLang AI 3.2.0 is a platform release: image generation, web search, fluent audio, a global agent & tool registry, and deep observability all land together. We can't wait to see what you build. πŸŽ‰

Add Your Comment

Recent Entries

MatchBox and WebAssembly: Running BoxLang in the Browser and at the Edge

MatchBox and WebAssembly: Running BoxLang in the Browser and at the Edge

The MatchBox open beta is live at https://boxlang.ortusbooks.com/boxlang-framework/matchbox, and it brings something genuinely new to the BoxLang ecosystem: a path into WebAssembly.

That means BoxLang code can now move into browser applications, static-site deployments, edge runtimes, and WASI-style containers - without requiring a JVM. The feature is still beta, but the core direction is already useful: write BoxLang, compile it with MatchBox, and ship the generated WASM artifact to wherever a small portable runtime makes sense.

Jacob Beers
Jacob Beers
June 04, 2026
One Language, Every Runtime: BoxLang Expands Beyond the Server

One Language, Every Runtime: BoxLang Expands Beyond the Server

Discover how BoxLang’s multi-runtime architecture helps developers build beyond the server with support for serverless functions, desktop applications, CI/CD workflows, Java integrations, containers, runtime management, and more.

Maria Jose Herrera
Maria Jose Herrera
June 04, 2026
BoxLang 1.14.0 : Introducing Inner Classes

BoxLang 1.14.0 : Introducing Inner Classes

BoxLang has always embraced a simple truth: the way you organize code shapes the way you think about problems. For a long time, if you needed a helper class, you needed a file. One class, one .bx file, no exceptions. That's clean and predictable, but it creates real friction when a class is tightly coupled to exactly one caller and has no business existing anywhere else.

Luis Majano
Luis Majano
June 03, 2026