blob: eee786c61d034c3d4f72eff9d5c11e92db68ee90 [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. You might want to use dodo_get_project_env tool to resolve ingress domains.",
InputSchema: llm.EmptySchema(),
Run: tool.Run,
EndsTurn: false,
}
}
type GetProjectConfigInput struct {
}
type GetProjectConfigOutput struct {
Config string `json:"config"`
}
func (d *GetProjectConfigTool) Run(ctx context.Context, m json.RawMessage) llm.ToolOut {
resp, err := http.Get(fmt.Sprintf("%s/api/project/%s/config", d.apiBaseAddress, d.projectId))
if err != nil {
return llm.ErrorToolOut(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return llm.ErrorToolOut(err)
}
if resp.StatusCode != http.StatusOK {
return llm.ErrorfToolOut("failed to get project config: %s", string(body))
}
output := GetProjectConfigOutput{
Config: string(body),
}
jsonOutput, err := json.Marshal(output)
if err != nil {
return llm.ErrorToolOut(err)
}
return llm.ToolOut{LLMContent: llm.TextContent(string(jsonOutput))}
}
// Env
type GetProjectEnvTool struct {
apiBaseAddress string
projectId string
}
func NewGetProjectEnvTool(apiBaseAddress string, projectId string) *llm.Tool {
tool := &GetProjectEnvTool{
apiBaseAddress: apiBaseAddress,
projectId: projectId,
}
return &llm.Tool{
Name: "dodo_get_project_env",
Description: `A tool for getting GitHub PAT (Personal Access Token) and available networks of the dodo project. Use if you need to:
1. Interact with GitHub and do not have PAT cached.
2. Want to resolve network domains of dodo service ingresses.
3. Access application wide environment variables.
Returns JSON object conforming following schema which you can cache:
{
"type": "object",
"properties": {
"githubToken": {
"type": "string",
"description": "Github PAT (Personal Access Token)"
},
"networks": {
"type": "array",
"description": "List of available networks",
"items": {
"type": "object",
"required": ["name", "domain", "hasAuth"],
"properties": {
"name": {
"type": "string",
"description": "The name of the network"
},
"domain": {
"type": "string",
"description": "The domain of the network"
},
"hasAuth": {
"type": "boolean",
"description": "Represents if services published on this network can use built-in authorization"
}
}
}
},
"envVars": {
"type": "array",
"description": "List of application wide environment variables",
"items": {
"type": "object",
"required": ["name", "value"],
"properties": {
"name": {
"type": "string",
"description": "The name of the environment variable"
},
"value": {
"type": "string",
"description": "The value of the environment variable"
}
}
}
}
}
}
`,
InputSchema: llm.EmptySchema(),
Run: tool.Run,
EndsTurn: false,
}
}
type GetProjectEnvInput struct {
}
type Network struct {
Name string `json:"name"`
Domain string `json:"domain"`
HasAuth bool `json:"hasAuth"`
}
type EnvVar struct {
Name string `json:"name"`
Value string `json:"value"`
}
type GetProjectEnvOutput struct {
GithubToken string `json:"githubToken,omitempty"`
Networks []Network `json:"networks,omitempty"`
EnvVars []EnvVar `json:"envVars"`
}
func (d *GetProjectEnvTool) Run(ctx context.Context, m json.RawMessage) llm.ToolOut {
resp, err := http.Get(fmt.Sprintf("%s/api/project/%s/env", d.apiBaseAddress, d.projectId))
if err != nil {
return llm.ErrorToolOut(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return llm.ErrorToolOut(err)
}
if resp.StatusCode != http.StatusOK {
return llm.ErrorfToolOut("failed to get project env: %s", string(body))
}
var output GetProjectEnvOutput
if err := json.Unmarshal(body, &output); err != nil {
return llm.ErrorToolOut(err)
}
jsonOutput, err := json.Marshal(output)
if err != nil {
return llm.ErrorToolOut(err)
}
return llm.ToolOut{LLMContent: llm.TextContent(string(jsonOutput))}
}
// Validate
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: false,
}
}
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.ToolOut {
var input ValidateConfigInput
if err := json.Unmarshal(m, &input); err != nil {
return llm.ErrorToolOut(err)
}
resp, err := http.Post(fmt.Sprintf("%s/api/validate-config", d.apiBaseAddress), "application/json", bytes.NewBuffer([]byte(input.Config)))
if err != nil {
return llm.ErrorToolOut(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return llm.ErrorToolOut(err)
}
if resp.StatusCode != http.StatusOK {
return llm.ErrorfToolOut("failed to validate config: %s", string(body))
}
var output ValidateConfigOutput
if err := json.Unmarshal(body, &output); err != nil {
return llm.ErrorToolOut(err)
}
jsonOutput, err := json.Marshal(output)
if err != nil {
return llm.ErrorToolOut(err)
}
return llm.ToolOut{LLMContent: llm.TextContent(string(jsonOutput))}
}
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.ToolOut {
req := deployProjectReq{
Type: "draft",
}
jsonReq, err := json.Marshal(req)
if err != nil {
return llm.ErrorToolOut(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 llm.ErrorToolOut(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return llm.ErrorToolOut(err)
}
if resp.StatusCode != http.StatusOK {
return llm.ErrorfToolOut("failed to deploy project: %s", string(body))
}
return llm.ToolOut{LLMContent: llm.TextContent(string("Project deployed successfully"))}
}
// 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: false,
}
}
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.ToolOut {
var input SaveProjectInput
if err := json.Unmarshal(m, &input); err != nil {
return llm.ErrorToolOut(err)
}
req := saveProjectReq{
Type: "config",
}
if err := json.Unmarshal([]byte(input.Config), &req.Config); err != nil {
return llm.ErrorToolOut(err)
}
jsonReq, err := json.Marshal(req)
if err != nil {
return llm.ErrorToolOut(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 llm.ErrorToolOut(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return llm.ErrorToolOut(err)
}
if resp.StatusCode != http.StatusOK {
return llm.ErrorfToolOut("failed to save project: %s", string(body))
}
return llm.ToolOut{LLMContent: llm.TextContent(string("Project saved successfully"))}
}
func NewDodoTools(apiBaseAddress string, projectId string) []*llm.Tool {
return []*llm.Tool{
NewGetProjectConfigTool(apiBaseAddress, projectId),
NewGetProjectEnvTool(apiBaseAddress, projectId),
NewValidateConfigTool(apiBaseAddress),
NewSaveProjectTool(apiBaseAddress, projectId),
NewDeployProjectTool(apiBaseAddress, projectId),
}
}