@@ -142,6 +142,95 @@ describe('Tool Parameters Utils', () => {
142142 expect ( schema . properties ) . toHaveProperty ( 'message' )
143143 } )
144144
145+ it . concurrent ( 'keeps the hosted key param optional when hosted keys are supported' , ( ) => {
146+ const hostedTool = {
147+ ...mockToolConfig ,
148+ id : 'hosted_key_tool' ,
149+ params : {
150+ query : {
151+ type : 'string' ,
152+ required : true ,
153+ visibility : 'user-or-llm' as ParameterVisibility ,
154+ description : 'Search query' ,
155+ } ,
156+ apiKey : {
157+ type : 'string' ,
158+ required : true ,
159+ visibility : 'user-only' as ParameterVisibility ,
160+ description : 'Exa AI API Key' ,
161+ } ,
162+ } ,
163+ hosting : {
164+ envKeyPrefix : 'EXA_API_KEY' ,
165+ apiKeyParam : 'apiKey' ,
166+ byokProviderId : 'exa' ,
167+ pricing : { type : 'per_request' as const , cost : 0.005 } ,
168+ rateLimit : { mode : 'per_request' as const , requestsPerMinute : 100 } ,
169+ } ,
170+ }
171+
172+ const hostedSchema = createUserToolSchema ( hostedTool , {
173+ surface : 'copilot' ,
174+ hostedKeySupport : true ,
175+ } )
176+
177+ // The key stays available as a bring-your-own-key override but is never
178+ // a required argument — the executor injects the hosted key server-side.
179+ expect ( hostedSchema . properties ) . toHaveProperty ( 'apiKey' )
180+ expect ( hostedSchema . required ) . not . toContain ( 'apiKey' )
181+ expect ( hostedSchema . properties . apiKey . description ) . toContain ( 'hosted key' )
182+ expect ( hostedSchema . required ) . toContain ( 'query' )
183+ } )
184+
185+ it . concurrent ( 'keeps the hosted key param required without hosted key support' , ( ) => {
186+ const hostedTool = {
187+ ...mockToolConfig ,
188+ id : 'hosted_key_tool_self_hosted' ,
189+ params : {
190+ apiKey : {
191+ type : 'string' ,
192+ required : true ,
193+ visibility : 'user-only' as ParameterVisibility ,
194+ description : 'Exa AI API Key' ,
195+ } ,
196+ } ,
197+ hosting : {
198+ envKeyPrefix : 'EXA_API_KEY' ,
199+ apiKeyParam : 'apiKey' ,
200+ byokProviderId : 'exa' ,
201+ pricing : { type : 'per_request' as const , cost : 0.005 } ,
202+ rateLimit : { mode : 'per_request' as const , requestsPerMinute : 100 } ,
203+ } ,
204+ }
205+
206+ const selfHostedSchema = createUserToolSchema ( hostedTool , { surface : 'copilot' } )
207+
208+ expect ( selfHostedSchema . required ) . toContain ( 'apiKey' )
209+ expect ( selfHostedSchema . properties . apiKey . description ) . not . toContain ( 'hosted key' )
210+ } )
211+
212+ it . concurrent ( 'does not relax required keys on tools without hosting' , ( ) => {
213+ const plainTool = {
214+ ...mockToolConfig ,
215+ id : 'plain_key_tool' ,
216+ params : {
217+ apiKey : {
218+ type : 'string' ,
219+ required : true ,
220+ visibility : 'user-only' as ParameterVisibility ,
221+ description : 'Service API Key' ,
222+ } ,
223+ } ,
224+ }
225+
226+ const schema = createUserToolSchema ( plainTool , {
227+ surface : 'copilot' ,
228+ hostedKeySupport : true ,
229+ } )
230+
231+ expect ( schema . required ) . toContain ( 'apiKey' )
232+ } )
233+
145234 it . concurrent ( 'adds credentialId only for copilot-facing oauth schemas' , ( ) => {
146235 const oauthTool = {
147236 ...mockToolConfig ,
0 commit comments