| gio | 3050307 | 2025-06-17 10:50:15 +0000 | [diff] [blame] | 1 | package dodo_tools |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| gio | fe6e714 | 2025-06-18 08:51:23 +0000 | [diff] [blame^] | 5 | "bytes" |
| gio | 3050307 | 2025-06-17 10:50:15 +0000 | [diff] [blame] | 6 | "encoding/json" |
| 7 | "fmt" |
| 8 | "io" |
| 9 | "net/http" |
| 10 | |
| 11 | "sketch.dev/llm" |
| 12 | ) |
| 13 | |
| 14 | type GetProjectConfigTool struct { |
| gio | fe6e714 | 2025-06-18 08:51:23 +0000 | [diff] [blame^] | 15 | apiBaseAddress string |
| 16 | projectId string |
| gio | 3050307 | 2025-06-17 10:50:15 +0000 | [diff] [blame] | 17 | } |
| 18 | |
| gio | 3050307 | 2025-06-17 10:50:15 +0000 | [diff] [blame] | 19 | |
| gio | fe6e714 | 2025-06-18 08:51:23 +0000 | [diff] [blame^] | 20 | func NewGetProjectConfigTool(apiBaseAddress string, projectId string) *llm.Tool { |
| 21 | tool := &GetProjectConfigTool{ |
| 22 | apiBaseAddress: apiBaseAddress, |
| 23 | projectId: projectId, |
| 24 | } |
| gio | 3050307 | 2025-06-17 10:50:15 +0000 | [diff] [blame] | 25 | 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", |
| gio | fe6e714 | 2025-06-18 08:51:23 +0000 | [diff] [blame^] | 28 | InputSchema: llm.EmptySchema(), |
| gio | 3050307 | 2025-06-17 10:50:15 +0000 | [diff] [blame] | 29 | Run: tool.Run, |
| 30 | EndsTurn: true, |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | type GetProjectConfigInput struct { |
| gio | 3050307 | 2025-06-17 10:50:15 +0000 | [diff] [blame] | 35 | } |
| 36 | |
| 37 | type GetProjectConfigOutput struct { |
| 38 | Config string `json:"config"` |
| 39 | } |
| 40 | |
| 41 | func (d *GetProjectConfigTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) { |
| gio | fe6e714 | 2025-06-18 08:51:23 +0000 | [diff] [blame^] | 42 | resp, err := http.Get(fmt.Sprintf("%s/api/project/%s/config", d.apiBaseAddress, d.projectId)) |
| gio | 3050307 | 2025-06-17 10:50:15 +0000 | [diff] [blame] | 43 | 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 | } |
| gio | 3050307 | 2025-06-17 10:50:15 +0000 | [diff] [blame] | 54 | 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 | } |
| gio | fe6e714 | 2025-06-18 08:51:23 +0000 | [diff] [blame^] | 63 | |
| 64 | type ValidateConfigTool struct { |
| 65 | apiBaseAddress string |
| 66 | } |
| 67 | |
| 68 | const ( |
| 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 | |
| 83 | func 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 | |
| 96 | type ValidateConfigInput struct { |
| 97 | Config string `json:"config"` |
| 98 | } |
| 99 | |
| 100 | type ValidateConfigOutput struct { |
| 101 | Success bool `json:"success"` |
| 102 | Errors any `json:"errors,omitempty"` |
| 103 | } |
| 104 | |
| 105 | func (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 | |
| 134 | type DeployProjectTool struct { |
| 135 | apiBaseAddress string |
| 136 | projectId string |
| 137 | } |
| 138 | |
| 139 | const ( |
| 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 | |
| 153 | func 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 | |
| 168 | type DeployProjectInput struct { |
| 169 | Config string `json:"config"` |
| 170 | } |
| 171 | |
| 172 | type DeployProjectOutput struct { |
| 173 | Success bool `json:"success"` |
| 174 | Errors any `json:"errors,omitempty"` |
| 175 | } |
| 176 | |
| 177 | type deployProjectReq struct { |
| 178 | Config map[string]any `json:"config"` |
| 179 | } |
| 180 | |
| 181 | func (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 | |
| 210 | func NewDodoTools(apiBaseAddress string, projectId string) []*llm.Tool { |
| 211 | return []*llm.Tool{ |
| 212 | NewGetProjectConfigTool(apiBaseAddress, projectId), |
| 213 | NewValidateConfigTool(apiBaseAddress), |
| 214 | NewDeployProjectTool(apiBaseAddress, projectId), |
| 215 | } |
| 216 | } |