1
1
package coderd
2
2
3
3
import (
4
- "bytes"
5
4
"context"
6
5
"database/sql"
7
- "encoding/json"
8
6
"errors"
9
7
"fmt"
10
- "io"
11
8
"net"
12
9
"net/http"
13
10
"net/url"
14
- "path"
15
11
"slices"
16
12
"strings"
17
13
"time"
@@ -31,6 +27,8 @@ import (
31
27
"github.com/coder/coder/v2/coderd/taskname"
32
28
"github.com/coder/coder/v2/coderd/util/slice"
33
29
"github.com/coder/coder/v2/codersdk"
30
+
31
+ aiagentapi"github.com/coder/agentapi-sdk-go"
34
32
)
35
33
36
34
// This endpoint is experimental and not guaranteed to be stable, so we're not
@@ -629,61 +627,37 @@ func (api *API) taskSend(rw http.ResponseWriter, r *http.Request) {
629
627
}
630
628
631
629
if err = api .authAndDoWithTaskSidebarAppClient (r ,taskID ,func (ctx context.Context ,client * http.Client ,appURL * url.URL )error {
632
- status ,err := agentapiDoStatusRequest ( ctx , client , appURL )
630
+ agentAPIClient ,err := aiagentapi . NewClient ( appURL . String (), aiagentapi . WithHTTPClient ( client ) )
633
631
if err != nil {
634
- return err
635
- }
636
-
637
- if status != "stable" {
638
632
return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
639
- Message :"Task app is not ready toaccept input ." ,
640
- Detail :fmt . Sprintf ( "Status: %s" , status ),
633
+ Message :"Failed tocreate agentapi client ." ,
634
+ Detail :err . Error ( ),
641
635
})
642
636
}
643
637
644
- var reqBody struct {
645
- Content string `json:"content"`
646
- Type string `json:"type"`
647
- }
648
- reqBody .Content = req .Input
649
- reqBody .Type = "user"
650
-
651
- req ,err := agentapiNewRequest (ctx ,http .MethodPost ,appURL ,"message" ,reqBody )
652
- if err != nil {
653
- return err
654
- }
655
-
656
- resp ,err := client .Do (req )
638
+ statusResp ,err := agentAPIClient .GetStatus (ctx )
657
639
if err != nil {
658
640
return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
659
- Message :"Failed toreach task app endpoint ." ,
641
+ Message :"Failed toget status from task app." ,
660
642
Detail :err .Error (),
661
643
})
662
644
}
663
- defer resp .Body .Close ()
664
-
665
- if resp .StatusCode != http .StatusOK {
666
- body ,_ := io .ReadAll (io .LimitReader (resp .Body ,128 ))
667
- return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
668
- Message :"Task app rejected the message." ,
669
- Detail :fmt .Sprintf ("Upstream status: %d; Body: %s" ,resp .StatusCode ,body ),
670
- })
671
- }
672
645
673
- // {"$schema":"http://localhost:3284/schemas/MessageResponseBody.json","ok":true}
674
- // {"$schema":"http://localhost:3284/schemas/ErrorModel.json","title":"Unprocessable Entity","status":422,"detail":"validation failed","errors":[{"location":"body.type","value":"oof"}]}
675
- var respBody map [string ]any
676
- if err := json .NewDecoder (resp .Body ).Decode (& respBody );err != nil {
646
+ if statusResp .Status != aiagentapi .StatusStable {
677
647
return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
678
- Message :"Failed to decode task app response body ." ,
679
- Detail :err . Error ( ),
648
+ Message :"Task app is not ready to accept input ." ,
649
+ Detail :fmt . Sprintf ( "Status: %s" , statusResp . Status ),
680
650
})
681
651
}
682
652
683
- if v ,ok := respBody ["ok" ].(bool );! ok || ! v {
653
+ _ ,err = agentAPIClient .PostMessage (ctx , aiagentapi.PostMessageParams {
654
+ Content :req .Input ,
655
+ Type :aiagentapi .MessageTypeUser ,
656
+ })
657
+ if err != nil {
684
658
return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
685
659
Message :"Task app rejected the message." ,
686
- Detail :fmt . Sprintf ( "Upstream response: %v" , respBody ),
660
+ Detail :err . Error ( ),
687
661
})
688
662
}
689
663
@@ -710,51 +684,29 @@ func (api *API) taskLogs(rw http.ResponseWriter, r *http.Request) {
710
684
711
685
var out codersdk.TaskLogsResponse
712
686
if err := api .authAndDoWithTaskSidebarAppClient (r ,taskID ,func (ctx context.Context ,client * http.Client ,appURL * url.URL )error {
713
- req ,err := agentapiNewRequest (ctx ,http .MethodGet ,appURL ,"messages" ,nil )
714
- if err != nil {
715
- return err
716
- }
717
-
718
- resp ,err := client .Do (req )
687
+ agentAPIClient ,err := aiagentapi .NewClient (appURL .String (),aiagentapi .WithHTTPClient (client ))
719
688
if err != nil {
720
689
return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
721
- Message :"Failed toreach task app endpoint ." ,
690
+ Message :"Failed tocreate agentapi client ." ,
722
691
Detail :err .Error (),
723
692
})
724
693
}
725
- defer resp .Body .Close ()
726
-
727
- if resp .StatusCode != http .StatusOK {
728
- body ,_ := io .ReadAll (io .LimitReader (resp .Body ,128 ))
729
- return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
730
- Message :"Task app rejected the request." ,
731
- Detail :fmt .Sprintf ("Upstream status: %d; Body: %s" ,resp .StatusCode ,body ),
732
- })
733
- }
734
694
735
- // {"$schema":"http://localhost:3284/schemas/MessagesResponseBody.json","messages":[]}
736
- var respBody struct {
737
- Messages []struct {
738
- ID int `json:"id"`
739
- Content string `json:"content"`
740
- Role string `json:"role"`
741
- Time time.Time `json:"time"`
742
- }`json:"messages"`
743
- }
744
- if err := json .NewDecoder (resp .Body ).Decode (& respBody );err != nil {
695
+ messagesResp ,err := agentAPIClient .GetMessages (ctx )
696
+ if err != nil {
745
697
return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
746
- Message :"Failed todecode task app response body ." ,
698
+ Message :"Failed toget messages from task app ." ,
747
699
Detail :err .Error (),
748
700
})
749
701
}
750
702
751
- logs := make ([]codersdk.TaskLogEntry ,0 ,len (respBody .Messages ))
752
- for _ ,m := range respBody .Messages {
703
+ logs := make ([]codersdk.TaskLogEntry ,0 ,len (messagesResp .Messages ))
704
+ for _ ,m := range messagesResp .Messages {
753
705
var typ codersdk.TaskLogType
754
- switch strings . ToLower ( m .Role ) {
755
- case "user" :
706
+ switch m .Role {
707
+ case aiagentapi . RoleUser :
756
708
typ = codersdk .TaskLogTypeInput
757
- case "agent" :
709
+ case aiagentapi . RoleAgent :
758
710
typ = codersdk .TaskLogTypeOutput
759
711
default :
760
712
return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
@@ -763,7 +715,7 @@ func (api *API) taskLogs(rw http.ResponseWriter, r *http.Request) {
763
715
})
764
716
}
765
717
logs = append (logs , codersdk.TaskLogEntry {
766
- ID :m . ID ,
718
+ ID :int ( m . Id ) ,
767
719
Content :m .Content ,
768
720
Type :typ ,
769
721
Time :m .Time ,
@@ -903,69 +855,3 @@ func (api *API) authAndDoWithTaskSidebarAppClient(
903
855
}
904
856
return do (ctx ,client ,parsedURL )
905
857
}
906
-
907
- func agentapiNewRequest (ctx context.Context ,method string ,appURL * url.URL ,appURLPath string ,body any ) (* http.Request ,error ) {
908
- u := * appURL
909
- u .Path = path .Join (appURL .Path ,appURLPath )
910
-
911
- var bodyReader io.Reader
912
- if body != nil {
913
- b ,err := json .Marshal (body )
914
- if err != nil {
915
- return nil ,httperror .NewResponseError (http .StatusBadRequest , codersdk.Response {
916
- Message :"Failed to marshal task app request body." ,
917
- Detail :err .Error (),
918
- })
919
- }
920
- bodyReader = bytes .NewReader (b )
921
- }
922
-
923
- req ,err := http .NewRequestWithContext (ctx ,method ,u .String (),bodyReader )
924
- if err != nil {
925
- return nil ,httperror .NewResponseError (http .StatusBadRequest , codersdk.Response {
926
- Message :"Failed to create task app request." ,
927
- Detail :err .Error (),
928
- })
929
- }
930
- req .Header .Set ("Content-Type" ,"application/json" )
931
- req .Header .Set ("Accept" ,"application/json" )
932
-
933
- return req ,nil
934
- }
935
-
936
- func agentapiDoStatusRequest (ctx context.Context ,client * http.Client ,appURL * url.URL ) (string ,error ) {
937
- req ,err := agentapiNewRequest (ctx ,http .MethodGet ,appURL ,"status" ,nil )
938
- if err != nil {
939
- return "" ,err
940
- }
941
-
942
- resp ,err := client .Do (req )
943
- if err != nil {
944
- return "" ,httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
945
- Message :"Failed to reach task app endpoint." ,
946
- Detail :err .Error (),
947
- })
948
- }
949
- defer resp .Body .Close ()
950
-
951
- if resp .StatusCode != http .StatusOK {
952
- return "" ,httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
953
- Message :"Task app status returned an error." ,
954
- Detail :fmt .Sprintf ("Status code: %d" ,resp .StatusCode ),
955
- })
956
- }
957
-
958
- // {"$schema":"http://localhost:3284/schemas/StatusResponseBody.json","status":"stable"}
959
- var respBody struct {
960
- Status string `json:"status"`
961
- }
962
-
963
- if err := json .NewDecoder (resp .Body ).Decode (& respBody );err != nil {
964
- return "" ,httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
965
- Message :"Failed to decode task app status response body." ,
966
- Detail :err .Error (),
967
- })
968
- }
969
-
970
- return respBody .Status ,nil
971
- }