toAiGateway() para ColdBox Core
Propuesta preliminar: un hermano con forma de gateway para el propio toAi() de ColdBox.
On this page
Propuesta: toAiGateway() — un terminador nativo del DSL de enrutamiento de ColdBox para la superficie de webhook de Gateway de bx-ai
Estado: borrador, escrito desde BX Agents (ortus-boxlang/bx-agents). Actualización desde el
primer borrador: coldbox-platform (específicamente ColdBox mismo, Router.cfc) SÍ se adjuntó y
se leyó directamente después en esta misma sesión — el límite entre propietarios resultó ser
por-estado-de-sesión, no permanente; una vez que el zip de ColdBox/coldbox-platform se obtuvo de
su URL de descarga real y se descomprimió, su código fuente de system/web/routing/Router.cfc se leyó
en su totalidad. Eso resolvió los dos elementos "vale la pena confirmar" de abajo y, más importante,
corrigió un error real que la sección toAi()/IAiRunnable de esta propuesta había heredado de
un pase anterior solo de documentación (ver las notas de corrección en línea).
Por qué
ColdBox 8.1 envía dos terminadores de DSL de enrutamiento específicos de IA:
route(pattern).toAi(target)— 4 rutas auto-registradas (invoke/stream/batch/info) contra un objetivoIAiRunnable.route(pattern).toMCP(target)— 1 ruta, despacha aMCPRequestProcessor.
bx-ai también envía una tercera superficie HTTP que no tiene ningún terminador de ColdBox en absoluto: la
superficie de webhook de channel-adapter IGateway/aiGatewayRegistry() (entrega Slack/webhook,
aprobación human-in-the-loop), gestionada por un procesador fijo de 3 rutas
(bxModules.bxai.models.gateway.http.GatewayRequestProcessor::processHttp()). Hoy,
usarla desde una app ColdBox significa cablear a mano 3 rutas simples a un handler de passthrough.
Ese es exactamente el tipo de cableado que toAi()/toMCP() ya existen para ahorrarle a la gente
hacer a mano para las otras dos superficies de bx-ai — esto propone cerrar la brecha con un
tercer terminador, toAiGateway(), construido de la misma manera.
BX Agents (un módulo de framework de agentes basado en convenciones sobre bx-ai + ColdBox) está enviando este cableado por su cuenta mientras tanto — ver "Workaround actual" abajo — precisamente para que pueda eliminarse una vez que esto aterrice en el núcleo.
Lo que ya está probado (verificado contra el código fuente de bx-ai esta sesión)
bxModules.bxai.models.gateway.http.GatewayRequestProcessor:
static string function processHttp() {
var requestData = static.httpTransport.readRequest();
var response = route( requestData );
static.httpTransport.writeResponse( response );
return response.content;
}
- Sin argumentos, estático. Lee el request HTTP en vivo él mismo (vía
cgi.PATH_INFO,cgi.REQUEST_METHOD,getHTTPRequestData()) y escribe la respuesta él mismo (víabx:header/bx:content reset=true). No necesita — y no puede usar — elevent/rc/prcde ColdBox para su propia lógica. - Enruta internamente basándose en
cgi.PATH_INFO, esperando exactamente 3 formas:POST /gateways/{gatewayName}/events— evento de plataforma entranteGET /interactions/{requestID}— hacer poll de una interacción de aprobación humana pendientePOST /interactions/{requestID}/decisions— enviar la decisión de un humano- (más
OPTIONSde preflight CORS, también manejado internamente)
- Porque analiza los segmentos de ruta él mismo, cualquier cosa que lo gestione debe exponer estas 3 formas
textualmente (sin prefijo de ruta extra) para que coincidan las comprobaciones de conteo de segmento/nombre en
GatewayRequestProcessor.route(). aiGatewayRegistry()resuelve gateways por nombre; nada sobre el enrutamiento necesita el contenido del registro, solo que los gateways se hayan registrado en algún momento antes de que llegue un request (típicamente en el arranque de la app).
Esto significa que toAiGateway() no necesita ninguna interfaz de adaptador en absoluto — a diferencia del
IAiRunnable de toAi(), no hay nada que una clase objetivo deba implementar. Todo el trabajo del
terminador es registrar las rutas correctas a la llamada estática correcta y decirle a ColdBox que no
renderice nada después (el procesador ya escribió la respuesta real).
Implementación propuesta del núcleo
Un único terminador, auto-registrando 3 rutas (reflejando la forma de "una llamada → N
rutas" de toAi()) sin ningún argumento de objetivo (reflejando la forma sin objetivo de
toMCP(), ya que el enrutamiento es impulsado por nombre desde la propia URL, no desde un mapeo de
WireBox):
route( "/bxai" ).toAiGateway();
registra, en relación a donde sea que route() ancle su patrón:
| Verbo | Ruta | Comportamiento |
|---|---|---|
| POST | {pattern}/gateways/:gatewayName/events | evento de plataforma entrante |
| GET | {pattern}/interactions/:requestID | hacer poll de la interacción |
| POST | {pattern}/interactions/:requestID/decisions | enviar decisión humana |
Las 3 despachan a la misma acción generada/interna, que no hace nada más que:
function process( event, rc, prc ) {
bxModules.bxai.models.gateway.http.GatewayRequestProcessor::processHttp();
return event.noRender();
}
Pregunta abierta para quien implemente esto contra el código fuente real de Route.bx: si el
prefijo {pattern} es seguro dado que GatewayRequestProcessor analiza cgi.PATH_INFO
asumiendo sin prefijo (ver la nota "textualmente" arriba). Dos formas de resolverlo, en orden
de preferencia:
toAiGateway()siempre ancla en la raíz de la app (ignora/rechaza un patrón no vacío), ya que la propia lógica de análisis de ruta del procesador no puede tolerar un prefijo de todos modos.- Si la reescritura de URL de ColdBox siempre hace que
cgi.PATH_INFOrefleje la ruta completa solicitada (típico en despliegues de ColdBox de reescribir-todo-a-index.bxm), un prefijo "simplemente funciona" de forma transparente y esto en realidad no es una restricción — verificar empíricamente antes de elegir cualquiera de las dos opciones.
Los modificadores de ruta estándar (.as(), .withModule(), .withDomain(), etc.) deberían aplicarse
de la misma manera que lo hacen para toAi()/toMCP().
Workaround actual (BX Agents, a eliminar una vez que esto aterrice)
El pipeline de build de BX Agents genera el cableado equivalente a mano hoy:
RouterGenerator.bxemite, solo cuando al menos un gateway de channel-adapter de tipohttpestá configurado:post( "/gateways/:gatewayName/events" ).toHandler( "Gateway.process" ) get( "/interactions/:requestID" ).toHandler( "Gateway.process" ) post( "/interactions/:requestID/decisions" ).toHandler( "Gateway.process" )GatewayGenerator.bxemite unhandlers/Gateway.bxgenerado con exactamente una acción:function process( event, rc, prc ) { bxModules.bxai.models.gateway.http.GatewayRequestProcessor::processHttp() return arguments.event.noRender() }- Se insertan llamadas
aiGatewayRegistry().register( aiGateway( type, options ) )en elApplication.bx onApplicationStart()de la app generada, una vez por cada gateway de channel-adapter configurado.
Una vez que toAiGateway() exista en el núcleo, RouterGenerator cambia sus 3 rutas escritas a mano
por una llamada route( ... ).toAiGateway(), y GatewayGenerator deja de generar
handlers/Gateway.bx por completo — pura eliminación, sin lógica nueva del lado de BX Agents necesaria.
Plan de pruebas para el PR del núcleo
- Unitaria:
route(...).toAiGateway()registra exactamente 3 rutas, verbos/rutas correctos, se aplican los modificadores de ruta estándar. - Integración: un request en vivo a cada una de las 3 rutas alcanza
GatewayRequestProcessor::processHttp()y devuelve su respuesta textualmente (código de estado, cabeceras, cuerpo) — registrar un gateway de tipomockvíaaiGatewayRegistry()en el arnés de pruebas (sin necesidad de red/llamada LLM real,bx-aienvía un proveedor"mock"literal exactamente para esto). - Regresión: confirmar que
event.noRender()evita que ColdBox escriba una respuesta por duplicado después de queprocessHttp()ya haya vaciado una víabx:content reset=true.
Confirmado más tarde en esta sesión (actualización)
ColdBox/coldbox-platform (8.1.0) se obtuvo directamente (https://downloads.ortussolutions.com/ortussolutions/coldbox/8.1.0/coldbox-8.1.0.zip)
y system/web/routing/Router.cfc se leyó en su totalidad. Ambos elementos originalmente listados aquí como
"vale la pena confirmar" ahora están resueltos, y una suposición anterior en esta misma propuesta
resultó incorrecta y ha sido corregida:
-
La resolución del objetivo de
toAi(target)— confirmada tal como se asumió. Router.cfc:var runnableInstance = isSimpleValue( capturedRunnable ) ? getInstance( capturedRunnable ) : capturedRunnable. Una cadena se resuelve víagetInstance()de WireBox; un objeto en vivo se usa directamente. -
El contrato real de
IAiRunnable— CORREGIDO, no lo que esta propuesta originalmente decía. La sección "Lo que ya está probado" de arriba (sin cambios, todavía precisa para la superficie de Gateway) se escribió solo a partir del código fuente de bx-ai. Por separado, el propio trabajo M8 de BX Agents dependió de una descripción de la documentación publicada del contrato de objetivo detoAi()que resultó ser incorrecta:invoke/stream/batch/infoson los nombres de las subrutas, no nombres de métodos quetoAi()llama en el objetivo. Los closures reales de Router.cfc llaman arunnableInstance.run( input, params, options )yrunnableInstance.stream( onChunk, input, params, options )— es decir, la propia interfazIAiRunnablede bx-ai (bxModules.bxai.models.runnables.IAiRunnable), queAiAgentya implementa nativamente víaAiBaseRunnable. No se necesita ninguna subclase de adaptador en absoluto — el valor de retorno del BIFaiAgent()simple ya satisface atoAi(). El generador de BX Agents se ha corregido para coincidir (ya no hay másGeneratedAgentRunnable.bx/exposeAgentAsRunnable). -
El
.toProvider(closure)de WireBox — no revisado de nuevo esta sesión (Router.cfc no toca la sintaxis de binder de WireBox); todavía es una suposición en el generadorconfig/WireBox.bxde BX Agents. Riesgo bajo:.toProvider()es un DSL de WireBox bien establecido y de uso común, simplemente no algo que este pase específico de código fuente haya tocado.