Showing posts with label code. Show all posts
Showing posts with label code. Show all posts

Pipi Workspace and Gemini integration

Mike's Notes

Yesterday (Wednesday NZ time), I attended the Google Startups Product Roadmap Seminar: October 2026 via Google Meet.

"Join Google Cloud and Wiz specialists to explore the latest in startup security and Gemini, with a deep dive into our newest models. This session is exclusively tailored for our strategic Google Cloud startup customers." - Google

There were 2 presentations

  • Gemini Roadmap: Agent Updates & Antigravity (Lia Passaglia: Cloud AI Specialist Engineer)
  • Wiz Roadmap: AI Threat Readiness (Ian Wehba: Cloud Security Customer Engineer Manager)

Wiz is impressive. 😎

    These great presentations got me thinking, and this morning I solved this problem on the big list.

    "How to connect any LLM model, starting with Gemini, to a Pipi workspace so that an Ajabbi customer can use AI tools without the AI changing the actual workspace code. Everything must run in CFML on Ortus BoxLang, starting from Pipi 10."

    Update 23/10/2026

    The AGNTCon + MCPCon North America is on today (5 am to 2 pm NZ time), and I'm watching it live. The unforgettable Demetrios Brinkmann from AAIF is the host. Fantastic coverage of many of the issues in great depth, connected with solving this problem;

    • Security
    • Scaling
    • Tuning
    • etc

    I'll watch the entire conference on replay later.

    Resources

    References

    • Reference

    Repository

    • Home > Ajabbi Research > Library >
    • Home > Handbook > 

    Last Updated

    23/10/2026

    Pipi Workspace and Gemini integration

    By: Mike Peters and Google Search AI Mode (Gemini)
    On a Sandy Beach: 22/10/2026

    Mike Peters: Mike invented and designed Pipi and founded Ajabbi.

    Google Search AI Mode (Gemini): GSAM is great.

    ...

    Today, using Google Search AI Mode, I asked some very dumb questions about using connectors. I did this to find a way to safely connect any workspace generated by Pipi running in CFML on BoxLang to Gemini. Originally, MCP was the top contender. 

    Here is the raw output, with Grammarly post-editing; I'll rework the code manually. This is a good place to start.

    Boxlang AI Module is free and open-source.

    Remember, "Use this code with caution." 😎😎😎😎


    Mike: What is a connector for Google Gemini?


    Gemini: A Google Gemini connector is a secure bridge that links Google Gemini with external apps, databases, and third-party software.

    Depending on the context, a "connector" refers to either data-ingestion tools built into enterprise versions of Gemini or third-party integrations that let other software platforms use Gemini’s AI model capabilities.

    1. Gemini Enterprise Connectors (Data Ingestion)

    In Gemini Enterprise (Business, Standard, and Plus editions), IT administrators manage connectors to securely bring scattered organisational data into Gemini. This allows users to search, summarise, and analyse their company's internal information using natural language.

    • Google Connectors: Link seamlessly to core Google Workspace apps such as Gmail, Google Drive, Google Calendar, Chat, and Google Sites.
    • Third-Party Connectors: Provide native access to external business ecosystems. Gemini Enterprise supports over 100 integrations, including Microsoft 365 (SharePoint, OneDrive), Atlassian (Jira, Confluence), Salesforce, ServiceNow, Slack, and Zendesk.
    • Custom Connectors: If a specific proprietary database or software isn't in the standard catalogue, companies can build custom connectors using the Google Cloud Discovery Engine API to fetch, transform, and sync their data.

    2. Outbound Connectors (AI for External Apps)

    Conversely, many software developers use "Gemini Connectors" to inject Gemini's AI capabilities into other software. In this scenario, the connector acts as an API plugin. Examples include:

    • Automation Platforms: Tools like Camunda and Microsoft Power Automate utilise a custom Gemini connector so business processes can automatically generate text, analyse sentiment, or extract keywords from files.
    • App Development Modules: Low-code platforms like Mendix feature pre-packaged Gemini modules that let developers easily build AI chatbots, translate languages, or parse large blocks of text directly inside their applications.

    Gemini: Are you looking to connect your enterprise data (like Jira or SharePoint) to Gemini, or are you trying to build Gemini's AI features into an external app you are developing? Let me know so I can provide the exact steps or documentation links!

    Mike: I want to find a way to connect an enterprise SaaS I built to integrate with Gemini, so that Gemini can edit the content stored in the SaaS, but not the SaaS itself.


    Gemini: To connect your enterprise SaaS so Gemini can manipulate content without threatening your core application codebase or infrastructure, you should implement Function Calling via the Gemini Enterprise Agent Platform (formerly Vertex AI) or the native Gemini developer API.

    With Function Calling, Gemini doesn't execute code inside your application and has no direct access to your codebase. Instead, it acts as a decision-maker: you provide a strict blueprint of the content-editing APIs it can use, and Gemini replies with a request containing the specific content edits to make.

    How the Security Boundary Works

    Because Gemini lives on Google's servers, it can only interact with your SaaS through the specific API endpoints you choose to expose.

    • Safe (Content Editing): You expose a PATCH /api/v1/documents/{id} or PUT /api/v1/content endpoint. Gemini can request changes to text or data fields.
    • Isolated (SaaS Integrity): You never give Gemini endpoints that can rewrite your code, alter your deployment configurations, drop database tables, or access server SSH keys. Gemini literally cannot touch the SaaS itself because no such API actions are made available to it.

    Architecture: The 4-Step Tool Loop

    When a user gives Gemini an instruction (e.g., "Fix the typos in the project brief stored in the SaaS"), the following cycle occurs:

    ┌──────┐  1. User Prompt + Function Schema   ┌────────┐

    │ Your │ ──────────────────────────────────> │        │

    │ App  │ <────────────────────────────────── │ Gemini │

    └──────┘       2. JSON Function Call         └────────┘

       │                                              ▲

       │ 3. Your App executes internal logic         │

       │    & patches the SaaS database               │ 4. Final Text Response

       ▼                                              │

    ┌──────┐                                          │

    │ SaaS │ ─────────────────────────────────────────┘

    └──────┘

    1. You Define the Blueprint: When you start a session using the Gemini API or the standard Interactions API, you pass a list of available tools in a JSON schema.
    2. Gemini Decides, It Does Not Execute: Gemini parses the user's request, notices that it needs to edit content, and spits back a structured JSON command (a functionCall) specifying which tool to use and what arguments to supply (e.g., document_id: "123", new_text: "...").
    3. Your SaaS Validates and Executes: Your application receives Gemini's JSON payload. Your server checks user permissions, sanitises the inputs, executes the actual database write, and generates a success confirmation.
    4. Closing the Loop: Your backend returns the execution results (functionResponse) back to Gemini, which then formats a friendly final confirmation message for the end user.

    Code Example: Exposing Content-Only Tools

    When configuring the model using a modern environment like Google AI Studio or your backend application SDK, you restrict Gemini's access directly in the tools array.

    javascript

    import { GoogleGenAI } from '@google/genai';
    const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
    // 1. Define only content-centric functions. 
    // Do NOT include system management or code modification tools here.
    const updateSaasContentTool = {
      functionDeclarations: [{
        name: 'updateSaasContent',
        description: 'Updates the text body or content of a specific resource inside the SaaS database.',
        parameters: {
          type: 'OBJECT',
          properties: {
            contentId: { type: 'STRING', description: 'The unique ID of the document or record.' },
            updatedText: { type: 'STRING', description: 'The newly edited or corrected text content.' }
          },
          required: ['contentId', 'updatedText']
        }
      }]
    };
    // 2. Pass the content tool into the Gemini model instance
    const chat = ai.chats.create({
      model: 'gemini-2.5-flash', // Or your preferred Gemini model
      config: {
        tools: [updateSaasContentTool], 
        systemInstruction: "You are an assistant that edits customer data records within our SaaS system. You only have access to modify data fields via the provided tools."
      }
    });

    Best Practices for Absolute Isolation

    • Least Privilege API Keys: Ensure the API key or OAuth token your application uses to execute the function calls only has read/write permissions for user content data—never admin or infrastructure roles.
    • Strict Schema Validation: Always treat Gemini’s JSON output as untrusted user input. Validate the fields on your server side before writing to your database.
    • The Model Context Protocol (MCP): If you want an open-source, standardised framework for securely exposing your SaaS data tools to Gemini, consider building an MCP Server. Gemini natively supports connecting to external data ecosystems via MCP server setups.


    Gemini: What language or framework is your SaaS backend built in (e.g., Node.js, Python, Go)? I can provide the exact code block for handling the functionCall feedback loop on your server.

    Mike: The SaaS backend is built in CFML.


    Gemini: Implementing Function Calling in a CFML backend (such as Lucee or Adobe ColdFusion) requires managing the request and response cycle via standard HTTP calls, as there isn't a native Google-maintained SDK for CFML.

    Because Gemini only sends back JSON instructions, your CFML application retains absolute control. Gemini decides what to edit, but your CFML components (.cfc) validate the parameters, check user sessions, and execute the actual SQL or ORM operations to modify the content database.

    The CFML Integration Workflow

    The integration is split into two phases inside your CFML application:

    1. The Request: Sending the user prompt alongside your defined CFML tool schema to Gemini.
    2. The Execution: Parse Gemini's response, run your internal CFML logic, and send the results back to complete the loop.

    Phase 1: Sending the Tool Definition to Gemini

    You will send a POST request to the Gemini API (/v1beta/models/gemini-2.5-flash:generateContent). Include your system boundaries and the explicit content-editing functions in the tools array.

    cfml

    <!--- Define the tools Gemini is allowed to use --->
    <cfset toolsPayload = [
        {
            "functionDeclarations": [
                {
                    "name": "editSaaSContent",
                    "description": "Edits the text body of a specific record inside the SaaS database. Use this when the user asks to modify, format, or fix text content.",
                    "parameters": {
                        "type": "OBJECT",
                        "properties": {
                            "recordID": { "type": "STRING", "description": "The unique UUID or ID of the content record." },
                            "newContent": { "type": "STRING", "description": "The newly edited text content." }
                        },
                        "required": ["recordID", "newContent"]
                    }
                }
            ]
        }
    ]>
    <!--- Prepare the request body --->
    <cfset requestBody = {
        "contents": [
            { "role": "user", "parts": [{ "text": "Fix the grammatical errors in document ID 98765" }] }
        ],
        "systemInstruction": {
            "parts": [{ "text": "You are a content assistant. You can only modify data records using the provided tools. You cannot touch SaaS infrastructure or codebase." }]
        },
        "tools": toolsPayload
    }>
    <!--- Make the API call to Gemini --->
    <cfhttp url="https://googleapis.com" method="POST" result="geminiResponse">
        <cfhttpparam type="header" name="Content-Type" value="application/json">
        <cfhttpparam type="body" value="#serializeJSON(requestBody)#">
    </cfhttp>

    Phase 2: Processing Gemini’s Decision in CFML

    Gemini will not return a text answer. Instead, it will return a functionCall object. Your CFML code must intercept this, verify it, run your safe database update, and send the result back to Gemini.

    Here is how you handle that response securely:

    cfml

    <cfif geminiResponse.statusCode CONTAINS "200">
        <cfset responseData = deserializeJSON(geminiResponse.fileContent)>
        
        <!--- Check if Gemini wants to call your content-editing tool --->
        <cfif structKeyExists(responseData, "candidates") 
              AND arrayLen(responseData.candidates) GT 0 
              AND structKeyExists(responseData.candidates[1].content.parts[1], "functionCall")>
              
            <cfset functionCall = responseData.candidates[1].content.parts[1].functionCall>
            
            <!--- Ensure Gemini is calling the expected tool --->
            <cfif functionCall.name EQ "editSaaSContent">
                <cfset args = functionCall.args>
                
                <!--- SECURITY: Safely validate incoming parameters before touching the database --->
                <cfif isValid("guid", args.recordID) OR isValid("integer", args.recordID)>
                    
                    <!--- Execute your native CFML database update --->
                    <cftry>
                        <query execute="UPDATE saas_content_table SET body_text = :newText WHERE id = :id" 
                               params="{ newText: {value=args.newContent, cfsqltype='cf_sql_clob'}, id: {value=args.recordID, cfsqltype='cf_sql_varchar'} }">
                        
                        <cfset executionStatus = "Success: Content has been updated inside the SaaS database.">
                        
                        <cfcatch type="any">
                            <cfset executionStatus = "Error: Failed to write to database. #cfcatch.message#">
                        </cfcatch>
                    </cftry>
                    
                <cfelse>
                    <cfset executionStatus = "Error: Invalid record ID format provided by AI.">
                </cfif>
                
                <!--- Final Step: Send the execution result back to Gemini so it can reply to the user --->
                <cfset followUpBody = {
                    "contents": [
                        <!--- You must include the original conversation history here, followed by: --->
                        {
                            "role": "function",
                            "parts": [{
                                "functionResponse": {
                                    "name": "editSaaSContent",
                                    "response": { "output": executionStatus }
                                }
                            }]
                        }
                    ]
                }>
                
                <!--- Post this back to Gemini to get the final conversational confirmation message --->
                
            </cfif>
        </cfif>
    </cfif>

    Keeping the Boundary Enforced in CFML

    • Session Validation: Do not rely solely on Gemini knowing who the user is. Before running the <cfquery> update, verify that the logged-in user session (session.userID) actually has permission to modify args.recordID.
    • Input Sanitisation: Use cfqueryparam (or query params inside script syntax as shown above) without exception. This completely stops Gemini from performing SQL Injection if it goes rogue or gets manipulated by a malicious user prompt (Prompt Injection).
    • Isolation of Admin CFCs: Keep this integration code inside a separate, non-administrative Component (e.g., aiService.cfc). Never pass any methods that handle server maps, application reloads (url.init), or file system manipulation (<cffile>) into the tools definition payload.


    Gemini: Are you using a specific CFML framework like ColdBox, Framework One (FW/1), or a legacy/custom legacy codebase? I can help tailor the controller architecture or routing settings to match your current setup.

    Mike: Custom legacy CFML codebase running on Boxlang.


    Gemini: Running on BoxLang, you have a massive architectural advantage. Because BoxLang includes a native, unified ecosystem called BoxLang AI (bx-ai), you do not need to mess with raw JSON schemas or manual HTTP requests like traditional CFML setups require.

    BoxLang AI acts as a multi-tenant orchestration layer. It allows you to wrap your legacy .cfm/.cfc functions as native AI Tools that Gemini can call.

    The Security Sandbox Strategy

    To ensure Gemini can edit data fields but cannot touch files, code, or the application framework, wrap your legacy database modification logic in a dedicated BoxLang AI tool execution loop.

    1. Expose only raw data components: Only register data-centric components with the BoxLang AI agent.
    2. Context isolation: BoxLang AI isolates system contexts, so Gemini sees only the specific functions you register via bx-ai. It has zero visibility into the underlying BoxLang JVM runtime, application maps, or file headers.

    Step-by-Step Implementation with BoxLang AI

    1. Configure the Gemini Provider

    In your configuration file (or programmatically at app startup), specify Google Gemini as your LLM driver.

    cfml

    // Setup your provider details securely
    application.aiConfig = {
        "provider": "google",
        "model": "gemini-2.5-flash",
        "apiKey": "#server.system.environment.GEMINI_API_KEY#"
    };

    2. Create the Legacy CFML Tool Component (ContentEditor.cfc)

    Write a clean, isolated component that handles the actual content-editing task. BoxLang uses metadata annotations to communicate tool definitions to Gemini.

    cfml

    component hint="Exposes database editing boundaries to Gemini via BoxLang AI" {
        /**
         * Updates the text body of a specific SaaS record. 
         * @recordID The unique primary key or UUID of the content row.
         * @newText The finalized text generated by Gemini to inject into the row.
         */
        public string function modifyRecordText( required string recordID, required string newText ) {
            
            // 1. SECURITY CRITICAL: Session/Role authorization check
            if ( !structKeyExists( session, "userID" ) ) {
                return "Error: Unauthorized execution block.";
            }
            // 2. Perform safe, isolated SQL write
            try {
                queryExecute(
                    "UPDATE saas_user_content SET body_content = :newText WHERE id = :id AND user_id = :uid",
                    {
                        newText: { value=arguments.newText, cfsqltype="cf_sql_clob" },
                        id: { value=arguments.recordID, cfsqltype="cf_sql_varchar" },
                        uid: { value=session.userID, cfsqltype="cf_sql_varchar" }
                    }
                );
                return "Success: Database updated successfully.";
            } catch ( any e ) {
                return "Database insertion failure: " & e.message;
            }
        }
    }

    3. Execute via the BoxLang AI Agent

    In your controller or execution script, register the ContentEditor.cfc as an active tool inside the BoxLang AI interface. BoxLang automatically converts your component methods into the function schemas Gemini expects.

    cfml

    // 1. Instantiate the BoxLang AI service manager
    aiService = new bx.ai.models.AIService( application.aiConfig );
    // 2. Create an agent and bind our safe legacy tool component
    agent = aiService.newAgent(
        systemInstruction = "You are a content assistant. You format, correct, and edit user content strings. You have NO permission to manage software infrastructure or access code files."
    );
    // BoxLang registers the component methods natively as tools Gemini can read
    agent.addTool( new path.to.components.ContentEditor() );
    // 3. Process incoming prompt
    userPrompt = "Please clean up the grammar inside item ID: ABC-12345";
    aiResponse = agent.chat( userPrompt );
    // BoxLang automatically intercepts Gemini's tool requests, matches it to your 
    // component method, executes the SQL, and handles the chat loop.
    writeOutput( aiResponse.getText() );

    Why BoxLang Safeguards Your Legacy Codebase

    • Automatic Reflection Parsing: BoxLang reads the doc comments (@recordID, @newText) of your legacy components natively. It writes the precise JSON schemas Gemini needs without exposing your component's internal SQL or actual source path.
    • Prompt Injection Defence: Because you execute database updates using standard BoxLang queryExecute bindings, any attempt by a user to manipulate Gemini into writing malicious SQL code is stopped via parameter isolation at the JVM database driver level.
    • Process Isolation: The model never touches file interpreters or runtime evaluations (evaluate()). It is entirely a "data-in, data-out" loop passing string arguments back into your custom CFML method.

    Gemini: Are you using CommandBox to manage this runtime environment, or are you utilizing the new BoxLang Spring Boot Starter / MiniServer configurations? Let me know if you want help mapping environment secrets or handling cross-origin requests securely.

    Mike: Is there a need to create a MCP server in this situation?


    Gemini: No, you do not need to create an MCP (Model Context Protocol) server for this specific situation.

    Since you are running your legacy CFML codebase on BoxLang, you can use the built-in bx-ai module to handle Gemini’s function calling out of the box. Creating an independent MCP server would add unnecessary architectural overhead.

    The distinction between the two approaches explains why the native BoxLang AI method is more effective for your current setup:

    1. Traditional Native BoxLang AI (bx-ai) Approach

    In the solution detailed above, you use BoxLang's native AI orchestration.

    • How it works: Your SaaS application initiates the API call to Gemini, provides the component tools directly, intercepts the response, and runs the local CFML code.
    • Why it's sufficient: The AI loop is fully contained inside your existing application runtime. There is no middleman.

    2. The MCP (Model Context Protocol) Alternative

    The Model Context Protocol (introduced by Anthropic) is designed to solve the "N×M integration problem"—enabling any AI model to talk to any software platform via a universal, standardised JSON-RPC protocol without custom API adapters.

    If you were to build an MCP server for your SaaS, the architecture would look like this:

    Gemini AI Client ⇄ Your Custom BoxLang MCP Server (JSON-RPC) ⇄ Your SaaS Database

    Head-to-Head Comparison

    Feature Native BoxLang AI (bx-ai) Building a Custom MCP Server
    Architectural Overhead Very Low. Built directly into your existing app. Medium. Requires setting up a persistent HTTP/SSE or STDIO listener.
    Security Handling Easier. Inherits your user's live session (session.userID) naturally during the web request. Harder. You must implement a separate token-auth protocol so the MCP server knows which tenant's data is being edited.
    When to use it When you control the app interface and want Gemini to act as an integrated feature inside your SaaS. When you want external AI clients (like Claude Desktop, Cursor, or ChatGPT) to connect directly into your SaaS database.

    The Only Exception: BoxLang's Built-In bx-mcp

    The creators of BoxLang (Ortus Solutions) shipped a native module called bx-mcp. However, this module is specifically used to expose BoxLang runtime diagnostics and server monitoring to AI coding assistants (like Cursor or Claude Desktop) so they can help you debug your server. It is not meant for business-logic data editing inside a SaaS platform.

    Final Recommendation

    Stick to the native BoxLang AI tool registration (agent.addTool()) approach. It keeps your security boundaries tied straight to your legacy CFML session state, eliminates the need to route traffic through external protocol translators, and requires significantly less code to maintain.


    Gemini: Is this Gemini integration meant to be a user-facing feature inside your SaaS dashboard, or is it an internal automation tool for your development team? Knowing the end-user environment helps determine the best way to handle user authentication.

    Mike: It will be a user-facing feature inside the SaaS dashboard. Drafting emails, writing reports, etc.


    Gemini: Since this is a user-facing feature inside your SaaS dashboard, security boundaries shift from protecting infrastructure to protecting tenant data isolation. You must guarantee that User A cannot trick Gemini into modifying or reading User B's content (a vulnerability known as Indirect Prompt Injection).

    Using the native BoxLang AI Agent framework, your legacy CFML codebase can safely contain this interaction within the user's active session.

    The 3-Tier Security Architecture

    Because Gemini operates statelessly, it doesn't know who is logged into your application. Your CFML controller acts as the gatekeeper by wrapping the execution cycle:

    [ User Dashboard ] 

           │ 1. Submits Prompt

           ▼

    [ BoxLang Controller ] ──( 2. Restricts Tool to Session User ID )──► [ Gemini API ]

           │                                                                   │

           │ 4. Executes SQL restricted by User ID                             │ 3. Returns Function Call

           ▼                                                                   ▼

    [ SaaS Content DB ] ◄──────────────────────────────────────────────────────┘

    Production-Ready BoxLang Implementation

    Here is how you handle the user's dashboard request safely. This ensures Gemini can draft emails and write reports only within the strict scope of that specific logged-in user.

    1. The Secure Legacy Component (ContentManager.cfc)

    To lock Gemini down, the component's internal SQL must strictly filter on the user's session identifier.

    cfml

    component hint="Handles user-facing AI content operations securely." {
        /**
         * Creates a brand new document draft (like an email or report) for the logged-in user.
         * @title The descriptive title of the document, email, or report.
         * @body The AI-generated text body content.
         */
        public struct function createDraftDocument( required string title, required string body ) {
            
            // 1. HARD SECURITY WALL: Verify user is actively authenticated
            if ( !structKeyExists( session, "userID" ) || !len( session.userID ) ) {
                return { "success": false, "message": "Authentication required." };
            }
            try {
                // 2. TENANT ISOLATION: The session.userID is hardcoded into the write operation
                var newID = createUUID();
                queryExecute(
                    "INSERT INTO saas_documents (id, user_id, title, body_content, status, created_at) 
                     VALUES (:id, :uid, :title, :body, 'draft', NOW())",
                    {
                        id:    { value=newID, cfsqltype="cf_sql_varchar" },
                        uid:   { value=session.userID, cfsqltype="cf_sql_varchar" },
                        title: { value=arguments.title, cfsqltype="cf_sql_varchar" },
                        body:  { value=arguments.body, cfsqltype="cf_sql_clob" }
                    }
                );
                return { 
                    "success": true, 
                    "message": "Draft securely created.", 
                    "documentID": newID 
                };
            } catch ( any e ) {
                return { "success": false, "message": "Failed to save draft system-side." };
            }
        }
    }

    2. The Dashboard Endpoint (/api/generate.cfm)

    When the user clicks "Draft Email" or "Write Report" on the UI, your legacy backend endpoint intercepts the action, provisions the BoxLang AI agent, and executes the sequence.

    cfml

    <cfsetting showdebugoutput="false">
    <cfcontent type="application/json">
    <!--- Basic session protection checklist --->
    <cfif !structKeyExists( session, "userID" )>
        <cfset writeOutput( serializeJSON({ "error": "Unauthorized session context." }) )>
        <cfabort>
    </cfif>
    <!--- Read incoming AJAX request payload from dashboard front-end --->
    <cfset requestBody = toString( getPageContext().getRequest().getInputStream() )>
    <cfset payload = isJSON( requestBody ) ? deserializeJSON( requestBody ) : {}>
    <cfset userPrompt = structKeyExists( payload, "prompt" ) ? payload.prompt : "">
    <cfif !len( trim( userPrompt ) )>
        <cfset writeOutput( serializeJSON({ "error": "Prompt cannot be empty." }) )>
        <cfabort>
    </cfif>
    <!--- Orchestrate the BoxLang AI Agent --->
    <cfscript>
        // Initialize BoxLang AI configuration
        aiConfig = {
            "provider": "google",
            "model": "gemini-2.5-flash", 
            "apiKey": server.system.environment.GEMINI_API_KEY
        };
        
        aiService = new bx.ai.models.AIService( aiConfig );
        
        // Inject persona boundaries preventing infrastructure manipulation
        agent = aiService.newAgent(
            systemInstruction = "You are an embedded text generation feature inside our SaaS dashboard. Your role is strictly to draft emails, generate reports, or format user notes. Use the provided creation tool when the text is ready."
        );
        
        // Register our secure, session-aware component
        contentTool = new path.to.components.ContentManager();
        agent.addTool( contentTool );
        
        try {
            // Execute the processing loop 
            // BoxLang handles: Prompt -> Gemini -> Tool Call execution -> Final message
            aiResponse = agent.chat( userPrompt );
            
            // Return clear status back to your Javascript dashboard UI
            writeOutput( serializeJSON({
                "status": "success",
                "message": aiResponse.getText()
            }));
        } catch ( any e ) {
            writeOutput( serializeJSON({
                "status": "error",
                "message": "An error occurred processing the AI generation request."
            }));
        }
    </cfscript>

    Core Rules for User-Facing UI Security

    • Never Pass Document IDs blindly from Gemini: If you allow Gemini to edit existing reports, always add a server-side cross-check inside your CFC method to ensure that the document's user_id matches the current session.userID before running an UPDATE query.
    • Sanitise Output Rendering: When displaying Gemini's output inside your web application's email template editor or dashboard UI, ensure your frontend escapes the returned string or uses text-only content inputs. This blocks potential HTML/XSS injection vulnerabilities if Gemini accidentally reads a malicious script from external text input.
    • Keep the UI Asynchronous: Generating comprehensive reports can take a few seconds. Ensure your dashboard interface triggers a loading spinner or streaming layout while waiting for this endpoint to reply.

    Gemini: How do you intend to present the output to your users? Will they see the final report saved quietly in the background to their file list, or should Gemini stream the text dynamically onto their screen inside a text editor layout?

    Mike: Both options are required.


    Gemini: To support both workflows seamlessly, structure your BoxLang backend to handle them dynamically.

    When streaming content directly into a UI editor, you want Gemini to respond with raw text as fast as possible. When saving files silently in the background, you want Gemini to process the text and automatically invoke your secure ContentManager.cfc tool to write to the database.

    Here is how you can implement both approaches within your legacy BoxLang infrastructure.

    Architecture Strategy

    You can use a simple URL parameter or request payload field (e.g., mode=stream or mode=background) to route the request:

    1. Stream Mode: Bypasses BoxLang's automated tool system entirely. It hooks into BoxLang AI's underlying streaming mechanism to push real-time text chunks straight to the client browser via Server-Sent Events (SSE) or a flushing response buffer.
    2. Background Mode: Utilises the Function Calling setup we built previously. It tells the agent to orchestrate the generation, save it quietly via your secure ContentManager.cfc, and return a simple "Success" payload.

    The Dual-Mode Endpoint (/api/generate.cfm)

    This unified legacy endpoint routes incoming dashboard requests based on the user's intent.

    cfml

    <cfsetting showdebugoutput="false">
    <!--- Session and validation guardrails --->
    <cfif !structKeyExists( session, "userID" )>
        <cfcontent type="application/json">
        <cfset writeOutput( serializeJSON({ "error": "Unauthorized" }) )>
        <cfabort>
    </cfif>
    <cfparam name="url.mode" default="background"> <!--- Options: 'stream' or 'background' --->
    <cfset userPrompt = form.prompt ?: "">
    <cfscript>
        // Initialize standard BoxLang AI Configuration
        aiConfig = {
            "provider": "google",
            "model": "gemini-2.5-flash", 
            "apiKey": server.system.environment.GEMINI_API_KEY
        };
        aiService = new bx.ai.models.AIService( aiConfig );
        
        // ----------------------------------------------------
        // OPTION 1: STREAM MODE (Direct to Text Editor UI)
        // ----------------------------------------------------
        if ( url.mode EQ "stream" ) {
            // Change content type to allow dynamic browser streaming
            cfcontent( type="text/event-stream" );
            
            agent = aiService.newAgent(
                systemInstruction = "You are an AI text editor assistant. Generate the requested text directly. Do NOT attempt to run tools or database commands."
            );
            // Request a stream from BoxLang AI
            // BoxLang stream chunks are iterated natively
            agent.chatStream( userPrompt, function( chunk ) {
                // Write each text fragment directly to the dashboard UI
                writeOutput( "data: " & chunk.getText() & "#chr(10)##chr(10)#" );
                cfflush(); // Force the server buffer to send the text chunk immediately
            });
            
            cfabort();
        }
        // ----------------------------------------------------
        // OPTION 2: BACKGROUND MODE (Save Silently to DB)
        // ----------------------------------------------------
        if ( url.mode EQ "background" ) {
            cfcontent( type="application/json" );
            agent = aiService.newAgent(
                systemInstruction = "You are a background generator. Take the prompt, create the required content, and immediately use the createDraftDocument tool to save it."
            );
            
            // Bind the secure tool component we created earlier
            contentTool = new path.to.components.ContentManager();
            agent.addTool( contentTool );
            
            try {
                // Gemini creates content, triggers tool, BoxLang updates database 
                aiResponse = agent.chat( userPrompt );
                
                writeOutput( serializeJSON({
                    "status": "success",
                    "message": "Your request has been generated and filed to your dashboard document list."
                }));
            } catch ( any e ) {
                writeOutput( serializeJSON({ "status": "error", "message": "Failed background execution loop." }));
            }
            
            cfabort();
        }
    </cfscript>

    Frontend JavaScript Connections

    Handling Option 1 (Streaming into a UI Rich Text Editor)

    To consume the streaming API on your client dashboard web page, use JavaScript's EventSource api or a standard fetch body reader:

    javascript

    async function startStreamingEditor(promptText) {
        const editorElement = document.getElementById('my-rich-text-editor');
        editorElement.innerHTML = "Generating draft...";
        const response = await fetch('/api/generate.cfm?mode=stream', {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({ prompt: promptText })
        });
        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        editorElement.innerHTML = ""; // Clear loader
        while (true) {
            const { value, done } = await reader.read();
            if (done) break;
            
            let chunk = decoder.decode(value);
            // Clean out SSE data prefix formatting if necessary
            let processedText = chunk.replace(/^data:\s*/gm, ''); 
            
            // Append raw content directly onto the editor screen in real-time
            editorElement.append(processedText); 
        }
    }

    Handling Option 2 (Background Generation Banner)

    For background workflows, a standard asynchronous fetch request alerts the user when the document safely lands in their dashboard portfolio.

    javascript

    function generateBackgroundReport(promptText) {
        showGlobalToast("Generating your report in the background...");
        fetch('/api/generate.cfm?mode=background', {
            method: 'POST',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            body: new URLSearchParams({ prompt: promptText })
        })
        .then(res => res.json())
        .then(data => {
            if(data.status === "success") {
                showGlobalToast("Finished! Your document is ready in your list.");
                refreshDocumentTableGrid(); // Reloads legacy dashboard table data
            }
        });
    }

    The industry standard for embedding Gemini into a custom web UI is a Hybrid API + Frontend Component architecture. Your BoxLang backend acts as the secure proxy to the Gemini API, and your frontend uses JavaScript to render a tailored, styled chat or text interface.

    Recommended Frontend Integration Paths

    Depending on how integrated you want the feature to feel, you have three primary architectural choices:

    1. Pre-built Custom Web Components (Easiest & Cleanest)

    Instead of building a text box and message bubbles from scratch, you can use open-source, framework-agnostic UI component libraries designed exactly for this.

    • The Tools: Use libraries like Vercel AI SDK UI components or Deep Chat Core.
    • How it works: You drop a custom HTML tag (e.g., <deep-chat>) into your legacy .cfm dashboard page. You point its url property to your /api/generate.cfm endpoint. The component handles layout, streaming chunks, loading skeletons, and markdown formatting natively.
    html

    <!-- Example using an open-source web component in a standard .cfm page -->
    <script type="module" src="https://unpkg.com"></script>
    <div class="dashboard-panel">
        <h3>SaaS AI Copywriter</h3>
        <!-- The component automatically handles user input and streams your BoxLang response -->
        <deep-chat 
            request='{"url": "/api/generate.cfm?mode=stream", "method": "POST"}'
            stream="true"
            initialMessages='[{"text": "Hi! Let me help you draft your next email or report.", "role": "ai"}]'
            style='{"borderRadius": "8px", "border": "1px solid #ccc", "height": "500px"}'>
        </deep-chat>
    </div>

    2. Native Dynamic Text Injection (For In-Line Editors)

    If your goal is to let users click a button (e.g., "Write a summary of this report"), you shouldn't use a chat window at all. Use inline JavaScript to inject text directly into your existing editor layout.

    • The Tools: Standard JavaScript fetch() API combined with your existing text editors (like TinyMCE, CKEditor, or Quill).
    • How it works: When the user clicks the "AI Assist" button, your JavaScript calls your BoxLang stream endpoint and updates the text editor's content block-by-block using the Reader API shown in the previous response. This creates a seamless "AI is writing inside my document" effect.

    3. Custom Chat Assistant Widget (For Floating Help Panels)

    If you want an expandable, floating AI helper in the bottom corner of your SaaS dashboard, you can build a small HTML/CSS chat container.

    • How it works: You manage a message array in JavaScript. When the user types a prompt and hits send, your code appends a user-message bubble to the DOM, fires the AJAX request to BoxLang, and streams the response directly into a newly appended ai-message bubble.

    Why This Architecture Keeps Your SaaS Secure

    By routing everything through your BoxLang backend and rendering natively on the client side:

    • API Key Masking: Your Google Gemini API key stays safe on your server inside BoxLang (server.system.environment.GEMINI_API_KEY). It never appears in the browser's developer inspector.
    • Seamless Styling: The AI feature matches your SaaS design system, fonts, and colours perfectly.
    • State Control: You maintain full control over the data loop, so you can save copies of the generated text to your database before it even touches the user's screen.

    Gemini: Which style fits your vision best? A floating conversational chat assistant, a standalone copywriting page, or contextual buttons embedded right inside your text input fields? I can give you the exact HTML/CSS boilerplates to start wiring it up.


    ... to be continued ... 😎😎😎😎


    Live from AGNTCon + MCPCon NA, OpenAI, Anthropic, DeepMind, Entire, Block & More

    Join the Agentic AI Foundation live on YouTube for AGNTCon + MCPCon North America on October 22, 2026, from 9:00 AM to 6:00 PM California time (PDT / UTC−7).

    Tune in from anywhere for a day focused on the ideas, people, and open technologies shaping agentic AI and the Model Context Protocol. Drop in for a keynote or stay with us for the full broadcast.

    What to expect

    • 3,500+ attendees from across the agentic AI ecosystem
    • 1,000+ organizations represented
    • 150+ talks across 11 tracks
    • 7 hands-on workshops at no added cost for in-person attendees
    • Featured keynote speakers
    • Hear from Tim O’Reilly…

    Hosted by Agentic AI Foundation

    No posts for a wee while

    Mike's Notes

    I was on holiday for the last few weeks and am back now. There will be no blog posts, newsletters or meetings until Pipi Core is back up and running.

    Update 27/05/2026

    Lots of surprises. Making rapid progress. The peace and quiet are bliss.

    Update 31/05/2026

    The problem and solution are how things are named. Pipi auto-generates thousands of code names using multiple pattern languages, and all the naming conventions require many minor fixes for several unexpected reasons after migrating from a developer laptop to a production server environment. Everything else is absolutely fine.

    Other naming problems are also being solved now, including:

    • The rapid development of Boxlang by Ortus has brought forward another challenge. Pipi 10 will be migrated to run on top of Boxlang in 2027 to support multiple languages, including C++, CFML, COBOL, Go, Java, JavaScript, PHP, Python, Rust, etc.
    • Future integration with cloud-based LLMs.
    • Future integrations with Office365, Google Workspace, Zoho, LibreOffice, etc.

    The common solution is to create standardised naming systems that are simple, stable, robust, schema-based, versioned, self-documenting, and extensible to meet unanticipated future needs.

    This is done by replacing code-based naming rules with database-driven ones that can be easily edited in the future via an admin UI.

    90% of these names are internal, hidden in the closed core, and how they work and what they are will not be discussed here. The rest will be publicly and fully documented as part of the open-source workspaces for developers to work with.

    Update 02/06/2026

    I'm changing the disclosure boundary between the Pipi closed-core and open-source workspaces. Previously, "disclose everything unless there is a security reason not to". This is now changed to "disclose on the basis of need to know".

    Closed-core accounts for 90% and open-source workspaces for 10% of lines of code, databases, etc.

    This will reduce the documentation burden, given Pipi's vast scale. So, the open-source workspaces will be fully shared and documented on GitHub, etc, without restriction. This includes;

    • Standards schema
    • Ontologies
    • Parameters
    • Laws of physics
    • HTML + CSS
    • Algorithms
    • Module DDD models
    • Workflow diagrams
    • Documentation
    • API schema
    • UI code
    • etc

    This also means some existing technical documentation about the closed-core will become hidden and only available internally.

    Update 07/06/2026

    Pipi Core is the IDE used to edit Pipi Core (AKA: which came first, the chicken or the egg?). Temporary UIs have been created and are being used across multiple engines to edit the names in use. This is much faster than directly editing data, which had to be done initially. The next step will be turning auto-generation back on. Once that's done, temporary UIs will be used to build permanent UIs. More automation will then be enabled via the UIs, and so on, as Pipi Core builds itself with a human in the loop.

    Update 08/06/2026

    The list of code cases available to use now for auto-generated naming, I/O translation, etc with examples, includes;

    • camelCase: userProfilePicture
    • kebab-case: user-profile-picture
    • PascalCase: UserProfilePicture
    • snake_case: user_profile_picture
    • SCREAMING_SNAKE_CASE: USER_PROFILE_PICTURE
    • Train-Case: User-Profile-Picture
    • flatcase: userprofilepicture
    • UPPER-CASE-KEBAB-CASE: USER-PROFILE-PICTURE
    • Sentence case: User profile picture
    • Title Case: User Profile Picture
    • middot·case: user·profile·picture
    • dot.case: user.profile.picture
    • UPPER CASE: USER PROFILE PICTURE
    • lowercase: user profile picture

    Update 12/06/20026

    Checking that these changes to variable names and internal messaging do not clash with the Gödel Machine.

    Update 17/06/2026

    The DevOps Engine (dvp) has unexpectedly proven to be critical to solving this puzzle. Mostly fixed last night. Watching the rather excellent live Google talk, Beyond the GPU: Maximising goodput with self-healing AI infrastructure, this morning has given me valuable insights into how to fix the remaining issues by reviewing Google HPC YAML files. 😎😎 Sometimes insights come from the strangest places.

    Update 01/07/2026

    The main work now is rapidly configuring Pipi for production and full autonomous automation. Using Google Search AI Mode (Gemini) and then Grammarly Pro makes the work easier and 100x faster.

    • I have decided to have Pipi re-render the many Ajabbi draft public websites with the new and missing developer information. (20K pages)
    • The website's .robot.txt file will then be unlocked to enable search engines.
    • The HTML will be updated to make it easier for AI to read.
    • This blog will be imported into Pipi, cleaned up, re-exported from Pipi, and published to Blogger via the API.
    • The new posts created in Pipi will return to A Sandy Beach to discuss something already built rather than being built.

    Update 02/07/2026

    The DevOps and IaC engines are getting rapid data model overhauls. The IaC engine is a great test for the variable names. I'm building a capability into Pipi to autonomously and automatically run OpenTofu and Ansible, initially targeting the Pipi Data Centre, then GCP and AWS for deployments. It's going very well and making rapid progress.

    Update 05/07/2026

    Pipi will initially run the open-source enterprise applications on Google Cloud Run and Google Cloud Storage (GCS). The code is complete and will be very low-cost to run, giving Ajabbi, a bootstrapping-purpose startup, a very long runway.

    Update 18/07/2026

    The job has now shifted to configuring, networking and deploying many physical servers. Installing software, including Pipi, labelling cables and rack gear, throwing out junk, tidying, etc., leaving nothing to chance. Shipping delays are holding up part deliveries.

    Update 28/07/2026

    Most of the equipment has arrived, and the small data centre setup is coming together. More deliveries later this week. It's already running a lot better and is much more productive.

    Update 31/07/2026

    Work on Pipi has reached a tipping point or system phase change as Pipi takes over tasks using autonomous automation. Pipi now has deadlines, not me. Soon it will set the deadlines. It's now a downhill run; daily posts from me resume tomorrow, and much more will come.

    In hindsight. This whole project has been systematic trial and error, spending 10 years learning how to crack a hard problem.

    Resources

    References

    • Reference

    Repository

    • Home > Ajabbi Research > Library >
    • Home > Handbook > 

    Last Updated

    31/07/2026

    No posts for a wee while

    By: Mike Peters
    On a Sandy Beach: 15/05/2026

    Mike is the inventor and architect of Pipi and the founder of Ajabbi.

    I was on a no-coding holiday for the last few weeks to clear my mind, and it has been great. I am back on the job today.

    Suspended

    Until the closed-source Pipi Core is back up and running 100% on autopilot, 10x faster, the following are suspended.

    • New posts "On a Sandy Beach
    • All newsletters, including the weekly Friday Report and the monthly Ajabbi Research Newsletter.
    • The fortnightly online Open R&D meeting.

    Rapid refocus

    • A new developer area with five coding screens, designed to be more productive for hypervisual learners.
    • A better library has been set up for my A4 drawings in ring binders, the many reference books I use, and more bookshelves are on the way.
    • The server rack has been moved to a better location.
    • The light levels have been adjusted.
    • A big office tidy is almost done. An office-work-only desk has yet to be set up with a cat bed included.
    • A separate area with no screens for the happy cat, coffee, music, reading and drawing.

    Less is more

    Minimise screen time to be more productive at work. The new setup is also much less tiring.

    Get the job done

    The good thing is that, with a holiday and lots of drawing, I now have mental clarity about what needs fixing and how to fix it. Mainly, quite delicate changes here and there, organised into a list of steps. Now, I need to concentrate on one thing only: go as fast as possible, without meetings, post-deadlines, phone calls, or other distractions.

    How

    1. Use an AI workforce

    Be the architect, and AI fills in the dots to make it happen.

    Use Google Search AI mode (Gemini) to generate 99% of the code in one-page chunks (including references) to copy and paste, then manually change the variable names and SQL. Careful, test everything, resulting in 100x faster progress. Know how everything works and rapidly raise personal skill level.

    2. Then build a cathedral

    Make a wooden scale model of a cathedral for the builders. Google Search AI mode (Gemini) makes each brick, and Pipi Core assembles the bricks into floors, arches, walls, and vaults...

    Speed is king

    With the 100x coding productivity gains from Google Search AI mode (Gemini), plus the 10x10x10x speedup of Pipi Core currently underway over the next few months, what previously took a year will be done in hours and better.

    Phase transitions

    Once these initial migration issues from laptop to server are resolved, further transitions can be anticipated as the number of engines rapidly increases beyond 20. Increasing the number of engines slowly changes the whole system's behaviour from deterministic to probabilistic and adaptive.

    Here is a partial list of transitions expected as the number of engines increases from 0 to 200. The actual numbers are a bit of a guess.

    • 20 engines enable Pipi 9 Core in a simple, deterministic structure.
    • 40 engines enable a workspace with a UI for administering Pipi Core.
    • 60 engines enable self-generation of user documentation.
    • 80 engines enable REPL and IAC (infrastructure-as-code).
    • 100 engines enable Workspaces for different user accounts.
    • Different Pipi 9 editions are made with the same engines, which recombine differently in response to the external environment.
    • And so on until...
    • 200 engines self-organise into a multi-layered complex fluid structure with probabilistic behaviour and emergent properties, as engines also act as agents.
    • 200+ engines enable Pipi 10 to interact with externally cloud-hosted LLMs, combining the very different strengths of both.