@@ -19,10 +19,15 @@ import {
19
19
type AxiosRequestConfig ,
20
20
type AxiosResponse ,
21
21
} from 'axios' ;
22
- import { ZodManifestSchema , createZodSchemaFromParams } from './protocol.js' ;
22
+ import {
23
+ ZodManifestSchema ,
24
+ createZodSchemaFromParams ,
25
+ ParameterSchema ,
26
+ } from './protocol.js' ;
23
27
import { logApiError } from './errorUtils.js' ;
24
28
import { ZodError } from 'zod' ;
25
- import { BoundParams , BoundValue , resolveValue } from './utils.js' ;
29
+ import { BoundParams , identifyAuthRequirements , resolveValue } from './utils.js' ;
30
+ import { AuthTokenGetters , RequiredAuthnParams } from './tool.js' ;
26
31
27
32
type Manifest = import ( 'zod' ) . infer < typeof ZodManifestSchema > ;
28
33
type ToolSchemaFromManifest = Manifest [ 'tools' ] [ string ] ;
@@ -123,33 +128,52 @@ class ToolboxClient {
123
128
#createToolInstance(
124
129
toolName : string ,
125
130
toolSchema : ToolSchemaFromManifest ,
131
+ authTokenGetters : AuthTokenGetters = { } ,
126
132
boundParams : BoundParams = { }
127
133
) : {
128
134
tool : ReturnType < typeof ToolboxTool > ;
135
+ usedAuthKeys : Set < string > ;
129
136
usedBoundKeys : Set < string > ;
130
137
} {
131
- const toolParamNames = new Set ( toolSchema . parameters . map ( p => p . name ) ) ;
132
- const applicableBoundParams : Record < string , BoundValue > = { } ;
133
- const usedBoundKeys = new Set < string > ( ) ;
134
-
135
- for ( const key in boundParams ) {
136
- if ( toolParamNames . has ( key ) ) {
137
- applicableBoundParams [ key ] = boundParams [ key ] ;
138
- usedBoundKeys . add ( key ) ;
138
+ const params : ParameterSchema [ ] = [ ] ;
139
+ const authParams : RequiredAuthnParams = { } ;
140
+ const currBoundParams : BoundParams = { } ;
141
+
142
+ for ( const p of toolSchema . parameters ) {
143
+ if ( p . authSources && p . authSources . length > 0 ) {
144
+ authParams [ p . name ] = p . authSources ;
145
+ } else if ( boundParams && p . name in boundParams ) {
146
+ currBoundParams [ p . name ] = boundParams [ p . name ] ;
147
+ } else {
148
+ params . push ( p ) ;
139
149
}
140
150
}
141
151
142
- const paramZodSchema = createZodSchemaFromParams ( toolSchema . parameters ) ;
152
+ const [ remainingAuthnParams , remainingAuthzTokens , usedAuthKeys ] =
153
+ identifyAuthRequirements (
154
+ authParams ,
155
+ toolSchema . authRequired || [ ] ,
156
+ authTokenGetters ? Object . keys ( authTokenGetters ) : [ ]
157
+ ) ;
158
+
159
+ const paramZodSchema = createZodSchemaFromParams ( params ) ;
160
+
143
161
const tool = ToolboxTool (
144
162
this . #session,
145
163
this . #baseUrl,
146
164
toolName ,
147
165
toolSchema . description ,
148
166
paramZodSchema ,
149
- applicableBoundParams ,
167
+ authTokenGetters ,
168
+ remainingAuthnParams ,
169
+ remainingAuthzTokens ,
170
+ currBoundParams ,
150
171
this . #clientHeaders
151
172
) ;
152
- return { tool, usedBoundKeys} ;
173
+
174
+ const usedBoundKeys = new Set ( Object . keys ( currBoundParams ) ) ;
175
+
176
+ return { tool, usedAuthKeys, usedBoundKeys} ;
153
177
}
154
178
155
179
/**
@@ -159,40 +183,59 @@ class ToolboxClient {
159
183
* tool remotely.
160
184
*
161
185
* @param {string } name - The unique name or identifier of the tool to load.
162
- * @param {BoundParams } [boundParams] - Optional parameters to pre-bind to the tool.
186
+ * @param {AuthTokenGetters | null } [authTokenGetters] - Optional map of auth service names to token getters.
187
+ * @param {BoundParams | null } [boundParams] - Optional parameters to pre-bind to the tool.
163
188
* @returns {Promise<ReturnType<typeof ToolboxTool>> } A promise that resolves
164
189
* to a ToolboxTool function, ready for execution.
165
190
* @throws {Error } If the tool is not found in the manifest, the manifest structure is invalid,
166
191
* or if there's an error fetching data from the API.
167
192
*/
168
193
async loadTool (
169
194
name : string ,
170
- boundParams : BoundParams = { }
195
+ authTokenGetters : AuthTokenGetters | null = { } ,
196
+ boundParams : BoundParams | null = { }
171
197
) : Promise < ReturnType < typeof ToolboxTool > > {
172
198
const apiPath = `/api/tool/${ name } ` ;
173
199
const manifest = await this . #fetchAndParseManifest( apiPath ) ;
174
200
175
201
if (
176
- manifest . tools && // Zod ensures manifest.tools exists if schema requires it
202
+ manifest . tools &&
177
203
Object . prototype . hasOwnProperty . call ( manifest . tools , name )
178
204
) {
179
205
const specificToolSchema = manifest . tools [ name ] ;
180
- const { tool, usedBoundKeys} = this . #createToolInstance(
206
+ const { tool, usedAuthKeys , usedBoundKeys} = this . #createToolInstance(
181
207
name ,
182
208
specificToolSchema ,
183
- boundParams
209
+ authTokenGetters || undefined ,
210
+ boundParams || { }
184
211
) ;
185
212
186
- const providedBoundKeys = Object . keys ( boundParams ) ;
187
- const unusedBound = providedBoundKeys . filter (
213
+ const providedAuthKeys = new Set (
214
+ authTokenGetters ? Object . keys ( authTokenGetters ) : [ ]
215
+ ) ;
216
+ const providedBoundKeys = new Set (
217
+ boundParams ? Object . keys ( boundParams ) : [ ]
218
+ ) ;
219
+ const unusedAuth = [ ...providedAuthKeys ] . filter (
220
+ key => ! usedAuthKeys . has ( key )
221
+ ) ;
222
+ const unusedBound = [ ...providedBoundKeys ] . filter (
188
223
key => ! usedBoundKeys . has ( key )
189
224
) ;
190
225
226
+ const errorMessages : string [ ] = [ ] ;
227
+ if ( unusedAuth . length > 0 ) {
228
+ errorMessages . push ( `unused auth tokens: ${ unusedAuth . join ( ', ' ) } ` ) ;
229
+ }
191
230
if ( unusedBound . length > 0 ) {
231
+ errorMessages . push (
232
+ `unused bound parameters: ${ unusedBound . join ( ', ' ) } `
233
+ ) ;
234
+ }
235
+
236
+ if ( errorMessages . length > 0 ) {
192
237
throw new Error (
193
- `Validation failed for tool '${ name } ': unused bound parameters: ${ unusedBound . join (
194
- ', '
195
- ) } .`
238
+ `Validation failed for tool '${ name } ': ${ errorMessages . join ( '; ' ) } .`
196
239
) ;
197
240
}
198
241
return tool ;
@@ -205,46 +248,95 @@ class ToolboxClient {
205
248
* Asynchronously fetches a toolset and loads all tools defined within it.
206
249
*
207
250
* @param {string | null } [name] - Name of the toolset to load. If null or undefined, loads the default toolset.
208
- * @param {BoundParams } [boundParams] - Optional parameters to pre-bind to the tools in the toolset.
251
+ * @param {AuthTokenGetters | null } [authTokenGetters] - Optional map of auth service names to token getters.
252
+ * @param {BoundParams | null } [boundParams] - Optional parameters to pre-bind to the tools in the toolset.
253
+ * @param {boolean } [strict=false] - If true, throws an error if any provided auth token or bound param is not used by at least one tool.
209
254
* @returns {Promise<Array<ReturnType<typeof ToolboxTool>>> } A promise that resolves
210
255
* to a list of ToolboxTool functions, ready for execution.
211
256
* @throws {Error } If the manifest structure is invalid or if there's an error fetching data from the API.
212
257
*/
213
258
async loadToolset (
214
259
name ?: string ,
215
- boundParams : BoundParams = { }
260
+ authTokenGetters : AuthTokenGetters | null = { } ,
261
+ boundParams : BoundParams | null = { } ,
262
+ strict = false
216
263
) : Promise < Array < ReturnType < typeof ToolboxTool > > > {
217
264
const toolsetName = name || '' ;
218
265
const apiPath = `/api/toolset/${ toolsetName } ` ;
219
266
220
267
const manifest = await this . #fetchAndParseManifest( apiPath ) ;
221
268
const tools : Array < ReturnType < typeof ToolboxTool > > = [ ] ;
222
269
223
- const providedBoundKeys = new Set ( Object . keys ( boundParams ) ) ;
270
+ const overallUsedAuthKeys : Set < string > = new Set ( ) ;
224
271
const overallUsedBoundParams : Set < string > = new Set ( ) ;
272
+ const providedAuthKeys = new Set (
273
+ authTokenGetters ? Object . keys ( authTokenGetters ) : [ ]
274
+ ) ;
275
+ const providedBoundKeys = new Set (
276
+ boundParams ? Object . keys ( boundParams ) : [ ]
277
+ ) ;
225
278
226
279
for ( const [ toolName , toolSchema ] of Object . entries ( manifest . tools ) ) {
227
- const { tool, usedBoundKeys} = this . #createToolInstance(
280
+ const { tool, usedAuthKeys , usedBoundKeys} = this . #createToolInstance(
228
281
toolName ,
229
282
toolSchema ,
230
- boundParams
283
+ authTokenGetters || { } ,
284
+ boundParams || { }
231
285
) ;
232
286
tools . push ( tool ) ;
233
- usedBoundKeys . forEach ( ( key : string ) => overallUsedBoundParams . add ( key ) ) ;
287
+
288
+ if ( strict ) {
289
+ const unusedAuth = [ ...providedAuthKeys ] . filter (
290
+ key => ! usedAuthKeys . has ( key )
291
+ ) ;
292
+ const unusedBound = [ ...providedBoundKeys ] . filter (
293
+ key => ! usedBoundKeys . has ( key )
294
+ ) ;
295
+ const errorMessages : string [ ] = [ ] ;
296
+ if ( unusedAuth . length > 0 ) {
297
+ errorMessages . push ( `unused auth tokens: ${ unusedAuth . join ( ', ' ) } ` ) ;
298
+ }
299
+ if ( unusedBound . length > 0 ) {
300
+ errorMessages . push (
301
+ `unused bound parameters: ${ unusedBound . join ( ', ' ) } `
302
+ ) ;
303
+ }
304
+ if ( errorMessages . length > 0 ) {
305
+ throw new Error (
306
+ `Validation failed for tool '${ toolName } ': ${ errorMessages . join ( '; ' ) } .`
307
+ ) ;
308
+ }
309
+ } else {
310
+ usedAuthKeys . forEach ( key => overallUsedAuthKeys . add ( key ) ) ;
311
+ usedBoundKeys . forEach ( key => overallUsedBoundParams . add ( key ) ) ;
312
+ }
234
313
}
235
314
236
- const unusedBound = [ ...providedBoundKeys ] . filter (
237
- k => ! overallUsedBoundParams . has ( k )
238
- ) ;
239
- if ( unusedBound . length > 0 ) {
240
- throw new Error (
241
- `Validation failed for toolset '${
242
- name || 'default'
243
- } ': unused bound parameters could not be applied to any tool: ${ unusedBound . join (
244
- ', '
245
- ) } .`
315
+ if ( ! strict ) {
316
+ const unusedAuth = [ ...providedAuthKeys ] . filter (
317
+ key => ! overallUsedAuthKeys . has ( key )
318
+ ) ;
319
+ const unusedBound = [ ...providedBoundKeys ] . filter (
320
+ key => ! overallUsedBoundParams . has ( key )
246
321
) ;
322
+ const errorMessages : string [ ] = [ ] ;
323
+ if ( unusedAuth . length > 0 ) {
324
+ errorMessages . push (
325
+ `unused auth tokens could not be applied to any tool: ${ unusedAuth . join ( ', ' ) } `
326
+ ) ;
327
+ }
328
+ if ( unusedBound . length > 0 ) {
329
+ errorMessages . push (
330
+ `unused bound parameters could not be applied to any tool: ${ unusedBound . join ( ', ' ) } `
331
+ ) ;
332
+ }
333
+ if ( errorMessages . length > 0 ) {
334
+ throw new Error (
335
+ `Validation failed for toolset '${ name || 'default' } ': ${ errorMessages . join ( '; ' ) } .`
336
+ ) ;
337
+ }
247
338
}
339
+
248
340
return tools ;
249
341
}
250
342
}
0 commit comments