blob: 5913d926784029e951668ee13d0d93249d3fdc6f [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 Lekveishvili49b7b0a2025-07-07 13:52:35 +0400191 "config": {
192 "type": "string",
193 "description": "Serialized dodo-app configuration to save"
194 }
195 },
196 "required": ["config"]
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400197}
198`
199)
200
201func NewSaveProjectTool(apiBaseAddress string, projectId string) *llm.Tool {
202 tool := &SaveProjectTool{
203 apiBaseAddress: apiBaseAddress,
204 projectId: projectId,
205 }
206
207 return &llm.Tool{
208 Name: "dodo_save_config",
209 Description: "A tool for saving the dodo-app configuration",
210 InputSchema: llm.MustSchema(saveProjectSchemaInputSchema),
211 Run: tool.Run,
212 EndsTurn: true,
213 }
214}
215
216type SaveProjectInput struct {
217 Config string `json:"config"`
218}
219
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400220type saveProjectReq struct {
221 Type string `json:"type"`
222 Config map[string]any `json:"config"`
223}
224
225func (d *SaveProjectTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
226 var input SaveProjectInput
227 if err := json.Unmarshal(m, &input); err != nil {
228 return nil, err
229 }
230 req := saveProjectReq{
231 Type: "config",
232 }
233 if err := json.Unmarshal([]byte(input.Config), &req.Config); err != nil {
234 return nil, err
235 }
236 jsonReq, err := json.Marshal(req)
237 if err != nil {
238 return nil, err
239 }
240 resp, err := http.Post(fmt.Sprintf("%s/api/project/%s/saved", d.apiBaseAddress, d.projectId), "application/json", bytes.NewBuffer(jsonReq))
241 if err != nil {
242 return nil, err
243 }
244 defer resp.Body.Close()
245 body, err := io.ReadAll(resp.Body)
246 if err != nil {
247 return nil, err
248 }
249 if resp.StatusCode != http.StatusOK {
250 return nil, fmt.Errorf("failed to save project: %s", string(body))
251 }
252 return llm.TextContent(string("Project saved successfully")), nil
253}
254
giofe6e7142025-06-18 08:51:23 +0000255func NewDodoTools(apiBaseAddress string, projectId string) []*llm.Tool {
256 return []*llm.Tool{
257 NewGetProjectConfigTool(apiBaseAddress, projectId),
258 NewValidateConfigTool(apiBaseAddress),
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400259 NewSaveProjectTool(apiBaseAddress, projectId),
Giorgi Lekveishvilif9d7fc52025-07-07 07:09:42 +0400260 NewDeployProjectTool(apiBaseAddress, projectId),
giofe6e7142025-06-18 08:51:23 +0000261 }
Giorgi Lekveishvili05569832025-07-06 09:16:29 +0400262}