blob: 2eccd459def7367b0882ab2c1d0a44663ae73205 [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
giofe6e7142025-06-18 08:51:23 +0000137func NewDeployProjectTool(apiBaseAddress string, projectId string) *llm.Tool {
138 tool := &DeployProjectTool{
139 apiBaseAddress: apiBaseAddress,
140 projectId: projectId,
141 }
142
143 return &llm.Tool{
144 Name: "dodo_deploy_project",
145 Description: "A tool for deploying the dodo-app configuration",
Giorgi Lekveishvilif9d7fc52025-07-07 07:09:42 +0400146 InputSchema: llm.EmptySchema(),
giofe6e7142025-06-18 08:51:23 +0000147 Run: tool.Run,
148 EndsTurn: true,
149 }
150}
151
giofe6e7142025-06-18 08:51:23 +0000152type deployProjectReq struct {
Giorgi Lekveishvilif9d7fc52025-07-07 07:09:42 +0400153 Type string `json:"type"`
giofe6e7142025-06-18 08:51:23 +0000154}
155
156func (d *DeployProjectTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
Giorgi Lekveishvilif9d7fc52025-07-07 07:09:42 +0400157 req := deployProjectReq{
158 Type: "draft",
giofe6e7142025-06-18 08:51:23 +0000159 }
giofe6e7142025-06-18 08:51:23 +0000160 jsonReq, err := json.Marshal(req)
161 if err != nil {
162 return nil, err
163 }
164 resp, err := http.Post(fmt.Sprintf("%s/api/project/%s/deploy", d.apiBaseAddress, d.projectId), "application/json", bytes.NewBuffer(jsonReq))
165 if err != nil {
166 return nil, err
167 }
168 defer resp.Body.Close()
169 body, err := io.ReadAll(resp.Body)
170 if err != nil {
171 return nil, err
172 }
173 if resp.StatusCode != http.StatusOK {
174 return nil, fmt.Errorf("failed to deploy project: %s", string(body))
175 }
176 return llm.TextContent(string("Project deployed successfully")), nil
177}
178
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400179// Save
180
181type SaveProjectTool struct {
182 apiBaseAddress string
183 projectId string
184}
185
186const (
187 saveProjectSchemaInputSchema = `
188{
189 "type": "object",
190 "properties": {
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400191 }
192}
193`
194)
195
196func NewSaveProjectTool(apiBaseAddress string, projectId string) *llm.Tool {
197 tool := &SaveProjectTool{
198 apiBaseAddress: apiBaseAddress,
199 projectId: projectId,
200 }
201
202 return &llm.Tool{
203 Name: "dodo_save_config",
204 Description: "A tool for saving the dodo-app configuration",
205 InputSchema: llm.MustSchema(saveProjectSchemaInputSchema),
206 Run: tool.Run,
207 EndsTurn: true,
208 }
209}
210
211type SaveProjectInput struct {
212 Config string `json:"config"`
213}
214
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400215type saveProjectReq struct {
216 Type string `json:"type"`
217 Config map[string]any `json:"config"`
218}
219
220func (d *SaveProjectTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
221 var input SaveProjectInput
222 if err := json.Unmarshal(m, &input); err != nil {
223 return nil, err
224 }
225 req := saveProjectReq{
226 Type: "config",
227 }
228 if err := json.Unmarshal([]byte(input.Config), &req.Config); err != nil {
229 return nil, err
230 }
231 jsonReq, err := json.Marshal(req)
232 if err != nil {
233 return nil, err
234 }
235 resp, err := http.Post(fmt.Sprintf("%s/api/project/%s/saved", d.apiBaseAddress, d.projectId), "application/json", bytes.NewBuffer(jsonReq))
236 if err != nil {
237 return nil, err
238 }
239 defer resp.Body.Close()
240 body, err := io.ReadAll(resp.Body)
241 if err != nil {
242 return nil, err
243 }
244 if resp.StatusCode != http.StatusOK {
245 return nil, fmt.Errorf("failed to save project: %s", string(body))
246 }
247 return llm.TextContent(string("Project saved successfully")), nil
248}
249
giofe6e7142025-06-18 08:51:23 +0000250func NewDodoTools(apiBaseAddress string, projectId string) []*llm.Tool {
251 return []*llm.Tool{
252 NewGetProjectConfigTool(apiBaseAddress, projectId),
253 NewValidateConfigTool(apiBaseAddress),
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400254 NewSaveProjectTool(apiBaseAddress, projectId),
Giorgi Lekveishvilif9d7fc52025-07-07 07:09:42 +0400255 NewDeployProjectTool(apiBaseAddress, projectId),
giofe6e7142025-06-18 08:51:23 +0000256 }
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400257}