@@ -33,7 +33,7 @@ import {
3333 ParameterLocation ,
3434 type ToolParameters ,
3535} from './types' ;
36- import { StackOneAPIError } from './utils/errors ' ;
36+ import { StackOneAPIError } from './utils/error-stackone-api ' ;
3737
3838// Create a mock tool for testing
3939const createMockTool = ( headers ?: Record < string , string > ) : BaseTool => {
@@ -328,6 +328,157 @@ describe('StackOneTool', () => {
328328 } ) ;
329329} ) ;
330330
331+ describe ( 'BaseTool - additional coverage' , ( ) => {
332+ it ( 'should throw error when execute is called on non-HTTP tool' , async ( ) => {
333+ const rpcTool = new BaseTool (
334+ 'rpc_tool' ,
335+ 'RPC tool' ,
336+ { type : 'object' , properties : { } } ,
337+ {
338+ kind : 'rpc' ,
339+ method : 'test_method' ,
340+ url : 'https://api.example.com/rpc' ,
341+ payloadKeys : { action : 'action' , body : 'body' } ,
342+ } ,
343+ ) ;
344+
345+ await expect ( rpcTool . execute ( { } ) ) . rejects . toThrow (
346+ 'BaseTool.execute is only available for HTTP-backed tools' ,
347+ ) ;
348+ } ) ;
349+
350+ it ( 'should throw error for invalid parameter type' , async ( ) => {
351+ const tool = createMockTool ( ) ;
352+
353+ // @ts -expect-error - intentionally passing invalid type
354+ await expect ( tool . execute ( 12345 ) ) . rejects . toThrow ( 'Invalid parameters type' ) ;
355+ } ) ;
356+
357+ it ( 'should create execution metadata for RPC config in toAISDK' , async ( ) => {
358+ const rpcTool = new BaseTool (
359+ 'rpc_tool' ,
360+ 'RPC tool' ,
361+ { type : 'object' , properties : { } } ,
362+ {
363+ kind : 'rpc' ,
364+ method : 'test_method' ,
365+ url : 'https://api.example.com/rpc' ,
366+ payloadKeys : { action : 'action' , body : 'body' , headers : 'headers' } ,
367+ } ,
368+ ) ;
369+
370+ const aiSdkTool = await rpcTool . toAISDK ( { executable : false } ) ;
371+ const execution = aiSdkTool . rpc_tool . execution ;
372+
373+ expect ( execution ) . toBeDefined ( ) ;
374+ expect ( execution ?. config . kind ) . toBe ( 'rpc' ) ;
375+ if ( execution ?. config . kind === 'rpc' ) {
376+ expect ( execution . config . method ) . toBe ( 'test_method' ) ;
377+ expect ( execution . config . url ) . toBe ( 'https://api.example.com/rpc' ) ;
378+ expect ( execution . config . payloadKeys ) . toEqual ( {
379+ action : 'action' ,
380+ body : 'body' ,
381+ headers : 'headers' ,
382+ } ) ;
383+ }
384+ } ) ;
385+
386+ it ( 'should create execution metadata for local config in toAISDK' , async ( ) => {
387+ const localTool = new BaseTool (
388+ 'local_tool' ,
389+ 'Local tool' ,
390+ { type : 'object' , properties : { } } ,
391+ {
392+ kind : 'local' ,
393+ identifier : 'local_test' ,
394+ description : 'local://test' ,
395+ } ,
396+ ) ;
397+
398+ const aiSdkTool = await localTool . toAISDK ( { executable : false } ) ;
399+ const execution = aiSdkTool . local_tool . execution ;
400+
401+ expect ( execution ) . toBeDefined ( ) ;
402+ expect ( execution ?. config . kind ) . toBe ( 'local' ) ;
403+ if ( execution ?. config . kind === 'local' ) {
404+ expect ( execution . config . identifier ) . toBe ( 'local_test' ) ;
405+ expect ( execution . config . description ) . toBe ( 'local://test' ) ;
406+ }
407+ } ) ;
408+
409+ it ( 'should allow providing custom execution metadata in toAISDK' , async ( ) => {
410+ const tool = createMockTool ( ) ;
411+ const customExecution = {
412+ config : {
413+ kind : 'http' as const ,
414+ method : 'POST' as const ,
415+ url : 'https://custom.example.com' ,
416+ bodyType : 'json' as const ,
417+ params : [ ] ,
418+ } ,
419+ headers : { 'X-Custom' : 'value' } ,
420+ } ;
421+
422+ const aiSdkTool = await tool . toAISDK ( { execution : customExecution } ) ;
423+ const execution = aiSdkTool . test_tool . execution ;
424+
425+ expect ( execution ) . toBeDefined ( ) ;
426+ expect ( execution ?. config . kind ) . toBe ( 'http' ) ;
427+ if ( execution ?. config . kind === 'http' ) {
428+ expect ( execution . config . url ) . toBe ( 'https://custom.example.com' ) ;
429+ }
430+ expect ( execution ?. headers ) . toEqual ( { 'X-Custom' : 'value' } ) ;
431+ } ) ;
432+
433+ it ( 'should return undefined execution when execution option is false' , async ( ) => {
434+ const tool = createMockTool ( ) ;
435+
436+ const aiSdkTool = await tool . toAISDK ( { execution : false } ) ;
437+ expect ( aiSdkTool . test_tool . execution ) . toBeUndefined ( ) ;
438+ } ) ;
439+
440+ it ( 'should return undefined execute when executable option is false' , async ( ) => {
441+ const tool = createMockTool ( ) ;
442+
443+ const aiSdkTool = await tool . toAISDK ( { executable : false } ) ;
444+ expect ( aiSdkTool . test_tool . execute ) . toBeUndefined ( ) ;
445+ } ) ;
446+
447+ it ( 'should get headers from tool without requestBuilder' , ( ) => {
448+ const rpcTool = new BaseTool (
449+ 'rpc_tool' ,
450+ 'RPC tool' ,
451+ { type : 'object' , properties : { } } ,
452+ {
453+ kind : 'rpc' ,
454+ method : 'test_method' ,
455+ url : 'https://api.example.com/rpc' ,
456+ payloadKeys : { action : 'action' , body : 'body' } ,
457+ } ,
458+ { 'X-Custom' : 'value' } ,
459+ ) ;
460+
461+ expect ( rpcTool . getHeaders ( ) ) . toEqual ( { 'X-Custom' : 'value' } ) ;
462+ } ) ;
463+
464+ it ( 'should set headers on tool without requestBuilder' , ( ) => {
465+ const rpcTool = new BaseTool (
466+ 'rpc_tool' ,
467+ 'RPC tool' ,
468+ { type : 'object' , properties : { } } ,
469+ {
470+ kind : 'rpc' ,
471+ method : 'test_method' ,
472+ url : 'https://api.example.com/rpc' ,
473+ payloadKeys : { action : 'action' , body : 'body' } ,
474+ } ,
475+ ) ;
476+
477+ rpcTool . setHeaders ( { 'X-New-Header' : 'new-value' } ) ;
478+ expect ( rpcTool . getHeaders ( ) ) . toEqual ( { 'X-New-Header' : 'new-value' } ) ;
479+ } ) ;
480+ } ) ;
481+
331482describe ( 'Tools' , ( ) => {
332483 it ( 'should get tool by name' , ( ) => {
333484 const tool = createMockTool ( ) ;
@@ -952,6 +1103,40 @@ describe('Meta Search Tools', () => {
9521103 } ) ;
9531104 } ) ;
9541105
1106+ describe ( 'Error handling' , ( ) => {
1107+ it ( 'should wrap non-StackOneError in meta_search_tools execute' , async ( ) => {
1108+ const filterTool = metaTools . getTool ( 'meta_search_tools' ) ;
1109+ assert ( filterTool , 'filterTool should be defined' ) ;
1110+
1111+ // Pass invalid params type to trigger JSON.parse error on non-JSON string
1112+ await expect ( filterTool . execute ( 'not valid json' ) ) . rejects . toThrow ( 'Error executing tool:' ) ;
1113+ } ) ;
1114+
1115+ it ( 'should wrap non-StackOneError in meta_execute_tool execute' , async ( ) => {
1116+ const executeTool = metaTools . getTool ( 'meta_execute_tool' ) ;
1117+ assert ( executeTool , 'executeTool should be defined' ) ;
1118+
1119+ // Pass invalid JSON string to trigger JSON.parse error
1120+ await expect ( executeTool . execute ( 'not valid json' ) ) . rejects . toThrow ( 'Error executing tool:' ) ;
1121+ } ) ;
1122+
1123+ it ( 'should throw StackOneError for invalid params type in meta_search_tools' , async ( ) => {
1124+ const filterTool = metaTools . getTool ( 'meta_search_tools' ) ;
1125+ assert ( filterTool , 'filterTool should be defined' ) ;
1126+
1127+ // @ts -expect-error - intentionally passing invalid type
1128+ await expect ( filterTool . execute ( 123 ) ) . rejects . toThrow ( 'Invalid parameters type' ) ;
1129+ } ) ;
1130+
1131+ it ( 'should throw StackOneError for invalid params type in meta_execute_tool' , async ( ) => {
1132+ const executeTool = metaTools . getTool ( 'meta_execute_tool' ) ;
1133+ assert ( executeTool , 'executeTool should be defined' ) ;
1134+
1135+ // @ts -expect-error - intentionally passing invalid type
1136+ await expect ( executeTool . execute ( true ) ) . rejects . toThrow ( 'Invalid parameters type' ) ;
1137+ } ) ;
1138+ } ) ;
1139+
9551140 describe ( 'Integration: meta tools workflow' , ( ) => {
9561141 it ( 'should discover and execute tools in sequence' , async ( ) => {
9571142 const filterTool = metaTools . getTool ( 'meta_search_tools' ) ;
0 commit comments