blob: 41127df678c27f90c3d03b43a8c5b28be4e2fecf [file] [log] [blame]
gio30503072025-06-17 10:50:15 +00001package dodo_tools
2
3import (
4 "context"
giofe6e7142025-06-18 08:51:23 +00005 "bytes"
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
gio30503072025-06-17 10:50:15 +000019
giofe6e7142025-06-18 08:51:23 +000020func NewGetProjectConfigTool(apiBaseAddress string, projectId string) *llm.Tool {
21 tool := &GetProjectConfigTool{
22 apiBaseAddress: apiBaseAddress,
23 projectId: projectId,
24 }
gio30503072025-06-17 10:50:15 +000025 return &llm.Tool{
26 Name: "dodo_get_project_config",
27 Description: "A tool for getting current state of the infrastructure configuration of a dodo project",
giofe6e7142025-06-18 08:51:23 +000028 InputSchema: llm.EmptySchema(),
gio30503072025-06-17 10:50:15 +000029 Run: tool.Run,
30 EndsTurn: true,
31 }
32}
33
34type GetProjectConfigInput struct {
gio30503072025-06-17 10:50:15 +000035}
36
37type GetProjectConfigOutput struct {
38 Config string `json:"config"`
39}
40
41func (d *GetProjectConfigTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
giofe6e7142025-06-18 08:51:23 +000042 resp, err := http.Get(fmt.Sprintf("%s/api/project/%s/config", d.apiBaseAddress, d.projectId))
gio30503072025-06-17 10:50:15 +000043 if err != nil {
44 return nil, err
45 }
46 defer resp.Body.Close()
47 body, err := io.ReadAll(resp.Body)
48 if err != nil {
49 return nil, err
50 }
51 if resp.StatusCode != http.StatusOK {
52 return nil, fmt.Errorf("failed to get project config: %s", string(body))
53 }
gio30503072025-06-17 10:50:15 +000054 output := GetProjectConfigOutput{
55 Config: string(body),
56 }
57 jsonOutput, err := json.Marshal(output)
58 if err != nil {
59 return nil, err
60 }
61 return llm.TextContent(string(jsonOutput)), nil
62}
giofe6e7142025-06-18 08:51:23 +000063
64type ValidateConfigTool struct {
65 apiBaseAddress string
66}
67
68const (
69 validateConfigSchemaInputSchema = `
70{
71 "type": "object",
72 "properties": {
73 "config": {
74 "type": "string",
75 "description": "The dodo-app configuration to validate"
76 }
77 },
78 "required": ["config"]
79}
80`
81)
82
83func NewValidateConfigTool(apiBaseAddress string) *llm.Tool {
84 tool := &ValidateConfigTool{
85 apiBaseAddress: apiBaseAddress,
86 }
87 return &llm.Tool{
88 Name: "dodo_validate_config",
89 Description: "A tool for validating the dodo-app configuration",
90 InputSchema: llm.MustSchema(validateConfigSchemaInputSchema),
91 Run: tool.Run,
92 EndsTurn: true,
93 }
94}
95
96type ValidateConfigInput struct {
97 Config string `json:"config"`
98}
99
100type ValidateConfigOutput struct {
101 Success bool `json:"success"`
102 Errors any `json:"errors,omitempty"`
103}
104
105func (d *ValidateConfigTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
106 fmt.Printf("%s\n", string(m))
107 var input ValidateConfigInput
108 if err := json.Unmarshal(m, &input); err != nil {
109 return nil, err
110 }
111 resp, err := http.Post(fmt.Sprintf("%s/api/validate-config", d.apiBaseAddress), "application/json", bytes.NewBuffer([]byte(input.Config)))
112 if err != nil {
113 return nil, err
114 }
115 defer resp.Body.Close()
116 body, err := io.ReadAll(resp.Body)
117 if err != nil {
118 return nil, err
119 }
120 if resp.StatusCode != http.StatusOK {
121 return nil, fmt.Errorf("failed to validate config: %s", string(body))
122 }
123 var output ValidateConfigOutput
124 if err := json.Unmarshal(body, &output); err != nil {
125 return nil, err
126 }
127 jsonOutput, err := json.Marshal(output)
128 if err != nil {
129 return nil, err
130 }
131 return llm.TextContent(string(jsonOutput)), nil
132}
133
134type DeployProjectTool struct {
135 apiBaseAddress string
136 projectId string
137}
138
139const (
140 deployProjectSchemaInputSchema = `
141{
142 "type": "object",
143 "properties": {
144 "config": {
145 "type": "string",
146 "description": "Serialized dodo-app configuration to deploy"
147 }
148 }
149}
150`
151)
152
153func NewDeployProjectTool(apiBaseAddress string, projectId string) *llm.Tool {
154 tool := &DeployProjectTool{
155 apiBaseAddress: apiBaseAddress,
156 projectId: projectId,
157 }
158
159 return &llm.Tool{
160 Name: "dodo_deploy_project",
161 Description: "A tool for deploying the dodo-app configuration",
162 InputSchema: llm.MustSchema(deployProjectSchemaInputSchema),
163 Run: tool.Run,
164 EndsTurn: true,
165 }
166}
167
168type DeployProjectInput struct {
169 Config string `json:"config"`
170}
171
172type DeployProjectOutput struct {
173 Success bool `json:"success"`
174 Errors any `json:"errors,omitempty"`
175}
176
177type deployProjectReq struct {
178 Config map[string]any `json:"config"`
179}
180
181func (d *DeployProjectTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
182 var input DeployProjectInput
183 if err := json.Unmarshal(m, &input); err != nil {
184 return nil, err
185 }
186 req := deployProjectReq{}
187 if err := json.Unmarshal([]byte(input.Config), &req.Config); err != nil {
188 return nil, err
189 }
190 fmt.Printf("### %+v\n", req)
191 jsonReq, err := json.Marshal(req)
192 if err != nil {
193 return nil, err
194 }
195 resp, err := http.Post(fmt.Sprintf("%s/api/project/%s/deploy", d.apiBaseAddress, d.projectId), "application/json", bytes.NewBuffer(jsonReq))
196 if err != nil {
197 return nil, err
198 }
199 defer resp.Body.Close()
200 body, err := io.ReadAll(resp.Body)
201 if err != nil {
202 return nil, err
203 }
204 if resp.StatusCode != http.StatusOK {
205 return nil, fmt.Errorf("failed to deploy project: %s", string(body))
206 }
207 return llm.TextContent(string("Project deployed successfully")), nil
208}
209
210func NewDodoTools(apiBaseAddress string, projectId string) []*llm.Tool {
211 return []*llm.Tool{
212 NewGetProjectConfigTool(apiBaseAddress, projectId),
213 NewValidateConfigTool(apiBaseAddress),
214 NewDeployProjectTool(apiBaseAddress, projectId),
215 }
216}