@@ -1210,6 +1210,107 @@ export class HttpDispatcher {
12101210 return s . charAt ( 0 ) . toUpperCase ( ) + s . slice ( 1 ) ;
12111211 }
12121212
1213+ /**
1214+ * Handle AI service routes (/ai/chat, /ai/models, /ai/conversations, etc.)
1215+ * Resolves the AI service and its built-in route handlers, then dispatches.
1216+ */
1217+ async handleAI ( subPath : string , method : string , body : any , query : any , _context : HttpProtocolContext ) : Promise < HttpDispatcherResult > {
1218+ let aiService : any ;
1219+ try {
1220+ aiService = await this . resolveService ( 'ai' ) ;
1221+ } catch {
1222+ // AI service not registered
1223+ }
1224+
1225+ if ( ! aiService ) {
1226+ return {
1227+ handled : true ,
1228+ response : {
1229+ status : 404 ,
1230+ body : { success : false , error : { message : 'AI service is not configured' , code : 404 } } ,
1231+ } ,
1232+ } ;
1233+ }
1234+
1235+ // The AI service exposes route definitions via buildAIRoutes.
1236+ // We match the request path against known AI route patterns.
1237+ const fullPath = `/api/v1${ subPath } ` ;
1238+
1239+ // Build a simple param-extracting matcher for route patterns like /api/v1/ai/conversations/:id
1240+ const matchRoute = ( pattern : string , path : string ) : Record < string , string > | null => {
1241+ const patternParts = pattern . split ( '/' ) ;
1242+ const pathParts = path . split ( '/' ) ;
1243+ if ( patternParts . length !== pathParts . length ) return null ;
1244+ const params : Record < string , string > = { } ;
1245+ for ( let i = 0 ; i < patternParts . length ; i ++ ) {
1246+ if ( patternParts [ i ] . startsWith ( ':' ) ) {
1247+ params [ patternParts [ i ] . substring ( 1 ) ] = pathParts [ i ] ;
1248+ } else if ( patternParts [ i ] !== pathParts [ i ] ) {
1249+ return null ;
1250+ }
1251+ }
1252+ return params ;
1253+ } ;
1254+
1255+ // Try to get route definitions from the AI service's cached routes
1256+ const routes = ( this . kernel as any ) . __aiRoutes as Array < {
1257+ method : string ; path : string ; handler : ( req : any ) => Promise < any > ;
1258+ } > | undefined ;
1259+
1260+ if ( ! routes ) {
1261+ return {
1262+ handled : true ,
1263+ response : {
1264+ status : 503 ,
1265+ body : { success : false , error : { message : 'AI service routes not yet initialized' , code : 503 } } ,
1266+ } ,
1267+ } ;
1268+ }
1269+
1270+ for ( const route of routes ) {
1271+ if ( route . method !== method ) continue ;
1272+ const params = matchRoute ( route . path , fullPath ) ;
1273+ if ( params === null ) continue ;
1274+
1275+ const result = await route . handler ( { body, params, query } ) ;
1276+
1277+ if ( result . stream && result . events ) {
1278+ // Return a streaming result for the adapter to handle
1279+ return {
1280+ handled : true ,
1281+ result : {
1282+ type : 'stream' ,
1283+ contentType : result . vercelDataStream
1284+ ? 'text/plain; charset=utf-8'
1285+ : 'text/event-stream' ,
1286+ events : result . events ,
1287+ vercelDataStream : result . vercelDataStream ,
1288+ headers : {
1289+ 'Content-Type' : result . vercelDataStream
1290+ ? 'text/plain; charset=utf-8'
1291+ : 'text/event-stream' ,
1292+ 'Cache-Control' : 'no-cache' ,
1293+ 'Connection' : 'keep-alive' ,
1294+ } ,
1295+ } ,
1296+ } ;
1297+ }
1298+
1299+ return {
1300+ handled : true ,
1301+ response : {
1302+ status : result . status ,
1303+ body : result . body ,
1304+ } ,
1305+ } ;
1306+ }
1307+
1308+ return {
1309+ handled : true ,
1310+ response : this . routeNotFound ( subPath ) ,
1311+ } ;
1312+ }
1313+
12131314 /**
12141315 * Main Dispatcher Entry Point
12151316 * Routes the request to the appropriate handler based on path and precedence
@@ -1284,6 +1385,11 @@ export class HttpDispatcher {
12841385 return this . handleI18n ( cleanPath . substring ( 5 ) , method , query , context ) ;
12851386 }
12861387
1388+ // AI Service — delegate to the registered AI route handlers
1389+ if ( cleanPath . startsWith ( '/ai' ) ) {
1390+ return this . handleAI ( cleanPath , method , body , query , context ) ;
1391+ }
1392+
12871393 // OpenAPI Specification
12881394 if ( cleanPath === '/openapi.json' && method === 'GET' ) {
12891395 const broker = this . ensureBroker ( ) ;
0 commit comments