blob: fdb7c8ff4e6b1f073ea02db5790231a335f4a992 [file] [log] [blame]
gio30503072025-06-17 10:50:15 +00001package dodo_tools
2
3import (
giofe6e7142025-06-18 08:51:23 +00004 "bytes"
Giorgi Lekveishvili05569832025-07-06 09:16:29 +04005 "context"
gio30503072025-06-17 10:50:15 +00006 "encoding/json"
7 "fmt"
8 "io"
9 "net/http"
10
11 "sketch.dev/llm"
12)
13
14type GetProjectConfigTool struct {
giofe6e7142025-06-18 08:51:23 +000015 apiBaseAddress string
16 projectId string
gio30503072025-06-17 10:50:15 +000017}
18
giofe6e7142025-06-18 08:51:23 +000019func NewGetProjectConfigTool(apiBaseAddress string, projectId string) *llm.Tool {
20 tool := &GetProjectConfigTool{
21 apiBaseAddress: apiBaseAddress,
22 projectId: projectId,
23 }
gio30503072025-06-17 10:50:15 +000024 return &llm.Tool{
25 Name: "dodo_get_project_config",
26 Description: "A tool for getting current state of the infrastructure configuration of a dodo project",
giofe6e7142025-06-18 08:51:23 +000027 InputSchema: llm.EmptySchema(),
gio30503072025-06-17 10:50:15 +000028 Run: tool.Run,
29 EndsTurn: true,
30 }
31}
32
33type GetProjectConfigInput struct {
gio30503072025-06-17 10:50:15 +000034}
35
36type GetProjectConfigOutput struct {
37 Config string `json:"config"`
38}
39
40func (d *GetProjectConfigTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
giofe6e7142025-06-18 08:51:23 +000041 resp, err := http.Get(fmt.Sprintf("%s/api/project/%s/config", d.apiBaseAddress, d.projectId))
gio30503072025-06-17 10:50:15 +000042 if err != nil {
43 return nil, err
44 }
45 defer resp.Body.Close()
46 body, err := io.ReadAll(resp.Body)
47 if err != nil {
48 return nil, err
49 }
50 if resp.StatusCode != http.StatusOK {
51 return nil, fmt.Errorf("failed to get project config: %s", string(body))
52 }
gio30503072025-06-17 10:50:15 +000053 output := GetProjectConfigOutput{
54 Config: string(body),
55 }
56 jsonOutput, err := json.Marshal(output)
57 if err != nil {
58 return nil, err
59 }
60 return llm.TextContent(string(jsonOutput)), nil
61}
giofe6e7142025-06-18 08:51:23 +000062
63type ValidateConfigTool struct {
64 apiBaseAddress string
65}
66
67const (
68 validateConfigSchemaInputSchema = `
69{
70 "type": "object",
71 "properties": {
72 "config": {
73 "type": "string",
74 "description": "The dodo-app configuration to validate"
75 }
76 },
77 "required": ["config"]
78}
79`
80)
81
82func NewValidateConfigTool(apiBaseAddress string) *llm.Tool {
83 tool := &ValidateConfigTool{
84 apiBaseAddress: apiBaseAddress,
85 }
86 return &llm.Tool{
87 Name: "dodo_validate_config",
88 Description: "A tool for validating the dodo-app configuration",
89 InputSchema: llm.MustSchema(validateConfigSchemaInputSchema),
90 Run: tool.Run,
91 EndsTurn: true,
92 }
93}
94
95type ValidateConfigInput struct {
96 Config string `json:"config"`
97}
98
99type ValidateConfigOutput struct {
100 Success bool `json:"success"`
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400101 Errors any `json:"errors,omitempty"`
giofe6e7142025-06-18 08:51:23 +0000102}
103
104func (d *ValidateConfigTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
giofe6e7142025-06-18 08:51:23 +0000105 var input ValidateConfigInput
106 if err := json.Unmarshal(m, &input); err != nil {
107 return nil, err
108 }
109 resp, err := http.Post(fmt.Sprintf("%s/api/validate-config", d.apiBaseAddress), "application/json", bytes.NewBuffer([]byte(input.Config)))
110 if err != nil {
111 return nil, err
112 }
113 defer resp.Body.Close()
114 body, err := io.ReadAll(resp.Body)
115 if err != nil {
116 return nil, err
117 }
118 if resp.StatusCode != http.StatusOK {
119 return nil, fmt.Errorf("failed to validate config: %s", string(body))
120 }
121 var output ValidateConfigOutput
122 if err := json.Unmarshal(body, &output); err != nil {
123 return nil, err
124 }
125 jsonOutput, err := json.Marshal(output)
126 if err != nil {
127 return nil, err
128 }
129 return llm.TextContent(string(jsonOutput)), nil
130}
131
132type DeployProjectTool struct {
133 apiBaseAddress string
134 projectId string
135}
136
137const (
138 deployProjectSchemaInputSchema = `
139{
140 "type": "object",
141 "properties": {
142 "config": {
143 "type": "string",
144 "description": "Serialized dodo-app configuration to deploy"
145 }
146 }
147}
148`
149)
150
151func NewDeployProjectTool(apiBaseAddress string, projectId string) *llm.Tool {
152 tool := &DeployProjectTool{
153 apiBaseAddress: apiBaseAddress,
154 projectId: projectId,
155 }
156
157 return &llm.Tool{
158 Name: "dodo_deploy_project",
159 Description: "A tool for deploying the dodo-app configuration",
160 InputSchema: llm.MustSchema(deployProjectSchemaInputSchema),
161 Run: tool.Run,
162 EndsTurn: true,
163 }
164}
165
166type DeployProjectInput struct {
167 Config string `json:"config"`
168}
169
170type DeployProjectOutput struct {
171 Success bool `json:"success"`
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400172 Errors any `json:"errors,omitempty"`
giofe6e7142025-06-18 08:51:23 +0000173}
174
175type deployProjectReq struct {
176 Config map[string]any `json:"config"`
177}
178
179func (d *DeployProjectTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
180 var input DeployProjectInput
181 if err := json.Unmarshal(m, &input); err != nil {
182 return nil, err
183 }
184 req := deployProjectReq{}
185 if err := json.Unmarshal([]byte(input.Config), &req.Config); err != nil {
186 return nil, err
187 }
giofe6e7142025-06-18 08:51:23 +0000188 jsonReq, err := json.Marshal(req)
189 if err != nil {
190 return nil, err
191 }
192 resp, err := http.Post(fmt.Sprintf("%s/api/project/%s/deploy", d.apiBaseAddress, d.projectId), "application/json", bytes.NewBuffer(jsonReq))
193 if err != nil {
194 return nil, err
195 }
196 defer resp.Body.Close()
197 body, err := io.ReadAll(resp.Body)
198 if err != nil {
199 return nil, err
200 }
201 if resp.StatusCode != http.StatusOK {
202 return nil, fmt.Errorf("failed to deploy project: %s", string(body))
203 }
204 return llm.TextContent(string("Project deployed successfully")), nil
205}
206
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400207// Save
208
209type SaveProjectTool struct {
210 apiBaseAddress string
211 projectId string
212}
213
214const (
215 saveProjectSchemaInputSchema = `
216{
217 "type": "object",
218 "properties": {
219 "config": {
220 "type": "string",
221 "description": "Serialized dodo-app configuration to save"
222 }
223 }
224}
225`
226)
227
228func NewSaveProjectTool(apiBaseAddress string, projectId string) *llm.Tool {
229 tool := &SaveProjectTool{
230 apiBaseAddress: apiBaseAddress,
231 projectId: projectId,
232 }
233
234 return &llm.Tool{
235 Name: "dodo_save_config",
236 Description: "A tool for saving the dodo-app configuration",
237 InputSchema: llm.MustSchema(saveProjectSchemaInputSchema),
238 Run: tool.Run,
239 EndsTurn: true,
240 }
241}
242
243type SaveProjectInput struct {
244 Config string `json:"config"`
245}
246
247type SaveProjectOutput struct {
248 Success bool `json:"success"`
249 Errors any `json:"errors,omitempty"`
250}
251
252type saveProjectReq struct {
253 Type string `json:"type"`
254 Config map[string]any `json:"config"`
255}
256
257func (d *SaveProjectTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
258 var input SaveProjectInput
259 if err := json.Unmarshal(m, &input); err != nil {
260 return nil, err
261 }
262 req := saveProjectReq{
263 Type: "config",
264 }
265 if err := json.Unmarshal([]byte(input.Config), &req.Config); err != nil {
266 return nil, err
267 }
268 jsonReq, err := json.Marshal(req)
269 if err != nil {
270 return nil, err
271 }
272 resp, err := http.Post(fmt.Sprintf("%s/api/project/%s/saved", d.apiBaseAddress, d.projectId), "application/json", bytes.NewBuffer(jsonReq))
273 if err != nil {
274 return nil, err
275 }
276 defer resp.Body.Close()
277 body, err := io.ReadAll(resp.Body)
278 if err != nil {
279 return nil, err
280 }
281 if resp.StatusCode != http.StatusOK {
282 return nil, fmt.Errorf("failed to save project: %s", string(body))
283 }
284 return llm.TextContent(string("Project saved successfully")), nil
285}
286
giofe6e7142025-06-18 08:51:23 +0000287func NewDodoTools(apiBaseAddress string, projectId string) []*llm.Tool {
288 return []*llm.Tool{
289 NewGetProjectConfigTool(apiBaseAddress, projectId),
290 NewValidateConfigTool(apiBaseAddress),
291 NewDeployProjectTool(apiBaseAddress, projectId),
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400292 NewSaveProjectTool(apiBaseAddress, projectId),
giofe6e7142025-06-18 08:51:23 +0000293 }
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400294}