From 7285972aa9533b533ca90251b585e149397c2936 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 22 Nov 2025 18:15:40 +0800 Subject: [PATCH] Add endpoint to append messages to running completions - Introduced a new POST endpoint `/completions/:context_id/append` to allow appending messages to ongoing chat completions. - Implemented `GinAppendMessages` function to handle request validation, including context ID and message requirements. - Added `AppendMessagesRequest` struct to define the request body format, ensuring proper handling of interrupt types and messages. - Enhanced error responses for invalid requests to improve user feedback and debugging capabilities. --- openapi/chat/chat.go | 3 ++ openapi/chat/completions.go | 72 +++++++++++++++++++++++++++++++++++++ openapi/chat/types.go | 9 +++++ 3 files changed, 84 insertions(+) diff --git a/openapi/chat/chat.go b/openapi/chat/chat.go index 69bc53f7..6c2d690a 100644 --- a/openapi/chat/chat.go +++ b/openapi/chat/chat.go @@ -30,6 +30,9 @@ func Attach(group *gin.RouterGroup, oauth types.OAuth) { // Delete Chat Completion group.DELETE("/completions/:completion_id", placeholder) + // Append messages to running completion + group.POST("/completions/:context_id/append", GinAppendMessages) + } func placeholder(c *gin.Context) { diff --git a/openapi/chat/completions.go b/openapi/chat/completions.go index 2bd83735..4699b84b 100644 --- a/openapi/chat/completions.go +++ b/openapi/chat/completions.go @@ -2,6 +2,7 @@ package chat import ( "fmt" + "time" "github.com/gin-gonic/gin" "github.com/yaoapp/kun/log" @@ -189,3 +190,74 @@ func GinCreateCompletions(c *gin.Context) { // GinUpdateCompletions handles PUT /chat/:assistant_id/completions - Update a chat completion metadata func GinUpdateCompletions(c *gin.Context) {} + +// GinAppendMessages handles POST /chat/:assistant_id/completions/:context_id/append +// Appends messages to a running completion (for user pre-input while AI is still generating) +func GinAppendMessages(c *gin.Context) { + // Get context_id from URL parameter + contextID := c.Param("context_id") + if contextID == "" { + response.RespondWithError(c, response.StatusBadRequest, &response.ErrorResponse{ + Code: response.ErrInvalidRequest.Code, + ErrorDescription: "context_id is required", + }) + return + } + + // Parse request body + var req AppendMessagesRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.RespondWithError(c, response.StatusBadRequest, &response.ErrorResponse{ + Code: response.ErrInvalidRequest.Code, + ErrorDescription: "Invalid request body: " + err.Error(), + }) + return + } + + // Validate interrupt type + if req.Type != context.InterruptGraceful && req.Type != context.InterruptForce { + response.RespondWithError(c, response.StatusBadRequest, &response.ErrorResponse{ + Code: response.ErrInvalidRequest.Code, + ErrorDescription: "Invalid interrupt type. Must be 'graceful' or 'force'", + }) + return + } + + // Validate messages + if len(req.Messages) == 0 { + response.RespondWithError(c, response.StatusBadRequest, &response.ErrorResponse{ + Code: response.ErrInvalidRequest.Code, + ErrorDescription: "At least one message is required", + }) + return + } + + // Create interrupt signal + signal := &context.InterruptSignal{ + Type: req.Type, + Messages: req.Messages, + Timestamp: time.Now().UnixMilli(), + Metadata: req.Metadata, + } + + // Send interrupt signal to the context + if err := context.SendInterrupt(contextID, signal); err != nil { + log.Trace("[INTERRUPT] Failed to send interrupt signal: context_id=%s, error=%v", contextID, err) + response.RespondWithError(c, response.StatusInternalServerError, &response.ErrorResponse{ + Code: response.ErrServerError.Code, + ErrorDescription: "Failed to send interrupt: " + err.Error(), + }) + return + } + + log.Trace("[INTERRUPT] Interrupt signal sent successfully: context_id=%s, type=%s, messages=%d", + contextID, req.Type, len(req.Messages)) + + // Return success response + response.RespondWithSuccess(c, response.StatusOK, gin.H{ + "message": "Messages appended successfully", + "context_id": contextID, + "type": req.Type, + "timestamp": signal.Timestamp, + }) +} diff --git a/openapi/chat/types.go b/openapi/chat/types.go index 5c2cd9a8..df0cfd8d 100644 --- a/openapi/chat/types.go +++ b/openapi/chat/types.go @@ -1 +1,10 @@ package chat + +import "github.com/yaoapp/yao/agent/context" + +// AppendMessagesRequest represents the request body for appending messages to running completion +type AppendMessagesRequest struct { + Type context.InterruptType `json:"type" binding:"required"` // Interrupt type: "graceful" or "force" + Messages []context.Message `json:"messages" binding:"required"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +}