@@ -18,6 +18,7 @@ import {type AxiosInstance, type AxiosResponse} from 'axios';
18
18
import { ZodManifestSchema , createZodSchemaFromParams } from './protocol.js' ;
19
19
import { logApiError } from './errorUtils.js' ;
20
20
import { ZodError } from 'zod' ;
21
+ import { BoundParams , BoundValue } from './utils.js' ;
21
22
22
23
type Manifest = import ( 'zod' ) . infer < typeof ZodManifestSchema > ;
23
24
type ToolSchemaFromManifest = Manifest [ 'tools' ] [ string ] ;
@@ -27,8 +28,8 @@ type ToolSchemaFromManifest = Manifest['tools'][string];
27
28
* Manages an Axios Client Session, if not provided.
28
29
*/
29
30
class ToolboxClient {
30
- private _baseUrl : string ;
31
- private _session : AxiosInstance ;
31
+ #baseUrl : string ;
32
+ #session : AxiosInstance ;
32
33
33
34
/**
34
35
* Initializes the ToolboxClient.
@@ -37,21 +38,20 @@ class ToolboxClient {
37
38
* requests. If not provided, a new one will be created.
38
39
*/
39
40
constructor ( url : string , session ?: AxiosInstance ) {
40
- this . _baseUrl = url ;
41
- this . _session = session || axios . create ( { baseURL : this . _baseUrl } ) ;
41
+ this . #baseUrl = url ;
42
+ this . #session = session || axios . create ( { baseURL : this . #baseUrl } ) ;
42
43
}
43
44
44
45
/**
45
46
* Fetches and parses the manifest from a given API path.
46
47
* @param {string } apiPath - The API path to fetch the manifest from (e.g., "/api/tool/mytool").
47
48
* @returns {Promise<Manifest> } A promise that resolves to the parsed manifest.
48
49
* @throws {Error } If there's an error fetching data or if the manifest structure is invalid.
49
- * @private
50
50
*/
51
- private async _fetchAndParseManifest ( apiPath : string ) : Promise < Manifest > {
52
- const url = `${ this . _baseUrl } ${ apiPath } ` ;
51
+ async #fetchAndParseManifest ( apiPath : string ) : Promise < Manifest > {
52
+ const url = `${ this . #baseUrl } ${ apiPath } ` ;
53
53
try {
54
- const response : AxiosResponse = await this . _session . get ( url ) ;
54
+ const response : AxiosResponse = await this . #session . get ( url ) ;
55
55
const responseData = response . data ;
56
56
57
57
try {
@@ -85,21 +85,38 @@ class ToolboxClient {
85
85
* Creates a ToolboxTool instance from its schema.
86
86
* @param {string } toolName - The name of the tool.
87
87
* @param {ToolSchemaFromManifest } toolSchema - The schema definition of the tool from the manifest.
88
+ * @param {BoundParams } [boundParams] - A map of all candidate parameters to bind.
88
89
* @returns {ReturnType<typeof ToolboxTool> } A ToolboxTool function.
89
- * @private
90
90
*/
91
- private _createToolInstance (
91
+ #createToolInstance (
92
92
toolName : string ,
93
- toolSchema : ToolSchemaFromManifest
94
- ) : ReturnType < typeof ToolboxTool > {
93
+ toolSchema : ToolSchemaFromManifest ,
94
+ boundParams : BoundParams = { }
95
+ ) : {
96
+ tool : ReturnType < typeof ToolboxTool > ;
97
+ usedBoundKeys : Set < string > ;
98
+ } {
99
+ const toolParamNames = new Set ( toolSchema . parameters . map ( p => p . name ) ) ;
100
+ const applicableBoundParams : Record < string , BoundValue > = { } ;
101
+ const usedBoundKeys = new Set < string > ( ) ;
102
+
103
+ for ( const key in boundParams ) {
104
+ if ( toolParamNames . has ( key ) ) {
105
+ applicableBoundParams [ key ] = boundParams [ key ] ;
106
+ usedBoundKeys . add ( key ) ;
107
+ }
108
+ }
109
+
95
110
const paramZodSchema = createZodSchemaFromParams ( toolSchema . parameters ) ;
96
- return ToolboxTool (
97
- this . _session ,
98
- this . _baseUrl ,
111
+ const tool = ToolboxTool (
112
+ this . #session ,
113
+ this . #baseUrl ,
99
114
toolName ,
100
115
toolSchema . description ,
101
- paramZodSchema
116
+ paramZodSchema ,
117
+ boundParams
102
118
) ;
119
+ return { tool, usedBoundKeys} ;
103
120
}
104
121
105
122
/**
@@ -108,22 +125,42 @@ class ToolboxClient {
108
125
* returns a callable (`ToolboxTool`) that can be used to invoke the
109
126
* tool remotely.
110
127
*
128
+ * @param {BoundParams } [boundParams] - Optional parameters to pre-bind to the tool.
111
129
* @param {string } name - The unique name or identifier of the tool to load.
112
130
* @returns {Promise<ReturnType<typeof ToolboxTool>> } A promise that resolves
113
131
* to a ToolboxTool function, ready for execution.
114
132
* @throws {Error } If the tool is not found in the manifest, the manifest structure is invalid,
115
133
* or if there's an error fetching data from the API.
116
134
*/
117
- async loadTool ( name : string ) : Promise < ReturnType < typeof ToolboxTool > > {
135
+ async loadTool (
136
+ name : string ,
137
+ boundParams : BoundParams = { }
138
+ ) : Promise < ReturnType < typeof ToolboxTool > > {
118
139
const apiPath = `/api/tool/${ name } ` ;
119
- const manifest = await this . _fetchAndParseManifest ( apiPath ) ;
140
+ const manifest = await this . #fetchAndParseManifest ( apiPath ) ;
120
141
121
142
if (
122
143
manifest . tools && // Zod ensures manifest.tools exists if schema requires it
123
144
Object . prototype . hasOwnProperty . call ( manifest . tools , name )
124
145
) {
125
146
const specificToolSchema = manifest . tools [ name ] ;
126
- return this . _createToolInstance ( name , specificToolSchema ) ;
147
+ const { tool, usedBoundKeys} = this . #createToolInstance(
148
+ name ,
149
+ specificToolSchema ,
150
+ boundParams
151
+ ) ;
152
+
153
+ const providedBoundKeys = Object . keys ( boundParams ) ;
154
+ const unusedBound = providedBoundKeys . filter (
155
+ key => ! usedBoundKeys . has ( key )
156
+ ) ;
157
+
158
+ if ( unusedBound . length > 0 ) {
159
+ throw new Error (
160
+ `Validation failed for tool '${ name } ': unused bound parameters: ${ unusedBound . join ( ', ' ) } .`
161
+ ) ;
162
+ }
163
+ return tool ;
127
164
} else {
128
165
throw new Error ( `Tool "${ name } " not found in manifest from ${ apiPath } .` ) ;
129
166
}
@@ -133,21 +170,43 @@ class ToolboxClient {
133
170
* Asynchronously fetches a toolset and loads all tools defined within it.
134
171
*
135
172
* @param {string | null } [name] - Name of the toolset to load. If null or undefined, loads the default toolset.
173
+ * @param {BoundParams } [boundParams] - Optional parameters to pre-bind to the tools in the toolset.
136
174
* @returns {Promise<Array<ReturnType<typeof ToolboxTool>>> } A promise that resolves
137
175
* to a list of ToolboxTool functions, ready for execution.
138
176
* @throws {Error } If the manifest structure is invalid or if there's an error fetching data from the API.
139
177
*/
140
178
async loadToolset (
141
- name ?: string
179
+ name ?: string ,
180
+ boundParams : BoundParams = { }
142
181
) : Promise < Array < ReturnType < typeof ToolboxTool > > > {
143
182
const toolsetName = name || '' ;
144
183
const apiPath = `/api/toolset/${ toolsetName } ` ;
145
- const manifest = await this . _fetchAndParseManifest ( apiPath ) ;
184
+
185
+ const manifest = await this . #fetchAndParseManifest( apiPath ) ;
146
186
const tools : Array < ReturnType < typeof ToolboxTool > > = [ ] ;
147
187
188
+ const providedBoundKeys = new Set ( Object . keys ( boundParams ) ) ;
189
+ const overallUsedBoundParams : Set < string > = new Set ( ) ;
190
+
148
191
for ( const [ toolName , toolSchema ] of Object . entries ( manifest . tools ) ) {
149
- const toolInstance = this . _createToolInstance ( toolName , toolSchema ) ;
150
- tools . push ( toolInstance ) ;
192
+ const { tool, usedBoundKeys} = this . #createToolInstance(
193
+ toolName ,
194
+ toolSchema ,
195
+ boundParams
196
+ ) ;
197
+ tools . push ( tool ) ;
198
+ usedBoundKeys . forEach ( ( key : string ) => overallUsedBoundParams . add ( key ) ) ;
199
+ }
200
+
201
+ const unusedBound = [ ...providedBoundKeys ] . filter (
202
+ k => ! overallUsedBoundParams . has ( k )
203
+ ) ;
204
+ if ( unusedBound . length > 0 ) {
205
+ throw new Error (
206
+ `Validation failed for toolset '${
207
+ name || 'default'
208
+ } ': unused bound parameters could not be applied to any tool: ${ unusedBound . join ( ', ' ) } .`
209
+ ) ;
151
210
}
152
211
return tools ;
153
212
}
0 commit comments