Skip to content

Commit 6a54f5a

Browse files
committed
Merge branch 'development' of github.com:ColdBox/coldbox-platform into development
2 parents 6e080b7 + 00c498a commit 6a54f5a

File tree

2 files changed

+79
-59
lines changed

2 files changed

+79
-59
lines changed

system/web/routing/Router.cfc

Lines changed: 69 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1948,15 +1948,15 @@ component
19481948
private function ensureBoxLang(){
19491949
if ( !server.keyExists( "boxlang" ) ) {
19501950
throw(
1951-
type : "BoxLangRequiredException",
1952-
message : "BoxLang is required for AI/MCP routing. toAi() and toMCP() are BoxLang-only features."
1951+
type : "BoxLangRequiredException",
1952+
message: "BoxLang is required for AI/MCP routing. toAi() and toMCP() are BoxLang-only features."
19531953
);
19541954
}
19551955

19561956
if ( !getModuleList().keyArray().findNoCase( "bxai" ) ) {
19571957
throw(
1958-
type : "ModuleNotFoundException",
1959-
message : "The BoxLang AI module (bxai) is required for AI/MCP routing. Install it via: box install bxai"
1958+
type : "ModuleNotFoundException",
1959+
message: "The BoxLang AI module (bxai) is required for AI/MCP routing. Install it via: box install bxai"
19601960
);
19611961
}
19621962
}
@@ -2008,10 +2008,10 @@ component
20082008
if (
20092009
( !isSimpleValue( arguments.runnable ) && !isObject( arguments.runnable ) ) ||
20102010
isNumeric( arguments.runnable )
2011-
) {
2011+
) {
20122012
throw(
2013-
type : "InvalidArgumentException",
2014-
message : "The 'runnable' argument must be a WireBox ID string or an IAiRunnable instance"
2013+
type : "InvalidArgumentException",
2014+
message: "The 'runnable' argument must be a WireBox ID string or an IAiRunnable instance"
20152015
)
20162016
}
20172017

@@ -2043,15 +2043,20 @@ component
20432043
// =====================================================================================
20442044

20452045
// POST {base}/invoke — synchronous execution via run()
2046-
var routeArgs = sharedArgs.copy()
2046+
var routeArgs = sharedArgs
2047+
.copy()
20472048
.append( {
2048-
"pattern" : "#basePath#/invoke",
2049-
"name" : "#baseName#.invoke",
2050-
"verbs" : "POST",
2051-
"response" : ( event, rc, prc ) => {
2049+
"pattern" : "#basePath#/invoke",
2050+
"name" : "#baseName#.invoke",
2051+
"verbs" : "POST",
2052+
"response" : ( event, rc, prc ) => {
20522053
var runnableInstance = isSimpleValue( capturedRunnable ) ? getInstance( capturedRunnable ) : capturedRunnable
2053-
var body = event.getHTTPContent( json:true )
2054-
var result = runnableInstance.run( body.input ?: {}, body.params ?: {}, body.options ?: {} )
2054+
var body = event.getHTTPContent( json: true )
2055+
var result = runnableInstance.run(
2056+
body.input ?: {},
2057+
body.params ?: {},
2058+
body.options ?: {}
2059+
)
20552060
return { "output" : result, "success" : true }
20562061
}
20572062
} )
@@ -2068,23 +2073,24 @@ component
20682073
// =====================================================================================
20692074

20702075
// POST {base}/stream — streaming via BoxLang SSE() BIF
2071-
routeArgs = sharedArgs.copy()
2076+
routeArgs = sharedArgs
2077+
.copy()
20722078
.append( {
2073-
"pattern" : "#basePath#/stream",
2074-
"name" : "#baseName#.stream",
2075-
"verbs" : "POST",
2076-
"response" : ( event, rc, prc ) => {
2079+
"pattern" : "#basePath#/stream",
2080+
"name" : "#baseName#.stream",
2081+
"verbs" : "POST",
2082+
"response" : ( event, rc, prc ) => {
20772083
var runnableInstance = isSimpleValue( capturedRunnable ) ? getInstance( capturedRunnable ) : capturedRunnable;
2078-
var body = event.getHTTPContent( json : true );
2084+
var body = event.getHTTPContent( json: true );
20792085
SSE(
2080-
callback : ( emitter ) => {
2086+
callback: ( emitter ) => {
20812087
runnableInstance.stream(
20822088
( chunk ) => {
20832089
if ( !emitter.isClosed() ) {
20842090
emitter.send( chunk, "chunk" );
20852091
}
20862092
},
2087-
body.input ?: {},
2093+
body.input ?: {},
20882094
body.params ?: {},
20892095
body.options ?: {}
20902096
);
@@ -2111,19 +2117,23 @@ component
21112117
// BATCH ROUTE
21122118
// =====================================================================================
21132119
// POST {base}/batch — run() for each item in inputs[]
2114-
routeArgs = sharedArgs.copy()
2120+
routeArgs = sharedArgs
2121+
.copy()
21152122
.append( {
2116-
"pattern" : "#basePath#/batch",
2117-
"name" : "#baseName#.batch",
2118-
"verbs" : "POST",
2119-
"response" : ( event, rc, prc ) => {
2123+
"pattern" : "#basePath#/batch",
2124+
"name" : "#baseName#.batch",
2125+
"verbs" : "POST",
2126+
"response" : ( event, rc, prc ) => {
21202127
var runnableInstance = isSimpleValue( capturedRunnable ) ? getInstance( capturedRunnable ) : capturedRunnable
2121-
var body = event.getHTTPContent( json:true )
2128+
var body = event.getHTTPContent( json: true )
21222129
var params = body.params ?: {}
21232130
var options = body.options ?: {}
21242131
var outputs = ( body.inputs ?: [] ).map( ( input ) => {
21252132
try {
2126-
return { output : runnableInstance.run( input, params, options ), success : true };
2133+
return {
2134+
output : runnableInstance.run( input, params, options ),
2135+
success : true
2136+
};
21272137
} catch ( any e ) {
21282138
return { error : e.message, success : false };
21292139
}
@@ -2143,22 +2153,39 @@ component
21432153
// INFO ROUTE
21442154
// =====================================================================================
21452155
// GET {base}/info — brief endpoint metadata
2146-
routeArgs = sharedArgs.copy()
2156+
routeArgs = sharedArgs
2157+
.copy()
21472158
.append( {
2148-
"pattern" : "#basePath#/info",
2149-
"name" : "#baseName#.info",
2150-
"verbs" : "GET",
2151-
"response" : ( event, rc, prc ) => {
2159+
"pattern" : "#basePath#/info",
2160+
"name" : "#baseName#.info",
2161+
"verbs" : "GET",
2162+
"response" : ( event, rc, prc ) => {
21522163
var runnableInstance = isSimpleValue( capturedRunnable ) ? getInstance( capturedRunnable ) : capturedRunnable
21532164
return {
21542165
"name" : runnableInstance.getName(),
21552166
"description" : runnableInstance?.getDescription() ?: "",
21562167
"pattern" : basePath,
2157-
"endpoints" : [
2158-
{ "verb" : "POST", "path" : basePath & "/invoke", "description" : "Synchronous execution" },
2159-
{ "verb" : "POST", "path" : basePath & "/stream", "description" : "Streaming SSE execution" },
2160-
{ "verb" : "POST", "path" : basePath & "/batch", "description" : "Batch execution" },
2161-
{ "verb" : "GET", "path" : basePath & "/info", "description" : "Endpoint metadata" }
2168+
"endpoints" : [
2169+
{
2170+
"verb" : "POST",
2171+
"path" : basePath & "/invoke",
2172+
"description" : "Synchronous execution"
2173+
},
2174+
{
2175+
"verb" : "POST",
2176+
"path" : basePath & "/stream",
2177+
"description" : "Streaming SSE execution"
2178+
},
2179+
{
2180+
"verb" : "POST",
2181+
"path" : basePath & "/batch",
2182+
"description" : "Batch execution"
2183+
},
2184+
{
2185+
"verb" : "GET",
2186+
"path" : basePath & "/info",
2187+
"description" : "Endpoint metadata"
2188+
}
21622189
]
21632190
}
21642191
}
@@ -2214,8 +2241,8 @@ component
22142241
// Validate argument
22152242
if ( !len( trim( arguments.serverName ) ) && !findNoCase( ":mcpServer", variables.thisRoute.pattern ) ) {
22162243
throw(
2217-
type : "InvalidArgumentException",
2218-
message : "The 'serverName' argument must be a non-empty string or the route pattern must contain the ':mcpServer' placeholder"
2244+
type : "InvalidArgumentException",
2245+
message: "The 'serverName' argument must be a non-empty string or the route pattern must contain the ':mcpServer' placeholder"
22192246
)
22202247
}
22212248

@@ -2226,7 +2253,7 @@ component
22262253

22272254
// Inline response closure: resolves the server name and delegates to MCPRequestProcessor
22282255
var mcpResponseClosure = ( event, rc, prc ) => {
2229-
var resolvedServerName = rc.keyExists( "mcpServer" ) ? rc.mcpServer : serverName
2256+
var resolvedServerName = rc.keyExists( "mcpServer" ) ? rc.mcpServer : serverName
22302257
return bxModules.bxai.models.mcp.MCPRequestProcessor::processHttp( resolvedServerName );
22312258
};
22322259

tests/specs/web/routing/RouterAITest.cfc

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* AI & MCP Routing Tests — covers toAi() and toMCP() terminators
33
*/
4-
component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
4+
component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang" {
55

66
boolean function notBoxlang(){
77
return !isBoxLang()
@@ -21,9 +21,7 @@ component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
2121
/*********************************** BDD SUITES ***********************************/
2222

2323
function run( testResults, testBox ){
24-
2524
describe( "AI Routing — toAi()", function(){
26-
2725
beforeEach( function(){
2826
variables.router = createMock( "coldbox.system.web.routing.Router" )
2927
.init()
@@ -35,7 +33,6 @@ component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
3533
} )
3634

3735
story( "I want to register an AI runnable behind a base route pattern", function(){
38-
3936
given( "a WireBox ID string as runnable", function(){
4037
then( "it should register 5 standardized sub-routes", function(){
4138
router.route( "/api/chat" ).toAi( "MockRunnable" )
@@ -64,7 +61,10 @@ component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
6461

6562
given( "a base route with withSSL() set", function(){
6663
then( "all sub-routes should inherit ssl=true", function(){
67-
router.route( "/api/chat" ).withSSL().toAi( "MockRunnable" )
64+
router
65+
.route( "/api/chat" )
66+
.withSSL()
67+
.toAi( "MockRunnable" )
6868
var routes = router.getRoutes()
6969

7070
expect( routes ).toHaveLength( 4 )
@@ -77,7 +77,7 @@ component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
7777
given( "a registered AI route family", function(){
7878
then( "POST sub-routes should be invoke/stream/batch and GET sub-routes should be info", function(){
7979
router.route( "/api/chat" ).toAi( "MockRunnable" )
80-
var routes = router.getRoutes()
80+
var routes = router.getRoutes()
8181
var postRoutes = routes.filter( ( r ) => r.verbs == "POST" )
8282
var getRoutes = routes.filter( ( r ) => r.verbs == "GET" )
8383

@@ -98,11 +98,9 @@ component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
9898
expect( names ).toInclude( "chat.info" )
9999
} )
100100
} )
101-
102101
} )
103102

104103
story( "I want argument validation on toAi()", function(){
105-
106104
given( "a numeric value as runnable", function(){
107105
then( "it should throw InvalidArgumentException", function(){
108106
expect( function(){
@@ -118,13 +116,10 @@ component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
118116
} ).toThrow( "InvalidArgumentException" )
119117
} )
120118
} )
121-
122119
} )
123-
124120
} )
125121

126122
describe( "MCP Routing — toMCP()", function(){
127-
128123
beforeEach( function(){
129124
variables.router = createMock( "coldbox.system.web.routing.Router" )
130125
.init()
@@ -136,7 +131,6 @@ component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
136131
} )
137132

138133
story( "I want to expose an MCP server via a route", function(){
139-
140134
given( "a valid server name", function(){
141135
then( "it should register one route with mcp=true and the server name", function(){
142136
router.route( "/mcp/filesystem" ).toMCP( "FileSystemServer" )
@@ -159,17 +153,18 @@ component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
159153

160154
given( "an MCP route with withSSL()", function(){
161155
then( "the route should have ssl=true", function(){
162-
router.route( "/mcp/filesystem" ).withSSL().toMCP( "FileSystemServer" )
156+
router
157+
.route( "/mcp/filesystem" )
158+
.withSSL()
159+
.toMCP( "FileSystemServer" )
163160
var routes = router.getRoutes()
164161

165162
expect( routes[ 1 ].ssl ).toBeTrue()
166163
} )
167164
} )
168-
169165
} )
170166

171167
story( "I want argument validation on toMCP()", function(){
172-
173168
given( "an empty string as serverName", function(){
174169
then( "it should throw InvalidArgumentException", function(){
175170
expect( function(){
@@ -185,7 +180,6 @@ component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
185180
} ).toThrow( "InvalidArgumentException" )
186181
} )
187182
} )
188-
189183
} )
190184

191185
story( "I want dynamic routing support on toMCP()", function(){
@@ -203,7 +197,6 @@ component extends="coldbox.system.testing.BaseModelTest" skip="notBoxlang"{
203197
} )
204198

205199
} )
206-
207200
}
208201

209202
}

0 commit comments

Comments
 (0)