@@ -17,6 +17,10 @@ import axios from 'axios';
17
17
import { type AxiosInstance , type AxiosResponse } from 'axios' ;
18
18
import { ZodManifestSchema , createZodSchemaFromParams } from './protocol' ;
19
19
import { logApiError } from './errorUtils' ;
20
+ import { ZodError } from 'zod' ;
21
+
22
+ type Manifest = import ( 'zod' ) . infer < typeof ZodManifestSchema > ;
23
+ type ToolSchemaFromManifest = Manifest [ 'tools' ] [ string ] ;
20
24
21
25
/**
22
26
* An asynchronous client for interacting with a Toolbox service.
@@ -34,62 +38,119 @@ class ToolboxClient {
34
38
*/
35
39
constructor ( url : string , session ?: AxiosInstance ) {
36
40
this . _baseUrl = url ;
37
- this . _session = session || axios . create ( { baseURL : url } ) ;
41
+ this . _session = session || axios . create ( { baseURL : this . _baseUrl } ) ;
38
42
}
39
43
40
44
/**
41
- * Asynchronously loads a tool from the server.
42
- * Retrieves the schema for the specified tool from the Toolbox server and
43
- * returns a callable (`ToolboxTool`) that can be used to invoke the
44
- * tool remotely.
45
- *
46
- * @param {string } name - The unique name or identifier of the tool to load.
47
- * @returns {Promise<ReturnType<typeof ToolboxTool>> } A promise that resolves
48
- * to a ToolboxTool function, ready for execution.
49
- * @throws {Error } If the tool is not found in the manifest, the manifest structure is invalid,
50
- * or if there's an error fetching data from the API.
45
+ * Fetches and parses the manifest from a given API path.
46
+ * @param {string } apiPath - The API path to fetch the manifest from (e.g., "/api/tool/mytool").
47
+ * @returns {Promise<Manifest> } A promise that resolves to the parsed manifest.
48
+ * @throws {Error } If there's an error fetching data or if the manifest structure is invalid.
49
+ * @private
51
50
*/
52
- async loadTool ( name : string ) : Promise < ReturnType < typeof ToolboxTool > > {
53
- const url = `${ this . _baseUrl } /api/tool/ ${ name } ` ;
51
+ private async _fetchAndParseManifest ( apiPath : string ) : Promise < Manifest > {
52
+ const url = `${ this . _baseUrl } ${ apiPath } ` ;
54
53
try {
55
54
const response : AxiosResponse = await this . _session . get ( url ) ;
56
55
const responseData = response . data ;
57
56
58
57
try {
59
58
const manifest = ZodManifestSchema . parse ( responseData ) ;
60
- if (
61
- manifest . tools &&
62
- Object . prototype . hasOwnProperty . call ( manifest . tools , name )
63
- ) {
64
- const specificToolSchema = manifest . tools [ name ] ;
65
- const paramZodSchema = createZodSchemaFromParams (
66
- specificToolSchema . parameters
67
- ) ;
68
- return ToolboxTool (
69
- this . _session ,
70
- this . _baseUrl ,
71
- name ,
72
- specificToolSchema . description ,
73
- paramZodSchema
74
- ) ;
75
- } else {
76
- throw new Error ( `Tool "${ name } " not found in manifest.` ) ;
77
- }
59
+ return manifest ;
78
60
} catch ( validationError ) {
79
- if ( validationError instanceof Error ) {
80
- throw new Error (
81
- `Invalid manifest structure received: ${ validationError . message } `
82
- ) ;
61
+ let detailedMessage = `Invalid manifest structure received from ${ url } : ` ;
62
+ if ( validationError instanceof ZodError ) {
63
+ const issueDetails = validationError . issues ;
64
+ detailedMessage += JSON . stringify ( issueDetails , null , 2 ) ;
65
+ } else if ( validationError instanceof Error ) {
66
+ detailedMessage += validationError . message ;
67
+ } else {
68
+ detailedMessage += 'Unknown validation error.' ;
83
69
}
84
- throw new Error (
85
- 'Invalid manifest structure received: Unknown validation error.'
86
- ) ;
70
+ throw new Error ( detailedMessage ) ;
87
71
}
88
72
} catch ( error ) {
73
+ if (
74
+ error instanceof Error &&
75
+ error . message . startsWith ( 'Invalid manifest structure received from' )
76
+ ) {
77
+ throw error ;
78
+ }
89
79
logApiError ( `Error fetching data from ${ url } :` , error ) ;
90
80
throw error ;
91
81
}
92
82
}
83
+
84
+ /**
85
+ * Creates a ToolboxTool instance from its schema.
86
+ * @param {string } toolName - The name of the tool.
87
+ * @param {ToolSchemaFromManifest } toolSchema - The schema definition of the tool from the manifest.
88
+ * @returns {ReturnType<typeof ToolboxTool> } A ToolboxTool function.
89
+ * @private
90
+ */
91
+ private _createToolInstance (
92
+ toolName : string ,
93
+ toolSchema : ToolSchemaFromManifest
94
+ ) : ReturnType < typeof ToolboxTool > {
95
+ const paramZodSchema = createZodSchemaFromParams ( toolSchema . parameters ) ;
96
+ return ToolboxTool (
97
+ this . _session ,
98
+ this . _baseUrl ,
99
+ toolName ,
100
+ toolSchema . description ,
101
+ paramZodSchema
102
+ ) ;
103
+ }
104
+
105
+ /**
106
+ * Asynchronously loads a tool from the server.
107
+ * Retrieves the schema for the specified tool from the Toolbox server and
108
+ * returns a callable (`ToolboxTool`) that can be used to invoke the
109
+ * tool remotely.
110
+ *
111
+ * @param {string } name - The unique name or identifier of the tool to load.
112
+ * @returns {Promise<ReturnType<typeof ToolboxTool>> } A promise that resolves
113
+ * to a ToolboxTool function, ready for execution.
114
+ * @throws {Error } If the tool is not found in the manifest, the manifest structure is invalid,
115
+ * or if there's an error fetching data from the API.
116
+ */
117
+ async loadTool ( name : string ) : Promise < ReturnType < typeof ToolboxTool > > {
118
+ const apiPath = `/api/tool/${ name } ` ;
119
+ const manifest = await this . _fetchAndParseManifest ( apiPath ) ;
120
+
121
+ if (
122
+ manifest . tools && // Zod ensures manifest.tools exists if schema requires it
123
+ Object . prototype . hasOwnProperty . call ( manifest . tools , name )
124
+ ) {
125
+ const specificToolSchema = manifest . tools [ name ] ;
126
+ return this . _createToolInstance ( name , specificToolSchema ) ;
127
+ } else {
128
+ throw new Error ( `Tool "${ name } " not found in manifest from ${ apiPath } .` ) ;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Asynchronously fetches a toolset and loads all tools defined within it.
134
+ *
135
+ * @param {string | null } [name] - Name of the toolset to load. If null or undefined, loads the default toolset.
136
+ * @returns {Promise<Array<ReturnType<typeof ToolboxTool>>> } A promise that resolves
137
+ * to a list of ToolboxTool functions, ready for execution.
138
+ * @throws {Error } If the manifest structure is invalid or if there's an error fetching data from the API.
139
+ */
140
+ async loadToolset (
141
+ name ?: string
142
+ ) : Promise < Array < ReturnType < typeof ToolboxTool > > > {
143
+ const toolsetName = name || '' ;
144
+ const apiPath = `/api/toolset/${ toolsetName } ` ;
145
+ const manifest = await this . _fetchAndParseManifest ( apiPath ) ;
146
+ const tools : Array < ReturnType < typeof ToolboxTool > > = [ ] ;
147
+
148
+ for ( const [ toolName , toolSchema ] of Object . entries ( manifest . tools ) ) {
149
+ const toolInstance = this . _createToolInstance ( toolName , toolSchema ) ;
150
+ tools . push ( toolInstance ) ;
151
+ }
152
+ return tools ;
153
+ }
93
154
}
94
155
95
156
export { ToolboxClient } ;
0 commit comments