blob: 5913d926784029e951668ee13d0d93249d3fdc6f [file] [log] [blame]
package dodo_tools
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"sketch.dev/llm"
)
type GetProjectConfigTool struct {
apiBaseAddress string
projectId string
}
func NewGetProjectConfigTool(apiBaseAddress string, projectId string) *llm.Tool {
tool := &GetProjectConfigTool{
apiBaseAddress: apiBaseAddress,
projectId: projectId,
}
return &llm.Tool{
Name: "dodo_get_project_config",
Description: "A tool for getting current state of the infrastructure configuration of a dodo project",
InputSchema: llm.EmptySchema(),
Run: tool.Run,
EndsTurn: true,
}
}
type GetProjectConfigInput struct {
}
type GetProjectConfigOutput struct {
Config string `json:"config"`
}
func (d *GetProjectConfigTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
resp, err := http.Get(fmt.Sprintf("%s/api/project/%s/config", d.apiBaseAddress, d.projectId))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get project config: %s", string(body))
}
output := GetProjectConfigOutput{
Config: string(body),
}
jsonOutput, err := json.Marshal(output)
if err != nil {
return nil, err
}
return llm.TextContent(string(jsonOutput)), nil
}
type ValidateConfigTool struct {
apiBaseAddress string
}
const (
validateConfigSchemaInputSchema = `
{
"type": "object",
"properties": {
"config": {
"type": "string",
"description": "The dodo-app configuration to validate"
}
},
"required": ["config"]
}
`
)
func NewValidateConfigTool(apiBaseAddress string) *llm.Tool {
tool := &ValidateConfigTool{
apiBaseAddress: apiBaseAddress,
}
return &llm.Tool{
Name: "dodo_validate_config",
Description: "A tool for validating the dodo-app configuration",
InputSchema: llm.MustSchema(validateConfigSchemaInputSchema),
Run: tool.Run,
EndsTurn: true,
}
}
type ValidateConfigInput struct {
Config string `json:"config"`
}
type ValidateConfigOutput struct {
Success bool `json:"success"`
Errors any `json:"errors,omitempty"`
}
func (d *ValidateConfigTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
var input ValidateConfigInput
if err := json.Unmarshal(m, &input); err != nil {
return nil, err
}
resp, err := http.Post(fmt.Sprintf("%s/api/validate-config", d.apiBaseAddress), "application/json", bytes.NewBuffer([]byte(input.Config)))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to validate config: %s", string(body))
}
var output ValidateConfigOutput
if err := json.Unmarshal(body, &output); err != nil {
return nil, err
}
jsonOutput, err := json.Marshal(output)
if err != nil {
return nil, err
}
return llm.TextContent(string(jsonOutput)), nil
}
type DeployProjectTool struct {
apiBaseAddress string
projectId string
}
func NewDeployProjectTool(apiBaseAddress string, projectId string) *llm.Tool {
tool := &DeployProjectTool{
apiBaseAddress: apiBaseAddress,
projectId: projectId,
}
return &llm.Tool{
Name: "dodo_deploy_project",
Description: "A tool for deploying the dodo-app configuration",
InputSchema: llm.EmptySchema(),
Run: tool.Run,
EndsTurn: true,
}
}
type deployProjectReq struct {
Type string `json:"type"`
}
func (d *DeployProjectTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
req := deployProjectReq{
Type: "draft",
}
jsonReq, err := json.Marshal(req)
if err != nil {
return nil, err
}
resp, err := http.Post(fmt.Sprintf("%s/api/project/%s/deploy", d.apiBaseAddress, d.projectId), "application/json", bytes.NewBuffer(jsonReq))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to deploy project: %s", string(body))
}
return llm.TextContent(string("Project deployed successfully")), nil
}
// Save
type SaveProjectTool struct {
apiBaseAddress string
projectId string
}
const (
saveProjectSchemaInputSchema = `
{
"type": "object",
"properties": {
"config": {
"type": "string",
"description": "Serialized dodo-app configuration to save"
}
},
"required": ["config"]
}
`
)
func NewSaveProjectTool(apiBaseAddress string, projectId string) *llm.Tool {
tool := &SaveProjectTool{
apiBaseAddress: apiBaseAddress,
projectId: projectId,
}
return &llm.Tool{
Name: "dodo_save_config",
Description: "A tool for saving the dodo-app configuration",
InputSchema: llm.MustSchema(saveProjectSchemaInputSchema),
Run: tool.Run,
EndsTurn: true,
}
}
type SaveProjectInput struct {
Config string `json:"config"`
}
type saveProjectReq struct {
Type string `json:"type"`
Config map[string]any `json:"config"`
}
func (d *SaveProjectTool) Run(ctx context.Context, m json.RawMessage) ([]llm.Content, error) {
var input SaveProjectInput
if err := json.Unmarshal(m, &input); err != nil {
return nil, err
}
req := saveProjectReq{
Type: "config",
}
if err := json.Unmarshal([]byte(input.Config), &req.Config); err != nil {
return nil, err
}
jsonReq, err := json.Marshal(req)
if err != nil {
return nil, err
}
resp, err := http.Post(fmt.Sprintf("%s/api/project/%s/saved", d.apiBaseAddress, d.projectId), "application/json", bytes.NewBuffer(jsonReq))
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to save project: %s", string(body))
}
return llm.TextContent(string("Project saved successfully")), nil
}
func NewDodoTools(apiBaseAddress string, projectId string) []*llm.Tool {
return []*llm.Tool{
NewGetProjectConfigTool(apiBaseAddress, projectId),
NewValidateConfigTool(apiBaseAddress),
NewSaveProjectTool(apiBaseAddress, projectId),
NewDeployProjectTool(apiBaseAddress, projectId),
}
}