@@ -38,6 +38,10 @@ import {
3838 zodSchemaToJsonString ,
3939 buildJsonSchemaInstruction ,
4040} from '../../utils/zod-schema.js' ;
41+ import {
42+ getCapability ,
43+ type CapabilityRuntimeContext ,
44+ } from '../../capabilities/index.js' ;
4145
4246// Define tool hook context - provides access to messages and tool call details
4347export type ToolHookContext = {
@@ -251,6 +255,23 @@ const ExpectedOutputSchema = z.union([
251255/** Type for conversation history messages - enables KV cache optimization */
252256export type ConversationMessage = z . infer < typeof ConversationMessageSchema > ;
253257
258+ // Schema for a single capability configuration on the AI agent
259+ const CapabilityConfigSchema = z . object ( {
260+ id : z
261+ . string ( )
262+ . min ( 1 )
263+ . describe ( 'Capability ID (e.g., "google-doc-knowledge-base")' ) ,
264+ inputs : z
265+ . record ( z . string ( ) , z . union ( [ z . string ( ) , z . number ( ) , z . boolean ( ) ] ) )
266+ . default ( { } )
267+ . describe ( 'Input parameter values for this capability' ) ,
268+ credentials : z
269+ . record ( z . nativeEnum ( CredentialType ) , z . string ( ) )
270+ . default ( { } )
271+ . optional ( )
272+ . describe ( 'Capability-specific credentials (injected at runtime)' ) ,
273+ } ) ;
274+
254275// Define the parameters schema for the AI Agent bubble
255276const AIAgentParamsSchema = z . object ( {
256277 message : z
@@ -322,6 +343,13 @@ const AIAgentParamsSchema = z.object({
322343 . describe (
323344 'Enable real-time streaming of tokens, tool calls, and iteration progress'
324345 ) ,
346+ capabilities : z
347+ . array ( CapabilityConfigSchema )
348+ . default ( [ ] )
349+ . optional ( )
350+ . describe (
351+ 'Capabilities that extend the agent with bundled tools, prompts, and credentials. Example: [{ id: "google-doc-knowledge-base", inputs: { docId: "your-doc-id" } }]'
352+ ) ,
325353 expectedOutputSchema : ExpectedOutputSchema . optional ( ) . describe (
326354 'Zod schema or JSON schema string that defines the expected structure of the AI response. When provided, automatically enables JSON mode and instructs the AI to output in the exact format. Example: z.object({ summary: z.string(), items: z.array(z.object({ name: z.string(), score: z.number() })) })'
327355 ) ,
@@ -522,6 +550,28 @@ export class AIAgentBubble extends ServiceBubble<
522550 ) ;
523551 this . params . systemPrompt = `${ this . params . systemPrompt } \n\n${ buildJsonSchemaInstruction ( schemaString ) } ` ;
524552 }
553+
554+ // Inject capability system prompts
555+ for ( const capConfig of this . params . capabilities ?? [ ] ) {
556+ const capDef = getCapability ( capConfig . id ) ;
557+ if ( ! capDef ) continue ;
558+
559+ const ctx : CapabilityRuntimeContext = {
560+ credentials :
561+ ( capConfig . credentials as Partial < Record < CredentialType , string > > ) ??
562+ { } ,
563+ inputs : capConfig . inputs ?? { } ,
564+ bubbleContext : this . context ,
565+ } ;
566+
567+ const addition =
568+ capDef . createSystemPrompt ?.( ctx ) ??
569+ capDef . metadata . systemPromptAddition ;
570+
571+ if ( addition ) {
572+ this . params . systemPrompt = `${ this . params . systemPrompt } \n\n${ addition } ` ;
573+ }
574+ }
525575 }
526576
527577 protected async performAction (
@@ -1059,9 +1109,118 @@ export class AIAgentBubble extends ServiceBubble<
10591109 }
10601110 }
10611111
1112+ // 3. Capability tools
1113+ for ( const capConfig of this . params . capabilities ?? [ ] ) {
1114+ const capDef = getCapability ( capConfig . id ) ;
1115+ if ( ! capDef ) {
1116+ console . warn (
1117+ `[AIAgent] Capability '${ capConfig . id } ' not found in registry. Skipping.`
1118+ ) ;
1119+ continue ;
1120+ }
1121+
1122+ try {
1123+ const ctx : CapabilityRuntimeContext = {
1124+ credentials :
1125+ ( capConfig . credentials as Partial <
1126+ Record < CredentialType , string >
1127+ > ) ?? { } ,
1128+ inputs : capConfig . inputs ?? { } ,
1129+ bubbleContext : this . context ,
1130+ } ;
1131+
1132+ const toolFuncs = capDef . createTools ( ctx ) ;
1133+
1134+ for ( const toolMeta of capDef . metadata . tools ) {
1135+ const func = toolFuncs [ toolMeta . name ] ;
1136+ if ( ! func ) continue ;
1137+
1138+ // Convert JSON schema back to Zod for DynamicStructuredTool
1139+ const toolSchema = this . jsonSchemaToZod ( toolMeta . parameterSchema ) ;
1140+
1141+ const dynamicTool = new DynamicStructuredTool ( {
1142+ name : toolMeta . name ,
1143+ description : toolMeta . description ,
1144+ schema : toolSchema ,
1145+ func : func as ( input : Record < string , unknown > ) => Promise < unknown > ,
1146+ } as any ) ;
1147+
1148+ tools . push ( dynamicTool ) ;
1149+ console . log (
1150+ `🔧 [AIAgent] Registered capability tool: ${ toolMeta . name } (from ${ capConfig . id } )`
1151+ ) ;
1152+ }
1153+ } catch ( error ) {
1154+ console . error (
1155+ `Error initializing capability '${ capConfig . id } ':` ,
1156+ error
1157+ ) ;
1158+ continue ;
1159+ }
1160+ }
1161+
10621162 return tools ;
10631163 }
10641164
1165+ /**
1166+ * Converts a JSON Schema object to a Zod schema for DynamicStructuredTool.
1167+ * Handles common JSON Schema types used by capability tool definitions.
1168+ */
1169+ private jsonSchemaToZod (
1170+ jsonSchema : Record < string , unknown >
1171+ ) : z . ZodObject < z . ZodRawShape > {
1172+ const properties = (
1173+ jsonSchema as { properties ?: Record < string , Record < string , unknown > > }
1174+ ) . properties ;
1175+ const required = ( jsonSchema as { required ?: string [ ] } ) . required ?? [ ] ;
1176+
1177+ if ( ! properties || Object . keys ( properties ) . length === 0 ) {
1178+ return z . object ( { } ) ;
1179+ }
1180+
1181+ const shape : z . ZodRawShape = { } ;
1182+ for ( const [ key , prop ] of Object . entries ( properties ) ) {
1183+ let fieldSchema : z . ZodTypeAny ;
1184+
1185+ switch ( prop . type ) {
1186+ case 'string' :
1187+ fieldSchema = z . string ( ) ;
1188+ if ( prop . description )
1189+ fieldSchema = fieldSchema . describe ( prop . description as string ) ;
1190+ break ;
1191+ case 'number' :
1192+ case 'integer' :
1193+ fieldSchema = z . number ( ) ;
1194+ if ( prop . description )
1195+ fieldSchema = fieldSchema . describe ( prop . description as string ) ;
1196+ break ;
1197+ case 'boolean' :
1198+ fieldSchema = z . boolean ( ) ;
1199+ if ( prop . description )
1200+ fieldSchema = fieldSchema . describe ( prop . description as string ) ;
1201+ break ;
1202+ case 'array' :
1203+ fieldSchema = z . array ( z . unknown ( ) ) ;
1204+ if ( prop . description )
1205+ fieldSchema = fieldSchema . describe ( prop . description as string ) ;
1206+ break ;
1207+ default :
1208+ fieldSchema = z . unknown ( ) ;
1209+ if ( prop . description )
1210+ fieldSchema = fieldSchema . describe ( prop . description as string ) ;
1211+ break ;
1212+ }
1213+
1214+ if ( ! required . includes ( key ) ) {
1215+ fieldSchema = fieldSchema . optional ( ) ;
1216+ }
1217+
1218+ shape [ key ] = fieldSchema ;
1219+ }
1220+
1221+ return z . object ( shape ) ;
1222+ }
1223+
10651224 /**
10661225 * Custom tool execution node that supports hooks
10671226 */
0 commit comments