mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
chore(coderd): update aitasks.go to leverage agentapi-sdk-go (#20159)
I only recently became aware of the existence of `agentapi-sdk-go`. Updates `aitasks.go` to make use of it.
This commit is contained in:
+37
-151
@@ -1,17 +1,13 @@
|
||||
package coderd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -31,6 +27,8 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/taskname"
|
||||
"github.com/coder/coder/v2/coderd/util/slice"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
|
||||
aiagentapi "github.com/coder/agentapi-sdk-go"
|
||||
)
|
||||
|
||||
// This endpoint is experimental and not guaranteed to be stable, so we're not
|
||||
@@ -629,64 +627,40 @@ func (api *API) taskSend(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if err = api.authAndDoWithTaskSidebarAppClient(r, taskID, func(ctx context.Context, client *http.Client, appURL *url.URL) error {
|
||||
status, err := agentapiDoStatusRequest(ctx, client, appURL)
|
||||
agentAPIClient, err := aiagentapi.NewClient(appURL.String(), aiagentapi.WithHTTPClient(client))
|
||||
if err != nil {
|
||||
return err
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Failed to create agentapi client.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
if status != "stable" {
|
||||
statusResp, err := agentAPIClient.GetStatus(ctx)
|
||||
if err != nil {
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Failed to get status from task app.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
if statusResp.Status != aiagentapi.StatusStable {
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Task app is not ready to accept input.",
|
||||
Detail: fmt.Sprintf("Status: %s", status),
|
||||
Detail: fmt.Sprintf("Status: %s", statusResp.Status),
|
||||
})
|
||||
}
|
||||
|
||||
var reqBody struct {
|
||||
Content string `json:"content"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
reqBody.Content = req.Input
|
||||
reqBody.Type = "user"
|
||||
|
||||
req, err := agentapiNewRequest(ctx, http.MethodPost, appURL, "message", reqBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
_, err = agentAPIClient.PostMessage(ctx, aiagentapi.PostMessageParams{
|
||||
Content: req.Input,
|
||||
Type: aiagentapi.MessageTypeUser,
|
||||
})
|
||||
if err != nil {
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Failed to reach task app endpoint.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 128))
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Task app rejected the message.",
|
||||
Detail: fmt.Sprintf("Upstream status: %d; Body: %s", resp.StatusCode, body),
|
||||
})
|
||||
}
|
||||
|
||||
// {"$schema":"http://localhost:3284/schemas/MessageResponseBody.json","ok":true}
|
||||
// {"$schema":"http://localhost:3284/schemas/ErrorModel.json","title":"Unprocessable Entity","status":422,"detail":"validation failed","errors":[{"location":"body.type","value":"oof"}]}
|
||||
var respBody map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Failed to decode task app response body.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
if v, ok := respBody["ok"].(bool); !ok || !v {
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Task app rejected the message.",
|
||||
Detail: fmt.Sprintf("Upstream response: %v", respBody),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
httperror.WriteResponseError(ctx, rw, err)
|
||||
@@ -710,51 +684,29 @@ func (api *API) taskLogs(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var out codersdk.TaskLogsResponse
|
||||
if err := api.authAndDoWithTaskSidebarAppClient(r, taskID, func(ctx context.Context, client *http.Client, appURL *url.URL) error {
|
||||
req, err := agentapiNewRequest(ctx, http.MethodGet, appURL, "messages", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
agentAPIClient, err := aiagentapi.NewClient(appURL.String(), aiagentapi.WithHTTPClient(client))
|
||||
if err != nil {
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Failed to reach task app endpoint.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 128))
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Task app rejected the request.",
|
||||
Detail: fmt.Sprintf("Upstream status: %d; Body: %s", resp.StatusCode, body),
|
||||
})
|
||||
}
|
||||
|
||||
// {"$schema":"http://localhost:3284/schemas/MessagesResponseBody.json","messages":[]}
|
||||
var respBody struct {
|
||||
Messages []struct {
|
||||
ID int `json:"id"`
|
||||
Content string `json:"content"`
|
||||
Role string `json:"role"`
|
||||
Time time.Time `json:"time"`
|
||||
} `json:"messages"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Failed to decode task app response body.",
|
||||
Message: "Failed to create agentapi client.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
logs := make([]codersdk.TaskLogEntry, 0, len(respBody.Messages))
|
||||
for _, m := range respBody.Messages {
|
||||
messagesResp, err := agentAPIClient.GetMessages(ctx)
|
||||
if err != nil {
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Failed to get messages from task app.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
logs := make([]codersdk.TaskLogEntry, 0, len(messagesResp.Messages))
|
||||
for _, m := range messagesResp.Messages {
|
||||
var typ codersdk.TaskLogType
|
||||
switch strings.ToLower(m.Role) {
|
||||
case "user":
|
||||
switch m.Role {
|
||||
case aiagentapi.RoleUser:
|
||||
typ = codersdk.TaskLogTypeInput
|
||||
case "agent":
|
||||
case aiagentapi.RoleAgent:
|
||||
typ = codersdk.TaskLogTypeOutput
|
||||
default:
|
||||
return httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
@@ -763,7 +715,7 @@ func (api *API) taskLogs(rw http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
logs = append(logs, codersdk.TaskLogEntry{
|
||||
ID: m.ID,
|
||||
ID: int(m.Id),
|
||||
Content: m.Content,
|
||||
Type: typ,
|
||||
Time: m.Time,
|
||||
@@ -903,69 +855,3 @@ func (api *API) authAndDoWithTaskSidebarAppClient(
|
||||
}
|
||||
return do(ctx, client, parsedURL)
|
||||
}
|
||||
|
||||
func agentapiNewRequest(ctx context.Context, method string, appURL *url.URL, appURLPath string, body any) (*http.Request, error) {
|
||||
u := *appURL
|
||||
u.Path = path.Join(appURL.Path, appURLPath)
|
||||
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, httperror.NewResponseError(http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Failed to marshal task app request body.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
bodyReader = bytes.NewReader(b)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, u.String(), bodyReader)
|
||||
if err != nil {
|
||||
return nil, httperror.NewResponseError(http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Failed to create task app request.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func agentapiDoStatusRequest(ctx context.Context, client *http.Client, appURL *url.URL) (string, error) {
|
||||
req, err := agentapiNewRequest(ctx, http.MethodGet, appURL, "status", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Failed to reach task app endpoint.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Task app status returned an error.",
|
||||
Detail: fmt.Sprintf("Status code: %d", resp.StatusCode),
|
||||
})
|
||||
}
|
||||
|
||||
// {"$schema":"http://localhost:3284/schemas/StatusResponseBody.json","status":"stable"}
|
||||
var respBody struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
|
||||
return "", httperror.NewResponseError(http.StatusBadGateway, codersdk.Response{
|
||||
Message: "Failed to decode task app status response body.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return respBody.Status, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user