| 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), |
| } |
| } |