@@ -2,7 +2,9 @@ package toolsdk
2
2
3
3
import (
4
4
"archive/tar"
5
+ "bytes"
5
6
"context"
7
+ "encoding/json"
6
8
"io"
7
9
8
10
"github.com/google/uuid"
@@ -20,28 +22,49 @@ type Deps struct {
20
22
AppStatusSlug string
21
23
}
22
24
23
- // HandlerFunc is a function that handles a tool call.
25
+ // HandlerFunc is a typed function that handles a tool call.
24
26
type HandlerFunc [Arg , Ret any ] func (context.Context , Deps , Arg ) (Ret , error )
25
27
28
+ // Tool consists of an aisdk.Tool and a corresponding typed handler function.
26
29
type Tool [Arg , Ret any ] struct {
27
30
aisdk.Tool
28
31
Handler HandlerFunc [Arg , Ret ]
29
32
}
30
33
31
- // Generic returns a type-erased version of the Tool.
32
- func (t Tool [Arg , Ret ]) Generic () Tool [any , any ] {
33
- return Tool [any , any ]{
34
+ // Generic returns a type-erased version of a TypedTool where the arguments and
35
+ // return values are converted to/from json.RawMessage.
36
+ // This allows the tool to be referenced without knowing the concrete arguments
37
+ // or return values. The original TypedHandlerFunc is wrapped to handle type
38
+ // conversion.
39
+ func (t Tool [Arg , Ret ]) Generic () GenericTool {
40
+ return GenericTool {
34
41
Tool : t .Tool ,
35
- Handler : func (ctx context.Context , tb Deps , args any ) (any , error ) {
36
- typedArg , ok := args .( Arg )
37
- if ! ok {
38
- return nil , xerrors .Errorf ("developer error: invalid argument type for tool %s " , t . Tool . Name )
42
+ Handler : wrap ( func (ctx context.Context , tb Deps , args json. RawMessage ) (json. RawMessage , error ) {
43
+ var typedArgs Arg
44
+ if err := json . Unmarshal ( args , & typedArgs ); err != nil {
45
+ return nil , xerrors .Errorf ("failed to unmarshal args: %w " , err )
39
46
}
40
- return t .Handler (ctx , tb , typedArg )
41
- },
47
+ ret , err := t .Handler (ctx , tb , typedArgs )
48
+ var buf bytes.Buffer
49
+ if err := json .NewEncoder (& buf ).Encode (ret ); err != nil {
50
+ return json.RawMessage {}, err
51
+ }
52
+ return buf .Bytes (), err
53
+ }, WithCleanContext , WithRecover ),
42
54
}
43
55
}
44
56
57
+ // GenericTool is a type-erased wrapper for GenericTool.
58
+ // This allows referencing the tool without knowing the concrete argument or
59
+ // return type. The Handler function allows calling the tool with known types.
60
+ type GenericTool struct {
61
+ aisdk.Tool
62
+ Handler GenericHandlerFunc
63
+ }
64
+
65
+ // GenericHandlerFunc is a function that handles a tool call.
66
+ type GenericHandlerFunc func (context.Context , Deps , json.RawMessage ) (json.RawMessage , error )
67
+
45
68
type NoArgs struct {}
46
69
47
70
type ReportTaskArgs struct {
@@ -114,8 +137,8 @@ type UploadTarFileArgs struct {
114
137
}
115
138
116
139
// WithRecover wraps a HandlerFunc to recover from panics and return an error.
117
- func WithRecover [ Arg , Ret any ] (h HandlerFunc [ Arg , Ret ]) HandlerFunc [ Arg , Ret ] {
118
- return func (ctx context.Context , tb Deps , args Arg ) (ret Ret , err error ) {
140
+ func WithRecover (h GenericHandlerFunc ) GenericHandlerFunc {
141
+ return func (ctx context.Context , tb Deps , args json. RawMessage ) (ret json. RawMessage , err error ) {
119
142
defer func () {
120
143
if r := recover (); r != nil {
121
144
err = xerrors .Errorf ("tool handler panic: %v" , r )
@@ -129,8 +152,8 @@ func WithRecover[Arg, Ret any](h HandlerFunc[Arg, Ret]) HandlerFunc[Arg, Ret] {
129
152
// This ensures that no data is passed using context.Value.
130
153
// If a deadline is set on the parent context, it will be passed to the child
131
154
// context.
132
- func WithCleanContext [ Arg , Ret any ] (h HandlerFunc [ Arg , Ret ]) HandlerFunc [ Arg , Ret ] {
133
- return func (parent context.Context , tb Deps , args Arg ) (ret Ret , err error ) {
155
+ func WithCleanContext (h GenericHandlerFunc ) GenericHandlerFunc {
156
+ return func (parent context.Context , tb Deps , args json. RawMessage ) (ret json. RawMessage , err error ) {
134
157
child , childCancel := context .WithCancel (context .Background ())
135
158
defer childCancel ()
136
159
// Ensure that cancellation propagates from the parent context to the child context.
@@ -153,19 +176,18 @@ func WithCleanContext[Arg, Ret any](h HandlerFunc[Arg, Ret]) HandlerFunc[Arg, Re
153
176
}
154
177
}
155
178
156
- // wrapAll wraps all provided tools with the given middleware function.
157
- func wrapAll (mw func (HandlerFunc [any , any ]) HandlerFunc [any , any ], tools ... Tool [any , any ]) []Tool [any , any ] {
158
- for i , t := range tools {
159
- t .Handler = mw (t .Handler )
160
- tools [i ] = t
179
+ // wrap wraps the provided GenericHandlerFunc with the provided middleware functions.
180
+ func wrap (hf GenericHandlerFunc , mw ... func (GenericHandlerFunc ) GenericHandlerFunc ) GenericHandlerFunc {
181
+ for _ , m := range mw {
182
+ hf = m (hf )
161
183
}
162
- return tools
184
+ return hf
163
185
}
164
186
165
187
var (
166
188
// All is a list of all tools that can be used in the Coder CLI.
167
189
// When you add a new tool, be sure to include it here!
168
- All = wrapAll ( WithCleanContext , wrapAll ( WithRecover ,
190
+ All = [] GenericTool {
169
191
CreateTemplate .Generic (),
170
192
CreateTemplateVersion .Generic (),
171
193
CreateWorkspace .Generic (),
@@ -182,9 +204,9 @@ var (
182
204
ReportTask .Generic (),
183
205
UploadTarFile .Generic (),
184
206
UpdateTemplateActiveVersion .Generic (),
185
- ) ... )
207
+ }
186
208
187
- ReportTask = Tool [ReportTaskArgs , string ]{
209
+ ReportTask = Tool [ReportTaskArgs , codersdk. Response ]{
188
210
Tool : aisdk.Tool {
189
211
Name : "coder_report_task" ,
190
212
Description : "Report progress on a user task in Coder." ,
@@ -211,22 +233,24 @@ var (
211
233
Required : []string {"summary" , "link" , "state" },
212
234
},
213
235
},
214
- Handler : func (ctx context.Context , tb Deps , args ReportTaskArgs ) (string , error ) {
236
+ Handler : func (ctx context.Context , tb Deps , args ReportTaskArgs ) (codersdk. Response , error ) {
215
237
if tb .AgentClient == nil {
216
- return "" , xerrors .New ("tool unavailable as CODER_AGENT_TOKEN or CODER_AGENT_TOKEN_FILE not set" )
238
+ return codersdk. Response {} , xerrors .New ("tool unavailable as CODER_AGENT_TOKEN or CODER_AGENT_TOKEN_FILE not set" )
217
239
}
218
240
if tb .AppStatusSlug == "" {
219
- return "" , xerrors .New ("workspace app status slug not found in toolbox" )
241
+ return codersdk. Response {} , xerrors .New ("workspace app status slug not found in toolbox" )
220
242
}
221
243
if err := tb .AgentClient .PatchAppStatus (ctx , agentsdk.PatchAppStatus {
222
244
AppSlug : tb .AppStatusSlug ,
223
245
Message : args .Summary ,
224
246
URI : args .Link ,
225
247
State : codersdk .WorkspaceAppStatusState (args .State ),
226
248
}); err != nil {
227
- return "" , err
249
+ return codersdk. Response {} , err
228
250
}
229
- return "Thanks for reporting!" , nil
251
+ return codersdk.Response {
252
+ Message : "Thanks for reporting!" ,
253
+ }, nil
230
254
},
231
255
}
232
256
@@ -934,9 +958,13 @@ The file_id provided is a reference to a tar file you have uploaded containing t
934
958
if err != nil {
935
959
return codersdk.TemplateVersion {}, xerrors .Errorf ("file_id must be a valid UUID: %w" , err )
936
960
}
937
- templateID , err := uuid .Parse (args .TemplateID )
938
- if err != nil {
939
- return codersdk.TemplateVersion {}, xerrors .Errorf ("template_id must be a valid UUID: %w" , err )
961
+ var templateID uuid.UUID
962
+ if args .TemplateID != "" {
963
+ tid , err := uuid .Parse (args .TemplateID )
964
+ if err != nil {
965
+ return codersdk.TemplateVersion {}, xerrors .Errorf ("template_id must be a valid UUID: %w" , err )
966
+ }
967
+ templateID = tid
940
968
}
941
969
templateVersion , err := tb .CoderClient .CreateTemplateVersion (ctx , me .OrganizationIDs [0 ], codersdk.CreateTemplateVersionRequest {
942
970
Message : "Created by AI" ,
@@ -1183,7 +1211,7 @@ The file_id provided is a reference to a tar file you have uploaded containing t
1183
1211
},
1184
1212
}
1185
1213
1186
- DeleteTemplate = Tool [DeleteTemplateArgs , string ]{
1214
+ DeleteTemplate = Tool [DeleteTemplateArgs , codersdk. Response ]{
1187
1215
Tool : aisdk.Tool {
1188
1216
Name : "coder_delete_template" ,
1189
1217
Description : "Delete a template. This is irreversible." ,
@@ -1195,16 +1223,18 @@ The file_id provided is a reference to a tar file you have uploaded containing t
1195
1223
},
1196
1224
},
1197
1225
},
1198
- Handler : func (ctx context.Context , tb Deps , args DeleteTemplateArgs ) (string , error ) {
1226
+ Handler : func (ctx context.Context , tb Deps , args DeleteTemplateArgs ) (codersdk. Response , error ) {
1199
1227
templateID , err := uuid .Parse (args .TemplateID )
1200
1228
if err != nil {
1201
- return "" , xerrors .Errorf ("template_id must be a valid UUID: %w" , err )
1229
+ return codersdk. Response {} , xerrors .Errorf ("template_id must be a valid UUID: %w" , err )
1202
1230
}
1203
1231
err = tb .CoderClient .DeleteTemplate (ctx , templateID )
1204
1232
if err != nil {
1205
- return "" , err
1233
+ return codersdk. Response {} , err
1206
1234
}
1207
- return "Successfully deleted template!" , nil
1235
+ return codersdk.Response {
1236
+ Message : "Template deleted successfully." ,
1237
+ }, nil
1208
1238
},
1209
1239
}
1210
1240
)
0 commit comments