Merge pull request #1350 from trheyi/main
Refactor message handling to support individual message lifecycle events
This commit is contained in:
commit
061fda6020
12 changed files with 89 additions and 485 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
|
@ -35,8 +34,8 @@ func DefaultStreamHandler(ctx *context.Context) message.StreamFunc {
|
||||||
case message.ChunkStreamStart:
|
case message.ChunkStreamStart:
|
||||||
return state.handleStreamStart(data)
|
return state.handleStreamStart(data)
|
||||||
|
|
||||||
case message.ChunkGroupStart:
|
case message.ChunkMessageStart:
|
||||||
return state.handleGroupStart(data)
|
return state.handleMessageStart(data)
|
||||||
|
|
||||||
case message.ChunkText:
|
case message.ChunkText:
|
||||||
return state.handleText(data)
|
return state.handleText(data)
|
||||||
|
|
@ -53,8 +52,8 @@ func DefaultStreamHandler(ctx *context.Context) message.StreamFunc {
|
||||||
case message.ChunkError:
|
case message.ChunkError:
|
||||||
return state.handleError(data)
|
return state.handleError(data)
|
||||||
|
|
||||||
case message.ChunkGroupEnd:
|
case message.ChunkMessageEnd:
|
||||||
return state.handleGroupEnd(data)
|
return state.handleMessageEnd(data)
|
||||||
|
|
||||||
case message.ChunkStreamEnd:
|
case message.ChunkStreamEnd:
|
||||||
return state.handleStreamEnd(data)
|
return state.handleStreamEnd(data)
|
||||||
|
|
@ -92,32 +91,32 @@ func (s *streamState) handleStreamStart(data []byte) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGroupStart handles group start event
|
// handleMessageStart handles message start event
|
||||||
func (s *streamState) handleGroupStart(data []byte) int {
|
func (s *streamState) handleMessageStart(data []byte) int {
|
||||||
// Parse group start data first to get the group ID
|
// Parse message start data first to get the message ID
|
||||||
var startData message.EventMessageStartData
|
var startData message.EventMessageStartData
|
||||||
if err := jsoniter.Unmarshal(data, &startData); err != nil {
|
if err := jsoniter.Unmarshal(data, &startData); err != nil {
|
||||||
log.Error("Failed to unmarshal group start data: %v", err)
|
log.Error("Failed to unmarshal message start data: %v", err)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the message ID from the start data, or generate one if not provided
|
// Use the message ID from the start data, or generate one if not provided
|
||||||
groupID := startData.MessageID
|
messageID := startData.MessageID
|
||||||
if groupID == "" {
|
if messageID == "" {
|
||||||
groupID = generateMessageID()
|
messageID = s.ctx.IDGenerator.GenerateMessageID()
|
||||||
startData.MessageID = groupID
|
startData.MessageID = messageID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize group state with the correct group ID
|
// Initialize message state with the correct message ID
|
||||||
s.inGroup = true
|
s.inGroup = true
|
||||||
s.currentGroupID = groupID
|
s.currentGroupID = messageID
|
||||||
s.buffer = []byte{}
|
s.buffer = []byte{}
|
||||||
s.chunkCount = 0
|
s.chunkCount = 0
|
||||||
s.messageSeq = 0 // Reset message sequence for each group
|
s.messageSeq = 0 // Reset message sequence for each message
|
||||||
s.groupStartTime = time.Now()
|
s.groupStartTime = time.Now()
|
||||||
|
|
||||||
// Send group_start event
|
// Send message_start event
|
||||||
msg := output.NewEventMessage(message.EventGroupStart, "Group started", startData)
|
msg := output.NewEventMessage(message.EventMessageStart, "Message started", startData)
|
||||||
s.ctx.Send(msg)
|
s.ctx.Send(msg)
|
||||||
|
|
||||||
return 0 // Continue
|
return 0 // Continue
|
||||||
|
|
@ -138,10 +137,10 @@ func (s *streamState) handleText(data []byte) int {
|
||||||
s.messageSeq++
|
s.messageSeq++
|
||||||
|
|
||||||
// Send delta message
|
// Send delta message
|
||||||
// - ChunkID: Sequential chunk ID (C1, C2, C3...) for this fragment
|
// - ChunkID: Unique chunk ID (C1, C2, C3...) for this fragment
|
||||||
// - MessageID: Same for all chunks of this logical message (frontend merges by message_id)
|
// - MessageID: Same for all chunks of this logical message (frontend merges by message_id)
|
||||||
msg := &message.Message{
|
msg := &message.Message{
|
||||||
ChunkID: s.generateSequentialID(), // Sequential ID for this chunk
|
ChunkID: s.ctx.IDGenerator.GenerateChunkID(), // Unique chunk ID
|
||||||
MessageID: s.currentGroupID, // Message ID for merging (all chunks share this)
|
MessageID: s.currentGroupID, // Message ID for merging (all chunks share this)
|
||||||
Type: message.TypeText,
|
Type: message.TypeText,
|
||||||
Delta: true,
|
Delta: true,
|
||||||
|
|
@ -173,10 +172,10 @@ func (s *streamState) handleThinking(data []byte) int {
|
||||||
s.messageSeq++
|
s.messageSeq++
|
||||||
|
|
||||||
// Send delta message
|
// Send delta message
|
||||||
// - ChunkID: Sequential chunk ID (C1, C2, C3...) for this fragment
|
// - ChunkID: Unique chunk ID (C1, C2, C3...) for this fragment
|
||||||
// - MessageID: Same for all chunks of this logical message (frontend merges by message_id)
|
// - MessageID: Same for all chunks of this logical message (frontend merges by message_id)
|
||||||
msg := &message.Message{
|
msg := &message.Message{
|
||||||
ChunkID: s.generateSequentialID(), // Sequential ID for this chunk
|
ChunkID: s.ctx.IDGenerator.GenerateChunkID(), // Unique chunk ID
|
||||||
MessageID: s.currentGroupID, // Message ID for merging (all chunks share this)
|
MessageID: s.currentGroupID, // Message ID for merging (all chunks share this)
|
||||||
Type: message.TypeThinking,
|
Type: message.TypeThinking,
|
||||||
Delta: true,
|
Delta: true,
|
||||||
|
|
@ -197,7 +196,8 @@ func (s *streamState) handleToolCall(data []byte) int {
|
||||||
// Tool calls are usually complete JSON objects
|
// Tool calls are usually complete JSON objects
|
||||||
// Parse and send as tool_call message
|
// Parse and send as tool_call message
|
||||||
msg := &message.Message{
|
msg := &message.Message{
|
||||||
MessageID: generateMessageID(),
|
ChunkID: s.ctx.IDGenerator.GenerateChunkID(),
|
||||||
|
MessageID: s.ctx.IDGenerator.GenerateMessageID(), // Tool call is a new message
|
||||||
Type: message.TypeToolCall,
|
Type: message.TypeToolCall,
|
||||||
Delta: true,
|
Delta: true,
|
||||||
Props: map[string]interface{}{
|
Props: map[string]interface{}{
|
||||||
|
|
@ -226,8 +226,8 @@ func (s *streamState) handleError(data []byte) int {
|
||||||
return 1 // Stop streaming on error
|
return 1 // Stop streaming on error
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGroupEnd handles group end event
|
// handleMessageEnd handles message end event
|
||||||
func (s *streamState) handleGroupEnd(data []byte) int {
|
func (s *streamState) handleMessageEnd(data []byte) int {
|
||||||
if !s.inGroup {
|
if !s.inGroup {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
@ -254,8 +254,8 @@ func (s *streamState) handleGroupEnd(data []byte) int {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send group_end event
|
// Send message_end event
|
||||||
msg := output.NewEventMessage(message.EventGroupEnd, "Group completed", endData)
|
msg := output.NewEventMessage(message.EventMessageEnd, "Message completed", endData)
|
||||||
s.ctx.Send(msg)
|
s.ctx.Send(msg)
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
|
|
@ -286,17 +286,3 @@ func (s *streamState) handleStreamEnd(data []byte) int {
|
||||||
s.ctx.Flush()
|
s.ctx.Flush()
|
||||||
return 0 // Continue (stream will end naturally)
|
return 0 // Continue (stream will end naturally)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateSequentialID generates a sequential message ID for better readability
|
|
||||||
func (s *streamState) generateSequentialID() string {
|
|
||||||
// Format: 1, 2, 3, etc.
|
|
||||||
// This makes it easier for developers to track message order in logs
|
|
||||||
return fmt.Sprintf("%d", s.messageSeq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateMessageID generates a unique message ID
|
|
||||||
func generateMessageID() string {
|
|
||||||
// TODO: Implement proper ID generation
|
|
||||||
// For now, use a simple approach
|
|
||||||
return output.GenerateID()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,11 @@ const (
|
||||||
ChunkError StreamChunkType = "error" // Error chunk
|
ChunkError StreamChunkType = "error" // Error chunk
|
||||||
ChunkUnknown StreamChunkType = "unknown" // Unknown/unrecognized chunk type
|
ChunkUnknown StreamChunkType = "unknown" // Unknown/unrecognized chunk type
|
||||||
|
|
||||||
// Lifecycle event types - stream and group boundaries
|
// Lifecycle event types - stream and message boundaries
|
||||||
ChunkStreamStart StreamChunkType = "stream_start" // Stream begins (entire request starts)
|
ChunkStreamStart StreamChunkType = "stream_start" // Stream begins (entire request starts)
|
||||||
ChunkStreamEnd StreamChunkType = "stream_end" // Stream ends (entire request completes)
|
ChunkStreamEnd StreamChunkType = "stream_end" // Stream ends (entire request completes)
|
||||||
ChunkGroupStart StreamChunkType = "group_start" // Message group begins (text/tool_call/thinking group starts)
|
ChunkMessageStart StreamChunkType = "message_start" // Message begins (text/tool_call/thinking message starts)
|
||||||
ChunkGroupEnd StreamChunkType = "group_end" // Message group ends (text/tool_call/thinking group completes)
|
ChunkMessageEnd StreamChunkType = "message_end" // Message ends (text/tool_call/thinking message completes)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Writer is an alias for http.ResponseWriter interface used by an agent to construct a response.
|
// Writer is an alias for http.ResponseWriter interface used by an agent to construct a response.
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yaoapp/gou/plan"
|
"github.com/yaoapp/gou/plan"
|
||||||
|
"github.com/yaoapp/yao/agent/output/message"
|
||||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -29,6 +30,7 @@ func newTestContextWithInterrupt(chatID, assistantID string) *Context {
|
||||||
Referer: RefererAPI,
|
Referer: RefererAPI,
|
||||||
Accept: AcceptWebCUI,
|
Accept: AcceptWebCUI,
|
||||||
Route: "/test/route",
|
Route: "/test/route",
|
||||||
|
IDGenerator: message.NewIDGenerator(), // Initialize context-scoped ID generator
|
||||||
Metadata: map[string]interface{}{
|
Metadata: map[string]interface{}{
|
||||||
"test": "context_metadata",
|
"test": "context_metadata",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/yaoapp/gou/runtime/v8/bridge"
|
"github.com/yaoapp/gou/runtime/v8/bridge"
|
||||||
"github.com/yaoapp/yao/agent/output"
|
"github.com/yaoapp/yao/agent/output"
|
||||||
"github.com/yaoapp/yao/agent/output/message"
|
|
||||||
traceJsapi "github.com/yaoapp/yao/trace/jsapi"
|
traceJsapi "github.com/yaoapp/yao/trace/jsapi"
|
||||||
"rogchap.com/v8go"
|
"rogchap.com/v8go"
|
||||||
)
|
)
|
||||||
|
|
@ -50,9 +47,6 @@ func (ctx *Context) NewObject(v8ctx *v8go.Context) (*v8go.Value, error) {
|
||||||
// Set methods
|
// Set methods
|
||||||
jsObject.Set("Trace", ctx.traceMethod(v8ctx.Isolate()))
|
jsObject.Set("Trace", ctx.traceMethod(v8ctx.Isolate()))
|
||||||
jsObject.Set("Send", ctx.sendMethod(v8ctx.Isolate()))
|
jsObject.Set("Send", ctx.sendMethod(v8ctx.Isolate()))
|
||||||
jsObject.Set("SendGroup", ctx.sendGroupMethod(v8ctx.Isolate()))
|
|
||||||
jsObject.Set("SendGroupStart", ctx.sendGroupStartMethod(v8ctx.Isolate()))
|
|
||||||
jsObject.Set("SendGroupEnd", ctx.sendGroupEndMethod(v8ctx.Isolate()))
|
|
||||||
|
|
||||||
// Create instance
|
// Create instance
|
||||||
instance, err := jsObject.NewInstance(v8ctx)
|
instance, err := jsObject.NewInstance(v8ctx)
|
||||||
|
|
@ -211,188 +205,3 @@ func (ctx *Context) sendMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
||||||
// sendGroupMethod implements ctx.SendGroup(group)
|
// sendGroupMethod implements ctx.SendGroup(group)
|
||||||
// Usage: ctx.SendGroup({ id: "group1", messages: [...] })
|
// Usage: ctx.SendGroup({ id: "group1", messages: [...] })
|
||||||
// Automatically generates IDs, sends group_start/group_end events, and flushes output
|
// Automatically generates IDs, sends group_start/group_end events, and flushes output
|
||||||
func (ctx *Context) sendGroupMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
|
||||||
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|
||||||
v8ctx := info.Context()
|
|
||||||
args := info.Args()
|
|
||||||
|
|
||||||
if len(args) < 1 {
|
|
||||||
return bridge.JsException(v8ctx, "SendGroup requires a group argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse group argument
|
|
||||||
group, err := parseGroup(v8ctx, args[0])
|
|
||||||
if err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "invalid group: "+err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate block ID if not provided
|
|
||||||
if group.ID == "" {
|
|
||||||
if ctx.IDGenerator != nil {
|
|
||||||
group.ID = ctx.IDGenerator.GenerateBlockID()
|
|
||||||
} else {
|
|
||||||
group.ID = output.GenerateID()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send group_start event
|
|
||||||
startTime := time.Now()
|
|
||||||
startEvent := output.NewEventMessage(
|
|
||||||
message.EventGroupStart,
|
|
||||||
"Group started",
|
|
||||||
message.EventMessageStartData{
|
|
||||||
MessageID: group.ID,
|
|
||||||
Type: "mixed", // Mixed types in group
|
|
||||||
Timestamp: startTime.UnixMilli(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err := ctx.Send(startEvent); err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "Failed to send group_start event: "+err.Error())
|
|
||||||
}
|
|
||||||
if err := ctx.Flush(); err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "Flush failed after group_start: "+err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate MessageIDs for messages and set BlockID
|
|
||||||
for _, msg := range group.Messages {
|
|
||||||
if msg.MessageID == "" {
|
|
||||||
if ctx.IDGenerator != nil {
|
|
||||||
msg.MessageID = ctx.IDGenerator.GenerateMessageID()
|
|
||||||
} else {
|
|
||||||
msg.MessageID = output.GenerateID()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if msg.BlockID == "" {
|
|
||||||
msg.BlockID = group.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call ctx.SendGroup
|
|
||||||
if err := ctx.SendGroup(group); err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "SendGroup failed: "+err.Error())
|
|
||||||
}
|
|
||||||
if err := ctx.Flush(); err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "Flush failed after SendGroup: "+err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send group_end event
|
|
||||||
endEvent := output.NewEventMessage(
|
|
||||||
message.EventGroupEnd,
|
|
||||||
"Group completed",
|
|
||||||
message.EventMessageEndData{
|
|
||||||
MessageID: group.ID,
|
|
||||||
Type: "mixed",
|
|
||||||
Timestamp: time.Now().UnixMilli(),
|
|
||||||
DurationMs: time.Since(startTime).Milliseconds(),
|
|
||||||
ChunkCount: len(group.Messages),
|
|
||||||
Status: "completed",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err := ctx.Send(endEvent); err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "Failed to send group_end event: "+err.Error())
|
|
||||||
}
|
|
||||||
if err := ctx.Flush(); err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "Flush failed after group_end: "+err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return v8go.Undefined(iso)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendGroupStartMethod implements ctx.SendGroupStart(type?, id?)
|
|
||||||
// Usage: const groupId = ctx.SendGroupStart() // type="mixed", auto-generate ID
|
|
||||||
// Usage: const groupId = ctx.SendGroupStart("text") // type="text", auto-generate ID
|
|
||||||
// Usage: const groupId = ctx.SendGroupStart("text", "my-group-id") // type="text", use provided ID
|
|
||||||
// Returns the group ID (generated or provided)
|
|
||||||
func (ctx *Context) sendGroupStartMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
|
||||||
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|
||||||
v8ctx := info.Context()
|
|
||||||
args := info.Args()
|
|
||||||
|
|
||||||
// Get type (default: "mixed")
|
|
||||||
groupType := "mixed"
|
|
||||||
if len(args) > 0 && args[0].IsString() {
|
|
||||||
groupType = args[0].String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get or generate block ID
|
|
||||||
var groupID string
|
|
||||||
if len(args) > 1 && args[1].IsString() {
|
|
||||||
groupID = args[1].String()
|
|
||||||
} else {
|
|
||||||
if ctx.IDGenerator != nil {
|
|
||||||
groupID = ctx.IDGenerator.GenerateBlockID()
|
|
||||||
} else {
|
|
||||||
groupID = output.GenerateID()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send group_start event
|
|
||||||
startEvent := output.NewEventMessage(
|
|
||||||
message.EventGroupStart,
|
|
||||||
"Group started",
|
|
||||||
message.EventMessageStartData{
|
|
||||||
MessageID: groupID,
|
|
||||||
Type: groupType,
|
|
||||||
Timestamp: time.Now().UnixMilli(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err := ctx.Send(startEvent); err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "Failed to send group_start event: "+err.Error())
|
|
||||||
}
|
|
||||||
if err := ctx.Flush(); err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "Flush failed after group_start: "+err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the group ID
|
|
||||||
groupIDVal, err := v8go.NewValue(iso, groupID)
|
|
||||||
if err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "Failed to create return value: "+err.Error())
|
|
||||||
}
|
|
||||||
return groupIDVal
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendGroupEndMethod implements ctx.SendGroupEnd(id, chunkCount?)
|
|
||||||
// Usage: ctx.SendGroupEnd(groupId)
|
|
||||||
// Usage: ctx.SendGroupEnd(groupId, 10) // With chunk count
|
|
||||||
func (ctx *Context) sendGroupEndMethod(iso *v8go.Isolate) *v8go.FunctionTemplate {
|
|
||||||
return v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|
||||||
v8ctx := info.Context()
|
|
||||||
args := info.Args()
|
|
||||||
|
|
||||||
// Group ID is required
|
|
||||||
if len(args) < 1 || !args[0].IsString() {
|
|
||||||
return bridge.JsException(v8ctx, "SendGroupEnd requires a group ID (string) as first argument")
|
|
||||||
}
|
|
||||||
groupID := args[0].String()
|
|
||||||
|
|
||||||
// Optional chunk count
|
|
||||||
chunkCount := 0
|
|
||||||
if len(args) > 1 && args[1].IsNumber() {
|
|
||||||
chunkCount = int(args[1].Integer())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send group_end event
|
|
||||||
endEvent := output.NewEventMessage(
|
|
||||||
message.EventGroupEnd,
|
|
||||||
"Group completed",
|
|
||||||
message.EventMessageEndData{
|
|
||||||
MessageID: groupID,
|
|
||||||
Type: "mixed",
|
|
||||||
Timestamp: time.Now().UnixMilli(),
|
|
||||||
DurationMs: 0, // Duration not tracked at this level
|
|
||||||
ChunkCount: chunkCount,
|
|
||||||
Status: "completed",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err := ctx.Send(endEvent); err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "Failed to send group_end event: "+err.Error())
|
|
||||||
}
|
|
||||||
if err := ctx.Flush(); err != nil {
|
|
||||||
return bridge.JsException(v8ctx, "Flush failed after group_end: "+err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return v8go.Undefined(iso)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||||
|
"github.com/yaoapp/yao/agent/output/message"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
"github.com/yaoapp/yao/test"
|
"github.com/yaoapp/yao/test"
|
||||||
)
|
)
|
||||||
|
|
@ -52,6 +53,7 @@ func TestJsValueSend(t *testing.T) {
|
||||||
Accept: "standard",
|
Accept: "standard",
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
Writer: newMockResponseWriter(),
|
Writer: newMockResponseWriter(),
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test sending string shorthand
|
// Test sending string shorthand
|
||||||
|
|
@ -108,106 +110,6 @@ func TestJsValueSend(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestJsValueSendGroup test the SendGroup method on Context
|
// TestJsValueSendGroup test the SendGroup method on Context
|
||||||
func TestJsValueSendGroup(t *testing.T) {
|
|
||||||
|
|
||||||
test.Prepare(t, config.Conf)
|
|
||||||
defer test.Clean()
|
|
||||||
|
|
||||||
cxt := &Context{
|
|
||||||
ChatID: "test-chat-id",
|
|
||||||
AssistantID: "test-assistant-id",
|
|
||||||
Context: context.Background(),
|
|
||||||
Accept: "standard",
|
|
||||||
Locale: "en",
|
|
||||||
Writer: newMockResponseWriter(),
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := v8.Call(v8.CallOptions{}, `
|
|
||||||
function test(ctx) {
|
|
||||||
try {
|
|
||||||
// Send message group
|
|
||||||
ctx.SendGroup({
|
|
||||||
id: "group_123",
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
props: { content: "First message" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
props: { content: "Second message" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "loading",
|
|
||||||
props: { message: "Processing..." }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
metadata: {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
sequence: 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}`, cxt)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Call failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, ok := res.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected map result, got %T", res)
|
|
||||||
}
|
|
||||||
assert.Equal(t, true, result["success"], "SendGroup should succeed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestJsValueSendGroupStartEnd test the SendGroupStart and SendGroupEnd methods
|
|
||||||
func TestJsValueSendGroupStartEnd(t *testing.T) {
|
|
||||||
|
|
||||||
test.Prepare(t, config.Conf)
|
|
||||||
defer test.Clean()
|
|
||||||
|
|
||||||
cxt := &Context{
|
|
||||||
ChatID: "test-chat-id",
|
|
||||||
AssistantID: "test-assistant-id",
|
|
||||||
Context: context.Background(),
|
|
||||||
Accept: "standard",
|
|
||||||
Locale: "en",
|
|
||||||
Writer: newMockResponseWriter(),
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := v8.Call(v8.CallOptions{}, `
|
|
||||||
function test(ctx) {
|
|
||||||
try {
|
|
||||||
// Start a group with auto-generated ID
|
|
||||||
const groupId = ctx.SendGroupStart("text");
|
|
||||||
|
|
||||||
// Send messages in the group
|
|
||||||
ctx.Send({ type: "text", props: { content: "Message 1" }, group_id: groupId });
|
|
||||||
ctx.Send({ type: "text", props: { content: "Message 2" }, group_id: groupId });
|
|
||||||
|
|
||||||
// End the group
|
|
||||||
ctx.SendGroupEnd(groupId, 2);
|
|
||||||
|
|
||||||
return { success: true, groupId: groupId };
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}`, cxt)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Call failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, ok := res.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected map result, got %T", res)
|
|
||||||
}
|
|
||||||
assert.Equal(t, true, result["success"], "SendGroupStart/End should succeed")
|
|
||||||
assert.NotEmpty(t, result["groupId"], "Should return group ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestJsValueSendDeltaUpdates test delta updates in Send
|
// TestJsValueSendDeltaUpdates test delta updates in Send
|
||||||
func TestJsValueSendDeltaUpdates(t *testing.T) {
|
func TestJsValueSendDeltaUpdates(t *testing.T) {
|
||||||
|
|
||||||
|
|
@ -221,6 +123,7 @@ func TestJsValueSendDeltaUpdates(t *testing.T) {
|
||||||
Accept: "standard",
|
Accept: "standard",
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
Writer: newMockResponseWriter(),
|
Writer: newMockResponseWriter(),
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := v8.Call(v8.CallOptions{}, `
|
res, err := v8.Call(v8.CallOptions{}, `
|
||||||
|
|
@ -275,6 +178,7 @@ func TestJsValueSendMultipleTypes(t *testing.T) {
|
||||||
Accept: "standard",
|
Accept: "standard",
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
Writer: newMockResponseWriter(),
|
Writer: newMockResponseWriter(),
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := v8.Call(v8.CallOptions{}, `
|
res, err := v8.Call(v8.CallOptions{}, `
|
||||||
|
|
@ -357,6 +261,7 @@ func TestJsValueSendErrorHandling(t *testing.T) {
|
||||||
Accept: "standard",
|
Accept: "standard",
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
Writer: newMockResponseWriter(),
|
Writer: newMockResponseWriter(),
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test invalid argument - no arguments
|
// Test invalid argument - no arguments
|
||||||
|
|
@ -382,62 +287,6 @@ func TestJsValueSendErrorHandling(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestJsValueSendGroupErrorHandling test error handling in SendGroup
|
// TestJsValueSendGroupErrorHandling test error handling in SendGroup
|
||||||
func TestJsValueSendGroupErrorHandling(t *testing.T) {
|
|
||||||
|
|
||||||
test.Prepare(t, config.Conf)
|
|
||||||
defer test.Clean()
|
|
||||||
|
|
||||||
cxt := &Context{
|
|
||||||
ChatID: "test-chat-id",
|
|
||||||
AssistantID: "test-assistant-id",
|
|
||||||
Context: context.Background(),
|
|
||||||
Accept: "standard",
|
|
||||||
Locale: "en",
|
|
||||||
Writer: newMockResponseWriter(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test invalid argument - no arguments
|
|
||||||
res, err := v8.Call(v8.CallOptions{}, `
|
|
||||||
function test(ctx) {
|
|
||||||
try {
|
|
||||||
ctx.SendGroup();
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}`, cxt)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Call failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, ok := res.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected map result, got %T", res)
|
|
||||||
}
|
|
||||||
assert.Equal(t, false, result["success"], "SendGroup without arguments should fail")
|
|
||||||
assert.Contains(t, result["error"], "SendGroup requires a group argument", "Error should mention missing group")
|
|
||||||
|
|
||||||
// Test invalid group - missing messages
|
|
||||||
res, err = v8.Call(v8.CallOptions{}, `
|
|
||||||
function test(ctx) {
|
|
||||||
try {
|
|
||||||
ctx.SendGroup({ id: "grp_1" });
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}`, cxt)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Call failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, ok = res.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected map result, got %T", res)
|
|
||||||
}
|
|
||||||
assert.Equal(t, false, result["success"], "SendGroup without messages should fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestJsValueSendWithCUIAccept test Send with CUI accept types
|
// TestJsValueSendWithCUIAccept test Send with CUI accept types
|
||||||
func TestJsValueSendWithCUIAccept(t *testing.T) {
|
func TestJsValueSendWithCUIAccept(t *testing.T) {
|
||||||
|
|
||||||
|
|
@ -483,67 +332,6 @@ func TestJsValueSendWithCUIAccept(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestJsValueSendGroupWithMetadata test SendGroup with various metadata
|
// TestJsValueSendGroupWithMetadata test SendGroup with various metadata
|
||||||
func TestJsValueSendGroupWithMetadata(t *testing.T) {
|
|
||||||
|
|
||||||
test.Prepare(t, config.Conf)
|
|
||||||
defer test.Clean()
|
|
||||||
|
|
||||||
cxt := &Context{
|
|
||||||
ChatID: "test-chat-id",
|
|
||||||
AssistantID: "test-assistant-id",
|
|
||||||
Context: context.Background(),
|
|
||||||
Accept: "standard",
|
|
||||||
Locale: "en",
|
|
||||||
Writer: newMockResponseWriter(),
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := v8.Call(v8.CallOptions{}, `
|
|
||||||
function test(ctx) {
|
|
||||||
try {
|
|
||||||
ctx.SendGroup({
|
|
||||||
id: "group_with_metadata",
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
props: { content: "Message 1" },
|
|
||||||
metadata: {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
sequence: 1,
|
|
||||||
trace_id: "trace_abc"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
props: { content: "Message 2" },
|
|
||||||
metadata: {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
sequence: 2,
|
|
||||||
trace_id: "trace_abc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
metadata: {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
sequence: 1,
|
|
||||||
trace_id: "trace_abc"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
return { success: false, error: error.message };
|
|
||||||
}
|
|
||||||
}`, cxt)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Call failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, ok := res.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Expected map result, got %T", res)
|
|
||||||
}
|
|
||||||
assert.Equal(t, true, result["success"], "SendGroup with metadata should succeed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestJsValueSendChainedCalls test chained Send calls
|
// TestJsValueSendChainedCalls test chained Send calls
|
||||||
func TestJsValueSendChainedCalls(t *testing.T) {
|
func TestJsValueSendChainedCalls(t *testing.T) {
|
||||||
|
|
||||||
|
|
@ -557,6 +345,7 @@ func TestJsValueSendChainedCalls(t *testing.T) {
|
||||||
Accept: "standard",
|
Accept: "standard",
|
||||||
Locale: "en",
|
Locale: "en",
|
||||||
Writer: newMockResponseWriter(),
|
Writer: newMockResponseWriter(),
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := v8.Call(v8.CallOptions{}, `
|
res, err := v8.Call(v8.CallOptions{}, `
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
v8 "github.com/yaoapp/gou/runtime/v8"
|
v8 "github.com/yaoapp/gou/runtime/v8"
|
||||||
"github.com/yaoapp/gou/runtime/v8/bridge"
|
"github.com/yaoapp/gou/runtime/v8/bridge"
|
||||||
|
"github.com/yaoapp/yao/agent/output/message"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||||
"github.com/yaoapp/yao/test"
|
"github.com/yaoapp/yao/test"
|
||||||
|
|
@ -25,6 +26,7 @@ func TestJsValue(t *testing.T) {
|
||||||
ChatID: "ChatID-123456",
|
ChatID: "ChatID-123456",
|
||||||
AssistantID: "AssistantID-1234",
|
AssistantID: "AssistantID-1234",
|
||||||
Sid: "Sid-1234",
|
Sid: "Sid-1234",
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
v8.RegisterFunction("testContextJsvalue", testContextJsvalueEmbed)
|
v8.RegisterFunction("testContextJsvalue", testContextJsvalueEmbed)
|
||||||
|
|
@ -93,6 +95,7 @@ func TestJsValueConcurrent(t *testing.T) {
|
||||||
ChatID: chatID,
|
ChatID: chatID,
|
||||||
AssistantID: assistantID,
|
AssistantID: assistantID,
|
||||||
Sid: sid,
|
Sid: sid,
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := v8.Call(v8.CallOptions{}, `
|
res, err := v8.Call(v8.CallOptions{}, `
|
||||||
|
|
@ -151,6 +154,7 @@ func TestJsValueRegistrationAndCleanup(t *testing.T) {
|
||||||
ChatID: fmt.Sprintf("ChatID-%d", i),
|
ChatID: fmt.Sprintf("ChatID-%d", i),
|
||||||
AssistantID: fmt.Sprintf("AssistantID-%d", i),
|
AssistantID: fmt.Sprintf("AssistantID-%d", i),
|
||||||
Sid: fmt.Sprintf("Sid-%d", i),
|
Sid: fmt.Sprintf("Sid-%d", i),
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := v8.Call(v8.CallOptions{}, `
|
_, err := v8.Call(v8.CallOptions{}, `
|
||||||
|
|
@ -442,6 +446,7 @@ func TestJsValueTrace(t *testing.T) {
|
||||||
TraceID: "test-trace-id",
|
TraceID: "test-trace-id",
|
||||||
},
|
},
|
||||||
Context: context.Background(),
|
Context: context.Background(),
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := v8.Call(v8.CallOptions{}, `
|
res, err := v8.Call(v8.CallOptions{}, `
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/yaoapp/gou/plan"
|
"github.com/yaoapp/gou/plan"
|
||||||
"github.com/yaoapp/gou/store"
|
"github.com/yaoapp/gou/store"
|
||||||
|
"github.com/yaoapp/yao/agent/output/message"
|
||||||
"github.com/yaoapp/yao/openapi/oauth/authorized"
|
"github.com/yaoapp/yao/openapi/oauth/authorized"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -66,6 +67,7 @@ func GetCompletionRequest(c *gin.Context, cache store.Store) (*CompletionRequest
|
||||||
Route: GetRoute(c, completionReq),
|
Route: GetRoute(c, completionReq),
|
||||||
Metadata: GetMetadata(c, completionReq),
|
Metadata: GetMetadata(c, completionReq),
|
||||||
Skip: GetSkip(c, completionReq),
|
Skip: GetSkip(c, completionReq),
|
||||||
|
IDGenerator: message.NewIDGenerator(), // Initialize context-scoped ID generator
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize interrupt controller
|
// Initialize interrupt controller
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/yaoapp/yao/agent/output/message"
|
||||||
"github.com/yaoapp/yao/config"
|
"github.com/yaoapp/yao/config"
|
||||||
"github.com/yaoapp/yao/test"
|
"github.com/yaoapp/yao/test"
|
||||||
)
|
)
|
||||||
|
|
@ -196,7 +197,9 @@ func TestEnterStack_RootCreation(t *testing.T) {
|
||||||
test.Prepare(t, config.Conf)
|
test.Prepare(t, config.Conf)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
ctx := &Context{}
|
ctx := &Context{
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
|
}
|
||||||
|
|
||||||
stack, traceID, done := EnterStack(ctx, "test-assistant", RefererAPI)
|
stack, traceID, done := EnterStack(ctx, "test-assistant", RefererAPI)
|
||||||
defer done()
|
defer done()
|
||||||
|
|
@ -239,7 +242,9 @@ func TestEnterStack_ChildCreation(t *testing.T) {
|
||||||
test.Prepare(t, config.Conf)
|
test.Prepare(t, config.Conf)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
ctx := &Context{}
|
ctx := &Context{
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
|
}
|
||||||
|
|
||||||
// Create parent
|
// Create parent
|
||||||
parentStack, parentTraceID, parentDone := EnterStack(ctx, "parent-assistant", RefererAPI)
|
parentStack, parentTraceID, parentDone := EnterStack(ctx, "parent-assistant", RefererAPI)
|
||||||
|
|
@ -282,7 +287,9 @@ func TestEnterStack_DoneCallback(t *testing.T) {
|
||||||
test.Prepare(t, config.Conf)
|
test.Prepare(t, config.Conf)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
ctx := &Context{}
|
ctx := &Context{
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
|
}
|
||||||
|
|
||||||
// Create parent
|
// Create parent
|
||||||
parentStack, _, parentDone := EnterStack(ctx, "parent-assistant", RefererAPI)
|
parentStack, _, parentDone := EnterStack(ctx, "parent-assistant", RefererAPI)
|
||||||
|
|
@ -321,7 +328,9 @@ func TestContextGetAllStacks(t *testing.T) {
|
||||||
test.Prepare(t, config.Conf)
|
test.Prepare(t, config.Conf)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
ctx := &Context{}
|
ctx := &Context{
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
|
}
|
||||||
|
|
||||||
// Create multiple stacks
|
// Create multiple stacks
|
||||||
_, _, done1 := EnterStack(ctx, "assistant1", RefererAPI)
|
_, _, done1 := EnterStack(ctx, "assistant1", RefererAPI)
|
||||||
|
|
@ -345,7 +354,9 @@ func TestContextGetStackByID(t *testing.T) {
|
||||||
test.Prepare(t, config.Conf)
|
test.Prepare(t, config.Conf)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
ctx := &Context{}
|
ctx := &Context{
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
|
}
|
||||||
|
|
||||||
stack, _, done := EnterStack(ctx, "test-assistant", RefererAPI)
|
stack, _, done := EnterStack(ctx, "test-assistant", RefererAPI)
|
||||||
defer done()
|
defer done()
|
||||||
|
|
@ -372,7 +383,9 @@ func TestContextGetStacksByTraceID(t *testing.T) {
|
||||||
test.Prepare(t, config.Conf)
|
test.Prepare(t, config.Conf)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
ctx := &Context{}
|
ctx := &Context{
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
|
}
|
||||||
|
|
||||||
// Create parent and child (same trace ID)
|
// Create parent and child (same trace ID)
|
||||||
_, traceID, done1 := EnterStack(ctx, "parent-assistant", RefererAPI)
|
_, traceID, done1 := EnterStack(ctx, "parent-assistant", RefererAPI)
|
||||||
|
|
@ -400,7 +413,9 @@ func TestContextGetRootStack(t *testing.T) {
|
||||||
test.Prepare(t, config.Conf)
|
test.Prepare(t, config.Conf)
|
||||||
defer test.Clean()
|
defer test.Clean()
|
||||||
|
|
||||||
ctx := &Context{}
|
ctx := &Context{
|
||||||
|
IDGenerator: message.NewIDGenerator(),
|
||||||
|
}
|
||||||
|
|
||||||
// Create parent
|
// Create parent
|
||||||
parentStack, _, done1 := EnterStack(ctx, "parent-assistant", RefererAPI)
|
parentStack, _, done1 := EnterStack(ctx, "parent-assistant", RefererAPI)
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ func TestDeepSeekR1StreamBasic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track group_end events to verify type field
|
// Track group_end events to verify type field
|
||||||
if chunkType == message.ChunkGroupEnd {
|
if chunkType == message.ChunkMessageEnd {
|
||||||
// Parse the group_end data to check the type field
|
// Parse the group_end data to check the type field
|
||||||
var groupEndData struct {
|
var groupEndData struct {
|
||||||
GroupID string `json:"group_id"`
|
GroupID string `json:"group_id"`
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ func (mt *messageTracker) startMessage(messageType message.StreamChunkType, hand
|
||||||
Timestamp: mt.startTime,
|
Timestamp: mt.startTime,
|
||||||
}
|
}
|
||||||
if startJSON, err := jsoniter.Marshal(startData); err == nil {
|
if startJSON, err := jsoniter.Marshal(startData); err == nil {
|
||||||
handler(message.ChunkGroupStart, startJSON)
|
handler(message.ChunkMessageStart, startJSON)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +79,7 @@ func (mt *messageTracker) startToolCallMessage(toolCallInfo *message.EventToolCa
|
||||||
ToolCall: toolCallInfo,
|
ToolCall: toolCallInfo,
|
||||||
}
|
}
|
||||||
if startJSON, err := jsoniter.Marshal(startData); err == nil {
|
if startJSON, err := jsoniter.Marshal(startData); err == nil {
|
||||||
handler(message.ChunkGroupStart, startJSON)
|
handler(message.ChunkMessageStart, startJSON)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +112,7 @@ func (mt *messageTracker) endMessage(handler message.StreamFunc) {
|
||||||
endData.ToolCall = mt.toolCallInfo
|
endData.ToolCall = mt.toolCallInfo
|
||||||
}
|
}
|
||||||
if endJSON, err := jsoniter.Marshal(endData); err == nil {
|
if endJSON, err := jsoniter.Marshal(endData); err == nil {
|
||||||
handler(message.ChunkGroupEnd, endJSON)
|
handler(message.ChunkMessageEnd, endJSON)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1299,7 +1299,7 @@ func TestOpenAIStreamLifecycleEvents(t *testing.T) {
|
||||||
case message.ChunkStreamEnd:
|
case message.ChunkStreamEnd:
|
||||||
t.Error("❌ LLM layer should NOT send stream_end (now sent at Agent level)")
|
t.Error("❌ LLM layer should NOT send stream_end (now sent at Agent level)")
|
||||||
|
|
||||||
case message.ChunkGroupStart:
|
case message.ChunkMessageStart:
|
||||||
groupStartReceived = true
|
groupStartReceived = true
|
||||||
var startData message.EventMessageStartData
|
var startData message.EventMessageStartData
|
||||||
if err := json.Unmarshal(data, &startData); err == nil {
|
if err := json.Unmarshal(data, &startData); err == nil {
|
||||||
|
|
@ -1311,7 +1311,7 @@ func TestOpenAIStreamLifecycleEvents(t *testing.T) {
|
||||||
t.Errorf("Failed to parse group_start data: %v", err)
|
t.Errorf("Failed to parse group_start data: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
case message.ChunkGroupEnd:
|
case message.ChunkMessageEnd:
|
||||||
groupEndReceived = true
|
groupEndReceived = true
|
||||||
var endData message.EventMessageEndData
|
var endData message.EventMessageEndData
|
||||||
if err := json.Unmarshal(data, &endData); err == nil {
|
if err := json.Unmarshal(data, &endData); err == nil {
|
||||||
|
|
|
||||||
|
|
@ -110,10 +110,6 @@ const (
|
||||||
// Message level events (LLM layer - individual logical messages)
|
// Message level events (LLM layer - individual logical messages)
|
||||||
EventMessageStart = "message_start" // Message started event
|
EventMessageStart = "message_start" // Message started event
|
||||||
EventMessageEnd = "message_end" // Message ended event
|
EventMessageEnd = "message_end" // Message ended event
|
||||||
|
|
||||||
// Backward compatibility aliases (kept for transition period)
|
|
||||||
EventGroupStart = "group_start" // Alias for EventMessageStart
|
|
||||||
EventGroupEnd = "group_end" // Alias for EventMessageEnd
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Standard Props structures for built-in types
|
// Standard Props structures for built-in types
|
||||||
|
|
@ -243,11 +239,11 @@ const (
|
||||||
ChunkError StreamChunkType = "error" // Error chunk
|
ChunkError StreamChunkType = "error" // Error chunk
|
||||||
ChunkUnknown StreamChunkType = "unknown" // Unknown/unrecognized chunk type
|
ChunkUnknown StreamChunkType = "unknown" // Unknown/unrecognized chunk type
|
||||||
|
|
||||||
// Lifecycle event types - stream and group boundaries
|
// Lifecycle event types - stream and message boundaries
|
||||||
ChunkStreamStart StreamChunkType = "stream_start" // Stream begins (entire request starts)
|
ChunkStreamStart StreamChunkType = "stream_start" // Stream begins (entire request starts)
|
||||||
ChunkStreamEnd StreamChunkType = "stream_end" // Stream ends (entire request completes)
|
ChunkStreamEnd StreamChunkType = "stream_end" // Stream ends (entire request completes)
|
||||||
ChunkGroupStart StreamChunkType = "group_start" // Message group begins (text/tool_call/thinking group starts)
|
ChunkMessageStart StreamChunkType = "message_start" // Message begins (text/tool_call/thinking message starts)
|
||||||
ChunkGroupEnd StreamChunkType = "group_end" // Message group ends (text/tool_call/thinking group completes)
|
ChunkMessageEnd StreamChunkType = "message_end" // Message ends (text/tool_call/thinking message completes)
|
||||||
)
|
)
|
||||||
|
|
||||||
// StreamFunc the streaming function callback
|
// StreamFunc the streaming function callback
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue