yao/agent/context/context.go
Max c25fbb22d7 Add trace management to context and enhance configuration options
- Introduced a trace manager in the context struct for lazy initialization and management of trace operations.
- Implemented the Trace method in the context to facilitate trace object creation and management within JavaScript.
- Enhanced configuration options for trace drivers, including defaults for local and store drivers, and added path and prefix settings.
- Updated related tests to verify the functionality of the new trace management features.
2025-11-19 12:27:42 +08:00

244 lines
5.7 KiB
Go

package context
import (
"context"
"fmt"
"time"
jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/gou/plan"
"github.com/yaoapp/kun/log"
"github.com/yaoapp/yao/config"
"github.com/yaoapp/yao/openapi/oauth/types"
"github.com/yaoapp/yao/trace"
traceTypes "github.com/yaoapp/yao/trace/types"
)
// New create a new context
func New(parent context.Context, authorized *types.AuthorizedInfo, chatID, payload string) Context {
if parent == nil {
parent = context.Background()
}
// Validate the client type
ctx := Context{
Context: parent,
Space: plan.NewMemorySharedSpace(),
ChatID: chatID,
}
if payload == "" {
return ctx
}
err := jsoniter.Unmarshal([]byte(payload), &ctx)
if err != nil {
log.Error("%s", err.Error())
}
return ctx
}
// NewWithCancel create a new context with cancel
func NewWithCancel(parent context.Context, authorized *types.AuthorizedInfo, chatID, payload string) (Context, context.CancelFunc) {
ctx := New(parent, authorized, chatID, payload)
return WithCancel(ctx)
}
// NewWithTimeout create a new context with timeout
func NewWithTimeout(parent context.Context, authorized *types.AuthorizedInfo, chatID, payload string, timeout time.Duration) (Context, context.CancelFunc) {
ctx := New(parent, authorized, chatID, payload)
return WithTimeout(ctx, timeout)
}
// WithCancel create a new context
func WithCancel(parent Context) (Context, context.CancelFunc) {
new, cancel := context.WithCancel(parent.Context)
parent.Context = new
return parent, cancel
}
// WithTimeout create a new context
func WithTimeout(parent Context, timeout time.Duration) (Context, context.CancelFunc) {
new, cancel := context.WithTimeout(parent.Context, timeout)
parent.Context = new
return parent, cancel
}
// Release the context and clean up all resources including stacks and trace
func (ctx *Context) Release() {
// Complete and release trace if exists
if ctx.trace != nil && ctx.Stack != nil && ctx.Stack.TraceID != "" {
// Mark trace as complete (sends final event)
_ = ctx.trace.MarkComplete()
// Release from global registry (removes from registry and closes resources)
_ = trace.Release(ctx.Stack.TraceID)
ctx.trace = nil
}
// Clear space
if ctx.Space != nil {
ctx.Space.Clear()
ctx.Space = nil
}
// Clear stacks
if ctx.Stacks != nil {
for k := range ctx.Stacks {
delete(ctx.Stacks, k)
}
ctx.Stacks = nil
}
// Clear current stack reference
ctx.Stack = nil
// Clear writer reference
ctx.Writer = nil
ctx = nil
}
// Send sends data to the context's writer
// This is used by the output module to send messages to the client
func (ctx *Context) Send(data []byte) error {
if ctx.Writer == nil {
return nil // No writer, silently ignore
}
_, err := ctx.Writer.Write(data)
return err
}
// Trace returns the trace manager for this context, lazily initialized on first call
// Uses the TraceID from ctx.Stack if available, or generates a new one
func (ctx *Context) Trace() (traceTypes.Manager, error) {
// Return trace if already initialized
if ctx.trace != nil {
return ctx.trace, nil
}
// Get TraceID from Stack or generate new one
var traceID string
if ctx.Stack != nil && ctx.Stack.TraceID != "" {
traceID = ctx.Stack.TraceID
// Try to load existing trace first
manager, err := trace.Load(traceID)
if err == nil {
// Found in registry, reuse it
ctx.trace = manager
return manager, nil
}
}
// Get trace configuration from global config
cfg := config.Conf
// Prepare driver options
var driverOptions []any
var driverType string
switch cfg.Trace.Driver {
case "store":
driverType = trace.Store
if cfg.Trace.Store == "" {
return nil, fmt.Errorf("trace store ID not configured")
}
driverOptions = []any{cfg.Trace.Store, cfg.Trace.Prefix}
case "local", "":
driverType = trace.Local
driverOptions = []any{cfg.Trace.Path}
default:
return nil, fmt.Errorf("unsupported trace driver: %s", cfg.Trace.Driver)
}
// Create trace using trace.New (handles registry)
createdTraceID, manager, err := trace.New(ctx.Context, driverType, &traceTypes.TraceOption{
ID: traceID, // Use existing ID from Stack or empty to generate new one
}, driverOptions...)
if err != nil {
return nil, fmt.Errorf("failed to create trace: %w", err)
}
// Update Stack with the created TraceID if needed
if ctx.Stack != nil && ctx.Stack.TraceID == "" {
ctx.Stack.TraceID = createdTraceID
}
// Store for future calls
ctx.trace = manager
return manager, nil
}
// Map the context to a map
func (ctx *Context) Map() map[string]interface{} {
data := map[string]interface{}{}
// Authorized information
if ctx.Authorized != nil {
data["authorized"] = ctx.Authorized
}
if ctx.ChatID != "" {
data["chat_id"] = ctx.ChatID
}
if ctx.AssistantID != "" {
data["assistant_id"] = ctx.AssistantID
}
if ctx.Connector != "" {
data["connector"] = ctx.Connector
}
if ctx.Search != nil {
data["search"] = *ctx.Search
}
// Arguments for call
if len(ctx.Args) > 0 {
data["args"] = ctx.Args
}
if ctx.Retry {
data["retry"] = ctx.Retry
}
if ctx.RetryTimes > 0 {
data["retry_times"] = ctx.RetryTimes
}
// Locale information
if ctx.Locale != "" {
data["locale"] = ctx.Locale
}
if ctx.Theme != "" {
data["theme"] = ctx.Theme
}
// Request information
if ctx.Client.Type != "" || ctx.Client.UserAgent != "" || ctx.Client.IP != "" {
data["client"] = map[string]interface{}{
"type": ctx.Client.Type,
"user_agent": ctx.Client.UserAgent,
"ip": ctx.Client.IP,
}
}
if ctx.Referer != "" {
data["referer"] = ctx.Referer
}
if ctx.Accept != "" {
data["accept"] = ctx.Accept
}
// CUI Context information
if ctx.Route != "" {
data["route"] = ctx.Route
}
if len(ctx.Metadata) > 0 {
data["metadata"] = ctx.Metadata
}
return data
}