@@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
33import type { Model } from "../../llm/types.js" ;
44import { AuthStorage } from "./auth-storage.js" ;
55import { createExtensionRuntime } from "./extensions/loader.js" ;
6- import type { LoadExtensionsResult , ToolDefinition } from "./extensions/types.js" ;
6+ import type { LoadExtensionsResult , RegisteredTool , ToolDefinition } from "./extensions/types.js" ;
77import { ModelRegistry } from "./model-registry.js" ;
88import type { ResourceLoader } from "./resource-loader.js" ;
99import { createAgentSession } from "./sdk.js" ;
@@ -30,17 +30,18 @@ function createEmptyResourceLoader(): ResourceLoader {
3030
3131function createResourceLoaderWithHandlers (
3232 handlers : Map < string , Array < ( ...args : unknown [ ] ) => Promise < unknown > > > ,
33+ tools : Map < string , RegisteredTool > = new Map ( ) ,
3334) : ResourceLoader {
3435 const extensionsResult : LoadExtensionsResult = {
3536 extensions :
36- handlers . size > 0
37+ handlers . size > 0 || tools . size > 0
3738 ? [
3839 {
3940 path : "<test-extension>" ,
4041 resolvedPath : "<test-extension>" ,
4142 sourceInfo : createSyntheticSourceInfo ( "<test-extension>" , { source : "temporary" } ) ,
4243 handlers,
43- tools : new Map ( ) ,
44+ tools,
4445 messageRenderers : new Map ( ) ,
4546 commands : new Map ( ) ,
4647 flags : new Map ( ) ,
@@ -64,6 +65,52 @@ function createResourceLoaderWithHandlers(
6465 } ;
6566}
6667
68+ function createTextTool ( name : string ) : ToolDefinition {
69+ return {
70+ name,
71+ label : "Test Tool" ,
72+ description : "Looks up a test value." ,
73+ parameters : Type . Object ( { } ) ,
74+ execute : async ( ) => ( {
75+ content : [ { type : "text" , text : "ok" } ] ,
76+ details : { } ,
77+ } ) ,
78+ } ;
79+ }
80+
81+ function createUnreadableNameTool ( ) : ToolDefinition {
82+ const tool = {
83+ label : "Broken Name" ,
84+ description : "Should be ignored." ,
85+ parameters : Type . Object ( { } ) ,
86+ execute : async ( ) => ( {
87+ content : [ { type : "text" , text : "bad" } ] ,
88+ details : { } ,
89+ } ) ,
90+ } as unknown as ToolDefinition ;
91+ Object . defineProperty ( tool , "name" , {
92+ get ( ) {
93+ throw new Error ( "bad\nname" ) ;
94+ } ,
95+ } ) ;
96+ return tool ;
97+ }
98+
99+ function createUnreadableParametersTool ( ) : ToolDefinition {
100+ return {
101+ name : "broken_params" ,
102+ label : "Broken Params" ,
103+ description : "Should be ignored." ,
104+ get parameters ( ) {
105+ throw new Error ( "bad\nparameters" ) ;
106+ } ,
107+ execute : async ( ) => ( {
108+ content : [ { type : "text" , text : "bad" } ] ,
109+ details : { } ,
110+ } ) ,
111+ } as unknown as ToolDefinition ;
112+ }
113+
67114describe ( "createAgentSession tool defaults" , ( ) => {
68115 it ( "forwards max thinking budgets from settings to the agent" , async ( ) => {
69116 const { session } = await createAgentSession ( {
@@ -117,6 +164,46 @@ describe("createAgentSession tool defaults", () => {
117164 expect ( session . getActiveToolNames ( ) ) . toEqual ( [ "custom_lookup" ] ) ;
118165 } ) ;
119166
167+ it ( "ignores custom tools with unreadable registry metadata" , async ( ) => {
168+ const { session } = await createAgentSession ( {
169+ model : testModel ,
170+ noTools : "builtin" ,
171+ customTools : [
172+ createUnreadableNameTool ( ) ,
173+ createUnreadableParametersTool ( ) ,
174+ createTextTool ( "custom_lookup" ) ,
175+ ] ,
176+ resourceLoader : createEmptyResourceLoader ( ) ,
177+ sessionManager : SessionManager . inMemory ( ) ,
178+ settingsManager : SettingsManager . inMemory ( ) ,
179+ modelRegistry : ModelRegistry . inMemory ( AuthStorage . inMemory ( ) ) ,
180+ } ) ;
181+
182+ expect ( session . getActiveToolNames ( ) ) . toEqual ( [ "custom_lookup" ] ) ;
183+ expect ( session . getAllTools ( ) . map ( ( tool ) => tool . name ) ) . toEqual ( [ "custom_lookup" ] ) ;
184+ } ) ;
185+
186+ it ( "ignores extension tools with unreadable registry metadata" , async ( ) => {
187+ const sourceInfo = createSyntheticSourceInfo ( "<test-extension>" , { source : "temporary" } ) ;
188+ const tools = new Map < string , RegisteredTool > ( [
189+ [ "unreadable_name" , { definition : createUnreadableNameTool ( ) , sourceInfo } ] ,
190+ [ "broken_params" , { definition : createUnreadableParametersTool ( ) , sourceInfo } ] ,
191+ [ "extension_lookup" , { definition : createTextTool ( "extension_lookup" ) , sourceInfo } ] ,
192+ ] ) ;
193+
194+ const { session } = await createAgentSession ( {
195+ model : testModel ,
196+ noTools : "builtin" ,
197+ resourceLoader : createResourceLoaderWithHandlers ( new Map ( ) , tools ) ,
198+ sessionManager : SessionManager . inMemory ( ) ,
199+ settingsManager : SettingsManager . inMemory ( ) ,
200+ modelRegistry : ModelRegistry . inMemory ( AuthStorage . inMemory ( ) ) ,
201+ } ) ;
202+
203+ expect ( session . getActiveToolNames ( ) ) . toEqual ( [ "extension_lookup" ] ) ;
204+ expect ( session . getAllTools ( ) . map ( ( tool ) => tool . name ) ) . toEqual ( [ "extension_lookup" ] ) ;
205+ } ) ;
206+
120207 it ( "preserves an exact base system prompt when active tools change" , async ( ) => {
121208 const customTool : ToolDefinition = {
122209 name : "custom_lookup" ,
0 commit comments