Skip to content

Commit 1153ef7

Browse files
committed
better API docs
1 parent bf074a6 commit 1153ef7

5 files changed

Lines changed: 79 additions & 19 deletions

File tree

lib/httpapi/events.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88

99
mf "github.com/coder/agentapi/lib/msgfmt"
1010
st "github.com/coder/agentapi/lib/screentracker"
11+
"github.com/coder/agentapi/lib/util"
12+
"github.com/danielgtaylor/huma/v2"
1113
)
1214

1315
type EventType string
@@ -21,19 +23,28 @@ const (
2123
type AgentStatus string
2224

2325
const (
24-
AgentStatusStable AgentStatus = "stable"
2526
AgentStatusRunning AgentStatus = "running"
27+
AgentStatusStable AgentStatus = "stable"
2628
)
2729

30+
var AgentStatusValues = []AgentStatus{
31+
AgentStatusStable,
32+
AgentStatusRunning,
33+
}
34+
35+
func (a AgentStatus) Schema(r huma.Registry) *huma.Schema {
36+
return util.OpenAPISchema(r, "AgentStatus", AgentStatusValues)
37+
}
38+
2839
type MessageUpdateBody struct {
29-
Id int `json:"id"`
30-
Role st.ConversationRole `json:"role"`
31-
Message string `json:"message"`
32-
Time time.Time `json:"time"`
40+
Id int `json:"id" doc:"Unique identifier for the message. This identifier also represents the order of the message in the conversation history."`
41+
Role st.ConversationRole `json:"role" doc:"Role of the message author"`
42+
Message string `json:"message" doc:"Message content"`
43+
Time time.Time `json:"time" doc:"Timestamp of the message"`
3344
}
3445

3546
type StatusChangeBody struct {
36-
Status AgentStatus `json:"status"`
47+
Status AgentStatus `json:"status" doc:"Agent status"`
3748
}
3849

3950
type ScreenUpdateBody struct {

lib/httpapi/models.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,60 @@
11
package httpapi
22

3+
import (
4+
st "github.com/coder/agentapi/lib/screentracker"
5+
"github.com/coder/agentapi/lib/util"
6+
"github.com/danielgtaylor/huma/v2"
7+
)
8+
39
type MessageType string
410

511
const (
612
MessageTypeUser MessageType = "user"
713
MessageTypeRaw MessageType = "raw"
814
)
915

16+
var MessageTypeValues = []MessageType{
17+
MessageTypeUser,
18+
MessageTypeRaw,
19+
}
20+
21+
func (m MessageType) Schema(r huma.Registry) *huma.Schema {
22+
return util.OpenAPISchema(r, "MessageType", MessageTypeValues)
23+
}
24+
1025
// Message represents a message
1126
type Message struct {
12-
Content string `json:"content" example:"Hello world" doc:"Message content"`
13-
Role string `json:"role" example:"user" doc:"Role of the message author"`
27+
Content string `json:"content" example:"Hello world" doc:"Message content"`
28+
Role st.ConversationRole `json:"role" doc:"Role of the message author"`
1429
}
1530

1631
// StatusResponse represents the server status
1732
type StatusResponse struct {
1833
Body struct {
19-
Status string `json:"status" example:"running" doc:"Current server status"`
34+
Status AgentStatus `json:"status" doc:"Current agent status. 'running' means that the agent is processing a message, 'stable' means that the agent is idle and waiting for input."`
2035
}
2136
}
2237

2338
// MessagesResponse represents the list of messages
2439
type MessagesResponse struct {
2540
Body struct {
26-
Messages []Message `json:"messages" doc:"List of messages"`
41+
Messages []Message `json:"messages" nullable:"false" doc:"List of messages"`
2742
}
2843
}
2944

3045
type MessageRequestBody struct {
31-
Content string `json:"content" example:"Hello, server!" doc:"Message content"`
32-
Type MessageType `json:"type" enum:"user,raw" example:"user" doc:"Type of the message"`
46+
Content string `json:"content" example:"Hello, agent!" doc:"Message content"`
47+
Type MessageType `json:"type" doc:"A 'user' type message will be logged as a user message in the conversation history and submitted to the agent. AgentAPI will wait until the agent starts carrying out the task described in the message before responding. A 'raw' type message will be written directly to the agent's terminal session as keystrokes and will not be saved in the conversation history. 'raw' messages are useful for sending escape sequences to the terminal."`
3348
}
3449

3550
// MessageRequest represents a request to create a new message
3651
type MessageRequest struct {
37-
Body MessageRequestBody `json:"body"`
52+
Body MessageRequestBody `json:"body" doc:"Message content and type"`
3853
}
3954

4055
// MessageResponse represents a newly created message
4156
type MessageResponse struct {
4257
Body struct {
43-
Ok bool `json:"ok" doc:"Whether the message was sent successfully"`
58+
Ok bool `json:"ok" doc:"Indicates whether the message was sent successfully. For messages of type 'user', success means detecting that the agent began executing the task described. For messages of type 'raw', success means the keystrokes were sent to the terminal."`
4459
}
4560
}

lib/httpapi/server.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,20 +98,27 @@ func NewServer(ctx context.Context, agentType mf.AgentType, process *termexec.Pr
9898
// registerRoutes sets up all API endpoints
9999
func (s *Server) registerRoutes() {
100100
// GET /status endpoint
101-
huma.Get(s.api, "/status", s.getStatus)
101+
huma.Get(s.api, "/status", s.getStatus, func(o *huma.Operation) {
102+
o.Description = "Returns the current status of the agent."
103+
})
102104

103105
// GET /messages endpoint
104-
huma.Get(s.api, "/messages", s.getMessages)
106+
huma.Get(s.api, "/messages", s.getMessages, func(o *huma.Operation) {
107+
o.Description = "Returns a list of messages representing the conversation history with the agent."
108+
})
105109

106110
// POST /message endpoint
107-
huma.Post(s.api, "/message", s.createMessage)
111+
huma.Post(s.api, "/message", s.createMessage, func(o *huma.Operation) {
112+
o.Description = "Send a message to the agent. For messages of type 'user', the agent's status must be 'stable' for the operation to complete successfully. Otherwise, this endpoint will return an error."
113+
})
108114

109115
// GET /events endpoint
110116
sse.Register(s.api, huma.Operation{
111117
OperationID: "subscribeEvents",
112118
Method: http.MethodGet,
113119
Path: "/events",
114120
Summary: "Subscribe to events",
121+
Description: "The events are sent as Server-Sent Events (SSE). Initially, the endpoint returns a list of events needed to reconstruct the current state of the conversation and the agent's status. After that, it only returns events that have occurred since the last event was sent.",
115122
}, map[string]any{
116123
// Mapping of event type name to Go struct for that event.
117124
"message_update": MessageUpdateBody{},
@@ -138,7 +145,7 @@ func (s *Server) getStatus(ctx context.Context, input *struct{}) (*StatusRespons
138145
agentStatus := convertStatus(status)
139146

140147
resp := &StatusResponse{}
141-
resp.Body.Status = string(agentStatus)
148+
resp.Body.Status = agentStatus
142149

143150
return resp, nil
144151
}
@@ -152,7 +159,7 @@ func (s *Server) getMessages(ctx context.Context, input *struct{}) (*MessagesRes
152159
resp.Body.Messages = make([]Message, len(s.conversation.Messages()))
153160
for i, msg := range s.conversation.Messages() {
154161
resp.Body.Messages[i] = Message{
155-
Role: string(msg.Role),
162+
Role: msg.Role,
156163
Content: msg.Message,
157164
}
158165
}

lib/screentracker/conversation.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/coder/agentapi/lib/msgfmt"
1111
"github.com/coder/agentapi/lib/util"
12+
"github.com/danielgtaylor/huma/v2"
1213
"golang.org/x/xerrors"
1314
)
1415

@@ -48,6 +49,15 @@ const (
4849
ConversationRoleAgent ConversationRole = "agent"
4950
)
5051

52+
var ConversationRoleValues = []ConversationRole{
53+
ConversationRoleUser,
54+
ConversationRoleAgent,
55+
}
56+
57+
func (c ConversationRole) Schema(r huma.Registry) *huma.Schema {
58+
return util.OpenAPISchema(r, "ConversationRole", ConversationRoleValues)
59+
}
60+
5161
type ConversationMessage struct {
5262
Id int
5363
Message string

lib/util/util.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package util
22

33
import (
44
"context"
5+
"fmt"
6+
"reflect"
57
"time"
68

9+
"github.com/danielgtaylor/huma/v2"
710
"golang.org/x/xerrors"
811
)
912

@@ -60,3 +63,17 @@ func WaitFor(ctx context.Context, timeout WaitTimeout, condition func() (bool, e
6063
}
6164
}
6265
}
66+
67+
// based on https://github.com/danielgtaylor/huma/issues/621#issuecomment-2456588788
68+
func OpenAPISchema[T ~string](r huma.Registry, enumName string, values []T) *huma.Schema {
69+
if r.Map()[enumName] == nil {
70+
schemaRef := r.Schema(reflect.TypeOf(""), true, enumName)
71+
schemaRef.Title = enumName
72+
schemaRef.Examples = []any{values[0]}
73+
for _, v := range values {
74+
schemaRef.Enum = append(schemaRef.Enum, string(v))
75+
}
76+
r.Map()[enumName] = schemaRef
77+
}
78+
return &huma.Schema{Ref: fmt.Sprintf("#/components/schemas/%s", enumName)}
79+
}

0 commit comments

Comments
 (0)