@@ -142,6 +142,134 @@ 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 ( 'keeps the key required for conditionally hosted tools' , ( ) => {
213+ const enabled = Object . assign (
214+ ( params : Record < string , unknown > ) => params . provider === 'falai' ,
215+ {
216+ condition : { field : 'provider' , operator : 'equals' as const , value : 'falai' } ,
217+ }
218+ )
219+ const conditionalTool = {
220+ ...mockToolConfig ,
221+ id : 'conditional_hosted_tool' ,
222+ params : {
223+ apiKey : {
224+ type : 'string' ,
225+ required : true ,
226+ visibility : 'user-only' as ParameterVisibility ,
227+ description : 'Provider API Key' ,
228+ } ,
229+ } ,
230+ hosting : {
231+ enabled,
232+ envKeyPrefix : 'FALAI_API_KEY' ,
233+ apiKeyParam : 'apiKey' ,
234+ byokProviderId : 'falai' ,
235+ pricing : { type : 'per_request' as const , cost : 0.01 } ,
236+ rateLimit : { mode : 'per_request' as const , requestsPerMinute : 100 } ,
237+ } ,
238+ }
239+
240+ const schema = createUserToolSchema ( conditionalTool , {
241+ surface : 'copilot' ,
242+ hostedKeySupport : true ,
243+ } )
244+
245+ // Injection only happens when the predicate passes at runtime, so the
246+ // schema must not promise a hosted key for every configuration.
247+ expect ( schema . required ) . toContain ( 'apiKey' )
248+ expect ( schema . properties . apiKey . description ) . not . toContain ( 'hosted key' )
249+ } )
250+
251+ it . concurrent ( 'does not relax required keys on tools without hosting' , ( ) => {
252+ const plainTool = {
253+ ...mockToolConfig ,
254+ id : 'plain_key_tool' ,
255+ params : {
256+ apiKey : {
257+ type : 'string' ,
258+ required : true ,
259+ visibility : 'user-only' as ParameterVisibility ,
260+ description : 'Service API Key' ,
261+ } ,
262+ } ,
263+ }
264+
265+ const schema = createUserToolSchema ( plainTool , {
266+ surface : 'copilot' ,
267+ hostedKeySupport : true ,
268+ } )
269+
270+ expect ( schema . required ) . toContain ( 'apiKey' )
271+ } )
272+
145273 it . concurrent ( 'adds credentialId only for copilot-facing oauth schemas' , ( ) => {
146274 const oauthTool = {
147275 ...mockToolConfig ,
0 commit comments