yao/agent/output/output.go
Max b43929210e Implement event message handling and enhance output capabilities
- Added support for lifecycle event messages, including a new event type for tracking stream states (e.g., stream_start, stream_end).
- Introduced a NewEventMessage function to create event messages with structured properties.
- Updated the output package to include event messages in the built-in types, ensuring compatibility with CUI clients while remaining silent for OpenAI clients.
- Enhanced the DefaultStreamHandler to utilize event messages for better lifecycle tracking during streaming operations.
- Improved error handling and logging in the OpenAI provider to capture and report streaming errors effectively.
2025-11-16 10:30:55 +08:00

149 lines
3.4 KiB
Go

package output
import (
"fmt"
"sync"
"github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/output/adapters/cui"
"github.com/yaoapp/yao/agent/output/adapters/openai"
"github.com/yaoapp/yao/agent/output/message"
)
var (
writerCache = make(map[*context.Context]message.Writer)
writerMutex sync.RWMutex
globalFactory message.WriterFactory
)
// Send sends a single message using the appropriate writer for the context
func Send(ctx *context.Context, msg *message.Message) error {
writer, err := GetWriter(ctx)
if err != nil {
return err
}
return writer.Write(msg)
}
// SendGroup sends a message group using the appropriate writer for the context
func SendGroup(ctx *context.Context, group *message.MessageGroup) error {
writer, err := GetWriter(ctx)
if err != nil {
return err
}
return writer.WriteGroup(group)
}
// GetWriter gets or creates a writer for the given context
// Writers are cached per context to avoid recreating them
func GetWriter(ctx *context.Context) (message.Writer, error) {
// Try to get cached writer
writerMutex.RLock()
writer, exists := writerCache[ctx]
writerMutex.RUnlock()
if exists {
return writer, nil
}
// Create new writer
writerMutex.Lock()
defer writerMutex.Unlock()
// Double-check after acquiring write lock
if writer, exists := writerCache[ctx]; exists {
return writer, nil
}
// Create writer based on context.Accept
writer, err := createWriter(ctx)
if err != nil {
return nil, err
}
// Cache the writer
writerCache[ctx] = writer
return writer, nil
}
// createWriter creates a writer based on context.Accept
func createWriter(ctx *context.Context) (message.Writer, error) {
// If global factory is set, use it
if globalFactory != nil {
return globalFactory.NewWriter(ctx, nil)
}
// Default: create based on Accept type
switch ctx.Accept {
case context.AcceptStandard:
// OpenAI-compatible format
return openai.NewWriter(ctx)
case context.AcceptWebCUI, context.AccepNativeCUI, context.AcceptDesktopCUI:
// CUI format
return cui.NewWriter(ctx)
default:
// Default to Standard
return openai.NewWriter(ctx)
}
}
// SetWriterFactory sets a custom writer factory
// This allows applications to provide their own writer implementations
func SetWriterFactory(factory message.WriterFactory) {
globalFactory = factory
}
// ClearWriterCache clears the writer cache
// Should be called when contexts are cleaned up
func ClearWriterCache(ctx *context.Context) {
writerMutex.Lock()
defer writerMutex.Unlock()
delete(writerCache, ctx)
}
// ClearAllWriterCache clears all cached writers
func ClearAllWriterCache() {
writerMutex.Lock()
defer writerMutex.Unlock()
writerCache = make(map[*context.Context]message.Writer)
}
// Flush flushes the writer for the given context
func Flush(ctx *context.Context) error {
writer, err := GetWriter(ctx)
if err != nil {
return err
}
return writer.Flush()
}
// Close closes the writer for the given context and removes it from cache
func Close(ctx *context.Context) error {
writer, err := GetWriter(ctx)
if err != nil {
return err
}
err = writer.Close()
ClearWriterCache(ctx)
return err
}
// SendMulti is a convenience function to send multiple messages
func SendMulti(ctx *context.Context, messages ...*message.Message) error {
writer, err := GetWriter(ctx)
if err != nil {
return err
}
for _, msg := range messages {
if err := writer.Write(msg); err != nil {
return fmt.Errorf("failed to send message: %w", err)
}
}
return nil
}