Refactor Assistant context handling to improve options management
- Updated the Assistant methods to accept an Options parameter, enhancing flexibility in context management. - Removed the Connector field from the context and related structures, transitioning to a more streamlined options-based approach. - Adjusted various tests to accommodate the new options handling, ensuring comprehensive coverage of the updated functionality. - Enhanced the Create and Next hooks to return options alongside responses, improving the overall usability of the API. - Cleaned up deprecated fields and improved context initialization for better maintainability.
This commit is contained in:
parent
beb63b77de
commit
a96946d8bb
42 changed files with 425 additions and 332 deletions
|
|
@ -18,7 +18,7 @@ import (
|
|||
|
||||
// Stream stream the agent
|
||||
// handler is optional, if not provided, a default handler will be used
|
||||
func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Message, handler ...message.StreamFunc) (interface{}, error) {
|
||||
func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Message, options ...*context.Options) (interface{}, error) {
|
||||
|
||||
log.Trace("[AGENT] Stream started: assistant=%s, contextID=%s", ast.ID, ctx.ID)
|
||||
defer log.Trace("[AGENT] Stream ended: assistant=%s, contextID=%s", ast.ID, ctx.ID)
|
||||
|
|
@ -39,8 +39,16 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa
|
|||
// Initialize
|
||||
// ================================================
|
||||
|
||||
// Get or create options
|
||||
var opts *context.Options
|
||||
if len(options) > 0 && options[0] != nil {
|
||||
opts = options[0]
|
||||
} else {
|
||||
opts = &context.Options{}
|
||||
}
|
||||
|
||||
// Initialize stack and auto-handle completion/failure/restore
|
||||
_, _, done := context.EnterStack(ctx, ast.ID, ctx.Referer)
|
||||
_, _, done := context.EnterStack(ctx, ast.ID, opts)
|
||||
defer done()
|
||||
|
||||
fmt.Println("--- Stack debug ---")
|
||||
|
|
@ -51,11 +59,11 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa
|
|||
fmt.Println("------ end stack debug ------")
|
||||
|
||||
// Determine stream handler
|
||||
streamHandler := ast.getStreamHandler(ctx, handler...)
|
||||
streamHandler := ast.getStreamHandler(ctx, opts)
|
||||
|
||||
// Get connector and capabilities early (before sending stream_start)
|
||||
// so that output adapters can use them when converting stream_start event
|
||||
err = ast.initializeCapabilities(ctx)
|
||||
err = ast.initializeCapabilities(ctx, opts)
|
||||
if err != nil {
|
||||
ast.sendStreamEndOnError(ctx, streamHandler, streamStartTime, err)
|
||||
return nil, err
|
||||
|
|
@ -85,7 +93,7 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa
|
|||
var createResponse *context.HookCreateResponse
|
||||
if ast.Script != nil {
|
||||
var err error
|
||||
createResponse, err = ast.Script.Create(ctx, fullMessages)
|
||||
createResponse, opts, err = ast.Script.Create(ctx, fullMessages, opts)
|
||||
if err != nil {
|
||||
ast.traceAgentFail(agentNode, err)
|
||||
// Send error stream_end for root stack
|
||||
|
|
@ -235,11 +243,11 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa
|
|||
|
||||
if ast.Script != nil {
|
||||
var err error
|
||||
nextResponse, err = ast.Script.Next(ctx, &context.NextHookPayload{
|
||||
nextResponse, opts, err = ast.Script.Next(ctx, &context.NextHookPayload{
|
||||
Messages: fullMessages,
|
||||
Completion: completionResponse,
|
||||
Tools: toolCallResponses,
|
||||
})
|
||||
}, opts)
|
||||
if err != nil {
|
||||
ast.traceAgentFail(agentNode, err)
|
||||
ast.sendStreamEndOnError(ctx, streamHandler, streamStartTime, err)
|
||||
|
|
@ -318,14 +326,14 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa
|
|||
return finalResponse, nil
|
||||
}
|
||||
|
||||
// GetConnector get the connector object, capabilities, and error with priority: createResponse > ctx > ast
|
||||
// Note: createResponse.Connector is already applied to ctx.Connector by applyContextAdjustments in create.go
|
||||
// GetConnector get the connector object, capabilities, and error with priority: opts.Connector > ast.Connector
|
||||
// Note: opts.Connector may be set by Create hook's applyOptionsAdjustments
|
||||
// Returns: (connector, capabilities, error)
|
||||
func (ast *Assistant) GetConnector(ctx *context.Context) (connector.Connector, *openai.Capabilities, error) {
|
||||
// Determine connector ID with priority
|
||||
func (ast *Assistant) GetConnector(ctx *context.Context, opts ...*context.Options) (connector.Connector, *openai.Capabilities, error) {
|
||||
// Determine connector ID with priority: opts.Connector > ast.Connector
|
||||
connectorID := ast.Connector
|
||||
if ctx.Connector != "" {
|
||||
connectorID = ctx.Connector
|
||||
if len(opts) > 0 && opts[0] != nil && opts[0].Connector != "" {
|
||||
connectorID = opts[0].Connector
|
||||
}
|
||||
|
||||
// If empty, return error
|
||||
|
|
@ -361,10 +369,11 @@ func (ast *Assistant) Info(locale ...string) *message.AssistantInfo {
|
|||
}
|
||||
}
|
||||
|
||||
// getStreamHandler returns the stream handler from the provided handlers or a default one
|
||||
func (ast *Assistant) getStreamHandler(ctx *context.Context, handler ...message.StreamFunc) message.StreamFunc {
|
||||
if len(handler) > 0 && handler[0] != nil {
|
||||
return handler[0]
|
||||
// getStreamHandler returns the stream handler from options or a default one
|
||||
func (ast *Assistant) getStreamHandler(ctx *context.Context, opts ...*context.Options) message.StreamFunc {
|
||||
// Check if handler is provided in options
|
||||
if len(opts) > 0 && opts[0] != nil && opts[0].Writer != nil {
|
||||
return handlers.DefaultStreamHandler(ctx)
|
||||
}
|
||||
return handlers.DefaultStreamHandler(ctx)
|
||||
}
|
||||
|
|
@ -460,12 +469,12 @@ func (ast *Assistant) handleInterrupt(ctx *context.Context, signal *context.Inte
|
|||
// initializeCapabilities gets connector and capabilities, then sets them in context
|
||||
// This should be called early (before sending stream_start) so that output adapters
|
||||
// can use capabilities when converting stream_start event
|
||||
func (ast *Assistant) initializeCapabilities(ctx *context.Context) error {
|
||||
func (ast *Assistant) initializeCapabilities(ctx *context.Context, opts *context.Options) error {
|
||||
if ast.Prompts == nil && ast.MCP == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, capabilities, err := ast.GetConnector(ctx)
|
||||
_, capabilities, err := ast.GetConnector(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ func newTestContextWithInterrupt(chatID, assistantID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ type agentCallerWrapper struct {
|
|||
ast *Assistant
|
||||
}
|
||||
|
||||
func (w *agentCallerWrapper) Stream(ctx *agentContext.Context, messages []agentContext.Message) (interface{}, error) {
|
||||
return w.ast.Stream(ctx, messages)
|
||||
func (w *agentCallerWrapper) Stream(ctx *agentContext.Context, messages []agentContext.Message, options ...*agentContext.Options) (interface{}, error) {
|
||||
return w.ast.Stream(ctx, messages, options...)
|
||||
}
|
||||
|
||||
// Get get the assistant by id
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ func TestBuildRequest_MCP(t *testing.T) {
|
|||
// Call create hook to get createResponse
|
||||
var createResponse *context.HookCreateResponse
|
||||
if hookAgent.Script != nil {
|
||||
createResponse, err = hookAgent.Script.Create(hookCtx, inputMessages)
|
||||
createResponse, _, err = hookAgent.Script.Create(hookCtx, inputMessages, &context.Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to call create hook: %s", err.Error())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -650,7 +650,7 @@ func TestPromptPresetAssistant(t *testing.T) {
|
|||
}
|
||||
|
||||
// Call Create hook
|
||||
createResponse, err := ast.Script.Create(ctx, messages)
|
||||
createResponse, _, err := ast.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createResponse)
|
||||
assert.Equal(t, "mode.friendly", createResponse.PromptPreset)
|
||||
|
|
@ -681,7 +681,7 @@ func TestPromptPresetAssistant(t *testing.T) {
|
|||
}
|
||||
|
||||
// Call Create hook
|
||||
createResponse, err := ast.Script.Create(ctx, messages)
|
||||
createResponse, _, err := ast.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createResponse)
|
||||
assert.Equal(t, "mode.professional", createResponse.PromptPreset)
|
||||
|
|
@ -718,7 +718,7 @@ func TestPromptPresetAssistant(t *testing.T) {
|
|||
}
|
||||
|
||||
// Call Create hook
|
||||
createResponse, err := ast.Script.Create(ctx, messages)
|
||||
createResponse, _, err := ast.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createResponse)
|
||||
require.NotNil(t, createResponse.DisableGlobalPrompts)
|
||||
|
|
@ -753,7 +753,7 @@ func TestPromptPresetAssistant(t *testing.T) {
|
|||
}
|
||||
|
||||
// Call Create hook
|
||||
createResponse, err := ast.Script.Create(ctx, messages)
|
||||
createResponse, _, err := ast.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createResponse)
|
||||
assert.Equal(t, "mode.friendly", createResponse.PromptPreset)
|
||||
|
|
@ -788,7 +788,7 @@ func TestPromptPresetAssistant(t *testing.T) {
|
|||
}
|
||||
|
||||
// Call Create hook
|
||||
createResponse, err := ast.Script.Create(ctx, messages)
|
||||
createResponse, _, err := ast.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, createResponse)
|
||||
assert.Equal(t, "non.existent.preset", createResponse.PromptPreset)
|
||||
|
|
@ -819,7 +819,7 @@ func TestPromptPresetAssistant(t *testing.T) {
|
|||
}
|
||||
|
||||
// Call Create hook - should return nil
|
||||
createResponse, err := ast.Script.Create(ctx, messages)
|
||||
createResponse, _, err := ast.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, createResponse)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ func newTestContext(chatID, assistantID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
@ -64,7 +63,7 @@ func TestBuildRequest(t *testing.T) {
|
|||
inputMessages := []context.Message{{Role: "user", Content: "no_override"}}
|
||||
|
||||
// Call Create hook
|
||||
createResponse, err := agent.Script.Create(ctx, inputMessages)
|
||||
createResponse, _, err := agent.Script.Create(ctx, inputMessages, &context.Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to call Create hook: %s", err.Error())
|
||||
}
|
||||
|
|
@ -112,7 +111,7 @@ func TestBuildRequest(t *testing.T) {
|
|||
t.Run("OverrideTemperature", func(t *testing.T) {
|
||||
inputMessages := []context.Message{{Role: "user", Content: "override_temperature"}}
|
||||
|
||||
createResponse, err := agent.Script.Create(ctx, inputMessages)
|
||||
createResponse, _, err := agent.Script.Create(ctx, inputMessages, &context.Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to call Create hook: %s", err.Error())
|
||||
}
|
||||
|
|
@ -143,7 +142,7 @@ func TestBuildRequest(t *testing.T) {
|
|||
t.Run("OverrideAll", func(t *testing.T) {
|
||||
inputMessages := []context.Message{{Role: "user", Content: "override_all"}}
|
||||
|
||||
createResponse, err := agent.Script.Create(ctx, inputMessages)
|
||||
createResponse, _, err := agent.Script.Create(ctx, inputMessages, &context.Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to call Create hook: %s", err.Error())
|
||||
}
|
||||
|
|
@ -196,7 +195,7 @@ func TestBuildRequest(t *testing.T) {
|
|||
t.Run("OverrideRouteMetadata", func(t *testing.T) {
|
||||
inputMessages := []context.Message{{Role: "user", Content: "override_route_metadata"}}
|
||||
|
||||
createResponse, err := agent.Script.Create(ctx, inputMessages)
|
||||
createResponse, _, err := agent.Script.Create(ctx, inputMessages, &context.Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to call Create hook: %s", err.Error())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,53 +9,57 @@ import (
|
|||
)
|
||||
|
||||
// Create create a new assistant
|
||||
func (s *Script) Create(ctx *context.Context, messages []context.Message) (*context.HookCreateResponse, error) {
|
||||
res, err := s.Execute(ctx, "Create", messages)
|
||||
// opts is optional - if provided, will be adjusted based on hook response
|
||||
func (s *Script) Create(ctx *context.Context, messages []context.Message, opts ...*context.Options) (*context.HookCreateResponse, *context.Options, error) {
|
||||
// Get or create options
|
||||
var options *context.Options
|
||||
if len(opts) > 0 && opts[0] != nil {
|
||||
options = opts[0]
|
||||
} else {
|
||||
options = &context.Options{}
|
||||
}
|
||||
|
||||
// Execute hook with ctx, messages, and options (convert options to map for JS)
|
||||
optionsMap := options.ToMap()
|
||||
res, err := s.Execute(ctx, "Create", messages, optionsMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
response, err := s.getHookCreateResponse(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Apply context adjustments from the response back to the context
|
||||
// Apply adjustments from the response
|
||||
if response != nil {
|
||||
s.applyContextAdjustments(ctx, response)
|
||||
s.applyOptionsAdjustments(options, response)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
return response, options, nil
|
||||
}
|
||||
|
||||
// applyContextAdjustments applies context field overrides from the hook response back to the context
|
||||
// applyContextAdjustments applies session-level field overrides from the hook response back to the context
|
||||
func (s *Script) applyContextAdjustments(ctx *context.Context, response *context.HookCreateResponse) {
|
||||
// Override assistant ID if provided
|
||||
if response.AssistantID != "" {
|
||||
ctx.AssistantID = response.AssistantID
|
||||
}
|
||||
// Note: AssistantID cannot be overridden - it's set at initialization and immutable
|
||||
|
||||
// Override connector if provided
|
||||
if response.Connector != "" {
|
||||
ctx.Connector = response.Connector
|
||||
}
|
||||
|
||||
// Override locale if provided
|
||||
// Override locale if provided (session-level)
|
||||
if response.Locale != "" {
|
||||
ctx.Locale = response.Locale
|
||||
}
|
||||
|
||||
// Override theme if provided
|
||||
// Override theme if provided (session-level)
|
||||
if response.Theme != "" {
|
||||
ctx.Theme = response.Theme
|
||||
}
|
||||
|
||||
// Override route if provided
|
||||
// Override route if provided (session-level)
|
||||
if response.Route != "" {
|
||||
ctx.Route = response.Route
|
||||
}
|
||||
|
||||
// Merge or override metadata if provided
|
||||
// Merge or override metadata if provided (session-level)
|
||||
if len(response.Metadata) > 0 {
|
||||
if ctx.Metadata == nil {
|
||||
ctx.Metadata = make(map[string]interface{})
|
||||
|
|
@ -67,6 +71,14 @@ func (s *Script) applyContextAdjustments(ctx *context.Context, response *context
|
|||
}
|
||||
}
|
||||
|
||||
// applyOptionsAdjustments applies call-level field overrides from the hook response to options
|
||||
func (s *Script) applyOptionsAdjustments(opts *context.Options, response *context.HookCreateResponse) {
|
||||
// Override connector if provided (call-level parameter)
|
||||
if response.Connector != "" {
|
||||
opts.Connector = response.Connector
|
||||
}
|
||||
}
|
||||
|
||||
// getHookCreateResponse convert the result to a HookCreateResponse
|
||||
func (s *Script) getHookCreateResponse(res interface{}) (*context.HookCreateResponse, error) {
|
||||
// Handle nil result
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func BenchmarkSimpleStandardMode(b *testing.B) {
|
|||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctx := newBenchContext("bench-simple-standard", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -61,7 +61,7 @@ func BenchmarkSimplePerformanceMode(b *testing.B) {
|
|||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ctx := newBenchContext("bench-simple-performance", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -95,7 +95,7 @@ func BenchmarkBusinessStandardMode(b *testing.B) {
|
|||
for i := 0; i < b.N; i++ {
|
||||
scenario := scenarios[i%len(scenarios)]
|
||||
ctx := newBenchContext("bench-business-standard", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: scenario.content},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -125,7 +125,7 @@ func BenchmarkBusinessPerformanceMode(b *testing.B) {
|
|||
for i := 0; i < b.N; i++ {
|
||||
scenario := scenarios[i%len(scenarios)]
|
||||
ctx := newBenchContext("bench-business-performance", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: scenario.content},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -159,7 +159,7 @@ func BenchmarkConcurrentSimpleStandardMode(b *testing.B) {
|
|||
i := 0
|
||||
for pb.Next() {
|
||||
ctx := newBenchContext("bench-concurrent-simple-standard", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -191,7 +191,7 @@ func BenchmarkConcurrentSimplePerformanceMode(b *testing.B) {
|
|||
i := 0
|
||||
for pb.Next() {
|
||||
ctx := newBenchContext("bench-concurrent-simple", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -226,7 +226,7 @@ func BenchmarkConcurrentBusinessStandardMode(b *testing.B) {
|
|||
for pb.Next() {
|
||||
scenario := scenarios[i%len(scenarios)]
|
||||
ctx := newBenchContext("bench-concurrent-business-standard", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: scenario.content},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -261,7 +261,7 @@ func BenchmarkConcurrentBusinessPerformanceMode(b *testing.B) {
|
|||
for pb.Next() {
|
||||
scenario := scenarios[i%len(scenarios)]
|
||||
ctx := newBenchContext("bench-concurrent-business", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: scenario.content},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -301,7 +301,6 @@ func newBenchContext(chatID, assistantID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func TestMemoryLeakStandardMode(t *testing.T) {
|
|||
// Warm up - execute a few times to stabilize memory
|
||||
for i := 0; i < 10; i++ {
|
||||
ctx := newMemTestContext("warmup", "tests.create")
|
||||
_, _ = agent.Script.Create(ctx, []context.Message{
|
||||
_, _, _ = agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
ctx.Release()
|
||||
|
|
@ -52,7 +52,7 @@ func TestMemoryLeakStandardMode(t *testing.T) {
|
|||
iterations := 1000
|
||||
for i := 0; i < iterations; i++ {
|
||||
ctx := newMemTestContext("mem-test-standard", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -124,7 +124,7 @@ func TestMemoryLeakPerformanceMode(t *testing.T) {
|
|||
// Warm up - execute a few times to stabilize memory and fill isolate pool
|
||||
for i := 0; i < 20; i++ {
|
||||
ctx := newMemTestContext("warmup", "tests.create")
|
||||
_, _ = agent.Script.Create(ctx, []context.Message{
|
||||
_, _, _ = agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
ctx.Release()
|
||||
|
|
@ -140,7 +140,7 @@ func TestMemoryLeakPerformanceMode(t *testing.T) {
|
|||
iterations := 1000
|
||||
for i := 0; i < iterations; i++ {
|
||||
ctx := newMemTestContext("mem-test-performance", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -221,7 +221,7 @@ func TestMemoryLeakBusinessScenarios(t *testing.T) {
|
|||
// Warm up
|
||||
for i := 0; i < 10; i++ {
|
||||
ctx := newMemTestContext("warmup", "tests.create")
|
||||
_, _ = agent.Script.Create(ctx, []context.Message{
|
||||
_, _, _ = agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "return_full"},
|
||||
})
|
||||
ctx.Release()
|
||||
|
|
@ -240,7 +240,7 @@ func TestMemoryLeakBusinessScenarios(t *testing.T) {
|
|||
iterations := 200
|
||||
for i := 0; i < iterations; i++ {
|
||||
ctx := newMemTestContext("mem-test-business", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: scenario.content},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -298,7 +298,7 @@ func TestMemoryLeakConcurrent(t *testing.T) {
|
|||
// Warm up
|
||||
for i := 0; i < 20; i++ {
|
||||
ctx := newMemTestContext("warmup", "tests.create")
|
||||
_, _ = agent.Script.Create(ctx, []context.Message{
|
||||
_, _, _ = agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
ctx.Release()
|
||||
|
|
@ -321,7 +321,7 @@ func TestMemoryLeakConcurrent(t *testing.T) {
|
|||
defer func() { done <- true }()
|
||||
for i := 0; i < iterPerGoroutine; i++ {
|
||||
ctx := newMemTestContext("mem-test-concurrent", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -383,7 +383,7 @@ func TestMemoryLeakNestedCalls(t *testing.T) {
|
|||
// Warm up
|
||||
for i := 0; i < 10; i++ {
|
||||
ctx := newMemTestContext("warmup", "tests.create")
|
||||
_, _ = agent.Script.Create(ctx, []context.Message{
|
||||
_, _, _ = agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "nested_script_call"},
|
||||
})
|
||||
ctx.Release()
|
||||
|
|
@ -400,7 +400,7 @@ func TestMemoryLeakNestedCalls(t *testing.T) {
|
|||
iterations := 200
|
||||
for i := 0; i < iterations; i++ {
|
||||
ctx := newMemTestContext("mem-test-nested", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "deep_nested_call"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -459,7 +459,7 @@ func TestMemoryLeakNestedConcurrent(t *testing.T) {
|
|||
// Warm up
|
||||
for i := 0; i < 20; i++ {
|
||||
ctx := newMemTestContext("warmup", "tests.create")
|
||||
_, _ = agent.Script.Create(ctx, []context.Message{
|
||||
_, _, _ = agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "nested_script_call"},
|
||||
})
|
||||
ctx.Release()
|
||||
|
|
@ -482,7 +482,7 @@ func TestMemoryLeakNestedConcurrent(t *testing.T) {
|
|||
defer func() { done <- true }()
|
||||
for i := 0; i < iterPerGoroutine; i++ {
|
||||
ctx := newMemTestContext("mem-test-nested-concurrent", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "deep_nested_call"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -549,7 +549,7 @@ func TestIsolateDisposal(t *testing.T) {
|
|||
iterations := 100
|
||||
for i := 0; i < iterations; i++ {
|
||||
ctx := newMemTestContext("disposal-test", "tests.create")
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -615,7 +615,6 @@ func newMemTestContext(chatID, assistantID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ func TestNestedScriptCall(t *testing.T) {
|
|||
|
||||
// Call with deep_nested_call scenario
|
||||
// This will: hook -> scripts.tests.create.NestedCall -> GetRoles -> model
|
||||
res, err := agent.Script.Create(ctx, []context.Message{
|
||||
res, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "deep_nested_call"},
|
||||
})
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ func TestNestedScriptCallConcurrent(t *testing.T) {
|
|||
for j := 0; j < iterations; j++ {
|
||||
ctx := newTestContext("test-concurrent", "tests.create")
|
||||
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "deep_nested_call"},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ func newTestContext(chatID, assistantID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
@ -74,7 +73,7 @@ func TestCreate(t *testing.T) {
|
|||
|
||||
// Test scenario 1: Return null (should get nil response)
|
||||
t.Run("ReturnNull", func(t *testing.T) {
|
||||
res, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_null"}})
|
||||
res, _, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_null"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create with null return: %s", err.Error())
|
||||
}
|
||||
|
|
@ -85,7 +84,7 @@ func TestCreate(t *testing.T) {
|
|||
|
||||
// Test scenario 2: Return undefined (should get nil response)
|
||||
t.Run("ReturnUndefined", func(t *testing.T) {
|
||||
res, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_undefined"}})
|
||||
res, _, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_undefined"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create with undefined return: %s", err.Error())
|
||||
}
|
||||
|
|
@ -96,7 +95,7 @@ func TestCreate(t *testing.T) {
|
|||
|
||||
// Test scenario 3: Return empty object (should get empty HookCreateResponse)
|
||||
t.Run("ReturnEmpty", func(t *testing.T) {
|
||||
res, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_empty"}})
|
||||
res, _, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_empty"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create with empty return: %s", err.Error())
|
||||
}
|
||||
|
|
@ -110,7 +109,7 @@ func TestCreate(t *testing.T) {
|
|||
|
||||
// Test scenario 4: Return full response with all fields
|
||||
t.Run("ReturnFull", func(t *testing.T) {
|
||||
res, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_full"}})
|
||||
res, _, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_full"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create with full return: %s", err.Error())
|
||||
}
|
||||
|
|
@ -166,7 +165,7 @@ func TestCreate(t *testing.T) {
|
|||
|
||||
// Test scenario 5: Return partial response
|
||||
t.Run("ReturnPartial", func(t *testing.T) {
|
||||
res, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_partial"}})
|
||||
res, _, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_partial"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create with partial return: %s", err.Error())
|
||||
}
|
||||
|
|
@ -197,7 +196,7 @@ func TestCreate(t *testing.T) {
|
|||
|
||||
// Test scenario 6: Process call - calls models.__yao.role.Get and adds to messages
|
||||
t.Run("ReturnProcess", func(t *testing.T) {
|
||||
res, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_process"}})
|
||||
res, _, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "return_process"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create with process return: %s", err.Error())
|
||||
}
|
||||
|
|
@ -225,7 +224,7 @@ func TestCreate(t *testing.T) {
|
|||
// Test scenario 7: Default response
|
||||
t.Run("ReturnDefault", func(t *testing.T) {
|
||||
testContent := "Hello, how are you?"
|
||||
res, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: testContent}})
|
||||
res, _, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: testContent}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create with default return: %s", err.Error())
|
||||
}
|
||||
|
|
@ -252,7 +251,7 @@ func TestCreate(t *testing.T) {
|
|||
|
||||
// Test scenario 8: Verify context fields - validates all context fields in JavaScript
|
||||
t.Run("VerifyContext", func(t *testing.T) {
|
||||
res, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "verify_context"}})
|
||||
res, _, err := agent.Script.Create(ctx, []context.Message{{Role: "user", Content: "verify_context"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create with verify_context: %s", err.Error())
|
||||
}
|
||||
|
|
@ -304,7 +303,7 @@ func TestCreate(t *testing.T) {
|
|||
adjustCtx := newTestContext("chat-test-adjust", "tests.create")
|
||||
|
||||
// Call the hook which should adjust context fields
|
||||
res, err := agent.Script.Create(adjustCtx, []context.Message{{Role: "user", Content: "adjust_context"}})
|
||||
res, _, err := agent.Script.Create(adjustCtx, []context.Message{{Role: "user", Content: "adjust_context"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create with adjust_context: %s", err.Error())
|
||||
}
|
||||
|
|
@ -313,9 +312,7 @@ func TestCreate(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify the response contains adjusted fields
|
||||
if res.AssistantID != "adjusted.assistant" {
|
||||
t.Errorf("Expected adjusted assistant_id 'adjusted.assistant', got: %s", res.AssistantID)
|
||||
}
|
||||
// Note: AssistantID cannot be overridden by hooks, removed from HookCreateResponse
|
||||
if res.Connector != "adjusted-connector" {
|
||||
t.Errorf("Expected adjusted connector 'adjusted-connector', got: %s", res.Connector)
|
||||
}
|
||||
|
|
@ -338,12 +335,8 @@ func TestCreate(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify context fields were actually updated
|
||||
if adjustCtx.AssistantID != "adjusted.assistant" {
|
||||
t.Errorf("Context assistant_id not updated. Expected 'adjusted.assistant', got: %s", adjustCtx.AssistantID)
|
||||
}
|
||||
if adjustCtx.Connector != "adjusted-connector" {
|
||||
t.Errorf("Context connector not updated. Expected 'adjusted-connector', got: %s", adjustCtx.Connector)
|
||||
}
|
||||
// Note: AssistantID is immutable and cannot be overridden
|
||||
// Note: Connector is now in Options, not in Context
|
||||
if adjustCtx.Locale != "zh-cn" {
|
||||
t.Errorf("Context locale not updated. Expected 'zh-cn', got: %s", adjustCtx.Locale)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ func TestGoroutineLeakDetailed(t *testing.T) {
|
|||
for i := 0; i < iterations; i++ {
|
||||
ctx := newLeakTestContext(fmt.Sprintf("leak-test-%d", i), "tests.create")
|
||||
|
||||
_, err := agent.Script.Create(ctx, []context.Message{
|
||||
_, _, err := agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -126,7 +126,7 @@ func TestGoroutineLeakByComponent(t *testing.T) {
|
|||
|
||||
for i := 0; i < 10; i++ {
|
||||
ctx := newLeakTestContext(fmt.Sprintf("test-%d", i), "tests.create")
|
||||
_, _ = agent.Script.Create(ctx, []context.Message{
|
||||
_, _, _ = agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
ctx.Release()
|
||||
|
|
@ -184,7 +184,7 @@ func TestGoroutineLeakWithoutRelease(t *testing.T) {
|
|||
|
||||
for i := 0; i < 10; i++ {
|
||||
ctx := newLeakTestContext(fmt.Sprintf("no-release-%d", i), "tests.create")
|
||||
_, _ = agent.Script.Create(ctx, []context.Message{
|
||||
_, _, _ = agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
// Intentionally NOT calling ctx.Release()
|
||||
|
|
@ -205,7 +205,7 @@ func TestGoroutineLeakWithoutRelease(t *testing.T) {
|
|||
|
||||
for i := 0; i < 10; i++ {
|
||||
ctx := newLeakTestContext(fmt.Sprintf("with-release-%d", i), "tests.create")
|
||||
_, _ = agent.Script.Create(ctx, []context.Message{
|
||||
_, _, _ = agent.Script.Create(ctx, []context.Message{
|
||||
{Role: "user", Content: "Hello"},
|
||||
})
|
||||
ctx.Release() // WITH Release
|
||||
|
|
@ -299,7 +299,6 @@ func newLeakTestContext(chatID, assistantID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,16 @@ import (
|
|||
)
|
||||
|
||||
// Next next hook for the next action after the completion
|
||||
func (s *Script) Next(ctx *context.Context, payload *context.NextHookPayload) (*context.NextHookResponse, error) {
|
||||
// opts is optional - if provided, will be passed to the hook
|
||||
func (s *Script) Next(ctx *context.Context, payload *context.NextHookPayload, opts ...*context.Options) (*context.NextHookResponse, *context.Options, error) {
|
||||
// Get or create options
|
||||
var options *context.Options
|
||||
if len(opts) > 0 && opts[0] != nil {
|
||||
options = opts[0]
|
||||
} else {
|
||||
options = &context.Options{}
|
||||
}
|
||||
|
||||
// Convert payload to map for JS (use JSON tag names)
|
||||
payloadMap := map[string]interface{}{
|
||||
"messages": payload.Messages,
|
||||
|
|
@ -18,12 +27,19 @@ func (s *Script) Next(ctx *context.Context, payload *context.NextHookPayload) (*
|
|||
"error": payload.Error,
|
||||
}
|
||||
|
||||
res, err := s.Execute(ctx, "Next", payloadMap)
|
||||
// Execute hook with ctx, payload, and options (convert options to map for JS)
|
||||
optionsMap := options.ToMap()
|
||||
res, err := s.Execute(ctx, "Next", payloadMap, optionsMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return s.getNextHookResponse(res)
|
||||
response, err := s.getNextHookResponse(res)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return response, options, nil
|
||||
}
|
||||
|
||||
// getNextHookResponse convert the result to a NextHookResponse
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ func newTestContextForNext(chatID, assistantID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
@ -86,7 +85,7 @@ func TestNext(t *testing.T) {
|
|||
Error: "",
|
||||
}
|
||||
|
||||
res, err := agent.Script.Next(ctx, payload)
|
||||
res, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute Next hook with null return: %s", err.Error())
|
||||
}
|
||||
|
|
@ -106,7 +105,7 @@ func TestNext(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
res, err := agent.Script.Next(ctx, payload)
|
||||
res, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute Next hook with undefined return: %s", err.Error())
|
||||
}
|
||||
|
|
@ -126,7 +125,7 @@ func TestNext(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
res, err := agent.Script.Next(ctx, payload)
|
||||
res, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute Next hook with empty return: %s", err.Error())
|
||||
}
|
||||
|
|
@ -152,7 +151,7 @@ func TestNext(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
res, err := agent.Script.Next(ctx, payload)
|
||||
res, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute Next hook with custom data: %s", err.Error())
|
||||
}
|
||||
|
|
@ -199,7 +198,7 @@ func TestNext(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
res, err := agent.Script.Next(ctx, payload)
|
||||
res, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute Next hook: %s", err.Error())
|
||||
}
|
||||
|
|
@ -245,7 +244,7 @@ func TestNext(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
res, err := agent.Script.Next(ctx, payload)
|
||||
res, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute Next hook with delegate: %s", err.Error())
|
||||
}
|
||||
|
|
@ -307,7 +306,7 @@ func TestNext(t *testing.T) {
|
|||
Error: "",
|
||||
}
|
||||
|
||||
res, err := agent.Script.Next(ctx, payload)
|
||||
res, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute Next hook: %s", err.Error())
|
||||
}
|
||||
|
|
@ -366,7 +365,7 @@ func TestNext(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
res, err := agent.Script.Next(ctx, payload)
|
||||
res, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute Next hook: %s", err.Error())
|
||||
}
|
||||
|
|
@ -410,7 +409,7 @@ func TestNext(t *testing.T) {
|
|||
Error: "Tool execution failed: timeout",
|
||||
}
|
||||
|
||||
res, err := agent.Script.Next(ctx, payload)
|
||||
res, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to execute Next hook: %s", err.Error())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ func newRealWorldNextContext(chatID, assistantID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
@ -76,7 +75,7 @@ func TestRealWorldNextStandard(t *testing.T) {
|
|||
Error: "",
|
||||
}
|
||||
|
||||
response, err := agent.Script.Next(ctx, payload)
|
||||
response, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Next hook failed: %v", err)
|
||||
}
|
||||
|
|
@ -118,7 +117,7 @@ func TestRealWorldNextCustomData(t *testing.T) {
|
|||
Error: "",
|
||||
}
|
||||
|
||||
response, err := agent.Script.Next(ctx, payload)
|
||||
response, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Next hook failed: %v", err)
|
||||
}
|
||||
|
|
@ -165,7 +164,7 @@ func TestRealWorldNextDelegate(t *testing.T) {
|
|||
Error: "",
|
||||
}
|
||||
|
||||
response, err := agent.Script.Next(ctx, payload)
|
||||
response, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Next hook failed: %v", err)
|
||||
}
|
||||
|
|
@ -227,7 +226,7 @@ func TestRealWorldNextProcessTools(t *testing.T) {
|
|||
Error: "",
|
||||
}
|
||||
|
||||
response, err := agent.Script.Next(ctx, payload)
|
||||
response, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Next hook failed: %v", err)
|
||||
}
|
||||
|
|
@ -280,7 +279,7 @@ func TestRealWorldNextErrorRecovery(t *testing.T) {
|
|||
Error: "System error: Database connection timeout",
|
||||
}
|
||||
|
||||
response, err := agent.Script.Next(ctx, payload)
|
||||
response, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Next hook failed: %v", err)
|
||||
}
|
||||
|
|
@ -329,7 +328,7 @@ func TestRealWorldNextConditional(t *testing.T) {
|
|||
Error: "",
|
||||
}
|
||||
|
||||
response, err := agent.Script.Next(ctx, payload)
|
||||
response, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Next hook failed: %v", err)
|
||||
}
|
||||
|
|
@ -362,7 +361,7 @@ func TestRealWorldNextConditional(t *testing.T) {
|
|||
Error: "",
|
||||
}
|
||||
|
||||
response, err := agent.Script.Next(ctx, payload)
|
||||
response, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Next hook failed: %v", err)
|
||||
}
|
||||
|
|
@ -406,7 +405,7 @@ func TestRealWorldNextDefault(t *testing.T) {
|
|||
Error: "",
|
||||
}
|
||||
|
||||
response, err := agent.Script.Next(ctx, payload)
|
||||
response, _, err := agent.Script.Next(ctx, payload)
|
||||
if err != nil {
|
||||
t.Fatalf("Next hook failed: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func TestRealWorldSimpleScenario(t *testing.T) {
|
|||
{Role: "user", Content: "simple"},
|
||||
}
|
||||
|
||||
response, err := agent.Script.Create(ctx, messages)
|
||||
response, _, err := agent.Script.Create(ctx, messages)
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ func TestRealWorldMCPScenarios(t *testing.T) {
|
|||
{Role: "user", Content: "mcp_health"},
|
||||
}
|
||||
|
||||
response, err := agent.Script.Create(ctx, messages)
|
||||
response, _, err := agent.Script.Create(ctx, messages)
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
|
|
@ -117,7 +117,7 @@ func TestRealWorldMCPScenarios(t *testing.T) {
|
|||
{Role: "user", Content: "mcp_tools"},
|
||||
}
|
||||
|
||||
response, err := agent.Script.Create(ctx, messages)
|
||||
response, _, err := agent.Script.Create(ctx, messages)
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
|
|
@ -162,7 +162,7 @@ func TestRealWorldMCPScenarios(t *testing.T) {
|
|||
ctx := newRealWorldContext("test-full-workflow", "tests.realworld")
|
||||
|
||||
// Initialize stack for trace
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", context.RefererAPI)
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", &context.Options{})
|
||||
defer done()
|
||||
ctx.Stack = stack
|
||||
|
||||
|
|
@ -170,7 +170,7 @@ func TestRealWorldMCPScenarios(t *testing.T) {
|
|||
{Role: "user", Content: "full_workflow"},
|
||||
}
|
||||
|
||||
response, err := agent.Script.Create(ctx, messages)
|
||||
response, _, err := agent.Script.Create(ctx, messages)
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
|
|
@ -237,7 +237,7 @@ func TestRealWorldTraceIntensive(t *testing.T) {
|
|||
}
|
||||
|
||||
ctx := newRealWorldContext("test-trace-intensive", "tests.realworld")
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", context.RefererAPI)
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", &context.Options{})
|
||||
defer done()
|
||||
ctx.Stack = stack
|
||||
|
||||
|
|
@ -245,7 +245,7 @@ func TestRealWorldTraceIntensive(t *testing.T) {
|
|||
{Role: "user", Content: "trace_intensive"},
|
||||
}
|
||||
|
||||
response, err := agent.Script.Create(ctx, messages)
|
||||
response, _, err := agent.Script.Create(ctx, messages)
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
|
|
@ -279,7 +279,7 @@ func TestRealWorldStressSimple(t *testing.T) {
|
|||
{Role: "user", Content: "simple"},
|
||||
}
|
||||
|
||||
response, err := agent.Script.Create(ctx, messages)
|
||||
response, _, err := agent.Script.Create(ctx, messages)
|
||||
if err != nil {
|
||||
t.Fatalf("Iteration %d failed: %v", i, err)
|
||||
}
|
||||
|
|
@ -338,14 +338,14 @@ func TestRealWorldStressMCP(t *testing.T) {
|
|||
ctx := newRealWorldContext(fmt.Sprintf("stress-mcp-%d", i), "tests.realworld")
|
||||
|
||||
// Initialize stack for trace
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", context.RefererAPI)
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
messages := []context.Message{
|
||||
{Role: "user", Content: scenario},
|
||||
}
|
||||
|
||||
response, err := agent.Script.Create(ctx, messages)
|
||||
response, _, err := agent.Script.Create(ctx, messages)
|
||||
if err != nil {
|
||||
t.Fatalf("Iteration %d (%s) failed: %v", i, scenario, err)
|
||||
}
|
||||
|
|
@ -427,14 +427,14 @@ func TestRealWorldStressFullWorkflow(t *testing.T) {
|
|||
ctx := newRealWorldContext(fmt.Sprintf("stress-workflow-%d", i), "tests.realworld")
|
||||
|
||||
// Initialize stack for trace
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", context.RefererAPI)
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
messages := []context.Message{
|
||||
{Role: "user", Content: "full_workflow"},
|
||||
}
|
||||
|
||||
response, err := agent.Script.Create(ctx, messages)
|
||||
response, _, err := agent.Script.Create(ctx, messages)
|
||||
if err != nil {
|
||||
t.Fatalf("Iteration %d failed: %v", i, err)
|
||||
}
|
||||
|
|
@ -531,14 +531,14 @@ func TestRealWorldStressConcurrent(t *testing.T) {
|
|||
)
|
||||
|
||||
// Initialize stack for trace
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", context.RefererAPI)
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
messages := []context.Message{
|
||||
{Role: "user", Content: scenario},
|
||||
}
|
||||
|
||||
response, err := agent.Script.Create(ctx, messages)
|
||||
response, _, err := agent.Script.Create(ctx, messages)
|
||||
if err != nil {
|
||||
errors <- fmt.Errorf("goroutine %d iteration %d (%s): %v", goroutineID, i, scenario, err)
|
||||
done()
|
||||
|
|
@ -658,14 +658,14 @@ func TestRealWorldStressResourceHeavy(t *testing.T) {
|
|||
ctx := newRealWorldContext(fmt.Sprintf("stress-heavy-%d", i), "tests.realworld")
|
||||
|
||||
// Initialize stack for trace
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", context.RefererAPI)
|
||||
stack, _, done := context.EnterStack(ctx, "tests.realworld", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
messages := []context.Message{
|
||||
{Role: "user", Content: "resource_heavy"},
|
||||
}
|
||||
|
||||
response, err := agent.Script.Create(ctx, messages)
|
||||
response, _, err := agent.Script.Create(ctx, messages)
|
||||
if err != nil {
|
||||
t.Fatalf("Iteration %d failed: %v", i, err)
|
||||
}
|
||||
|
|
@ -726,7 +726,6 @@ func newRealWorldContext(chatID, assistantID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "gpt-4o",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -180,7 +180,6 @@ func newStoreTestContext(chatID, assistantID string) *context.Context {
|
|||
Context: stdContext.Background(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
@ -269,7 +268,7 @@ function Create(ctx: any, messages: any[]): any {
|
|||
ctx := newStoreTestContext("test-chat-id", assistantID)
|
||||
messages := []context.Message{{Role: "user", Content: "Hello"}}
|
||||
|
||||
res, err := loaded.Script.Create(ctx, messages)
|
||||
res, _, err := loaded.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err, "Create hook should execute without error")
|
||||
require.NotNil(t, res, "Create hook should return a response")
|
||||
|
||||
|
|
@ -576,7 +575,7 @@ function Create(ctx: any, messages: any[]): any {
|
|||
ctx := newStoreTestContext("test-chat-all-fields", assistantID)
|
||||
messages := []context.Message{{Role: "user", Content: "Test message"}}
|
||||
|
||||
res, err := loaded.Script.Create(ctx, messages)
|
||||
res, _, err := loaded.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err, "Create hook should execute without error")
|
||||
require.NotNil(t, res, "Create hook should return a response")
|
||||
|
||||
|
|
@ -691,7 +690,7 @@ function Create(ctx: CreateContext, messages: Message[]): CreateResponse | null
|
|||
{Role: "user", Content: "How are you?"},
|
||||
}
|
||||
|
||||
res, err := loaded.Script.Create(ctx, messages)
|
||||
res, _, err := loaded.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err, "TypeScript Create hook should execute without error")
|
||||
require.NotNil(t, res, "Create hook should return a response")
|
||||
|
||||
|
|
@ -759,7 +758,7 @@ function Create(ctx: any, messages: any[]): any {
|
|||
ctx := newStoreTestContext("null-test-chat", assistantID)
|
||||
messages := []context.Message{{Role: "user", Content: "Hello"}}
|
||||
|
||||
res, err := loaded.Script.Create(ctx, messages)
|
||||
res, _, err := loaded.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err, "Hook returning null should not error")
|
||||
assert.Nil(t, res, "Hook returning null should return nil response")
|
||||
}
|
||||
|
|
@ -832,7 +831,7 @@ function Create(ctx: any, messages: any[]): any {
|
|||
ctx := newStoreTestContext("preset-test-1", assistantID)
|
||||
messages := []context.Message{{Role: "user", Content: "Be friendly please"}}
|
||||
|
||||
res, err := loaded.Script.Create(ctx, messages)
|
||||
res, _, err := loaded.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
assert.Equal(t, "friendly", res.PromptPreset)
|
||||
|
|
@ -843,7 +842,7 @@ function Create(ctx: any, messages: any[]): any {
|
|||
ctx := newStoreTestContext("preset-test-2", assistantID)
|
||||
messages := []context.Message{{Role: "user", Content: "Be professional"}}
|
||||
|
||||
res, err := loaded.Script.Create(ctx, messages)
|
||||
res, _, err := loaded.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
assert.Equal(t, "professional", res.PromptPreset)
|
||||
|
|
@ -854,7 +853,7 @@ function Create(ctx: any, messages: any[]): any {
|
|||
ctx := newStoreTestContext("preset-test-3", assistantID)
|
||||
messages := []context.Message{{Role: "user", Content: "Hello"}}
|
||||
|
||||
res, err := loaded.Script.Create(ctx, messages)
|
||||
res, _, err := loaded.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, res)
|
||||
})
|
||||
|
|
@ -916,7 +915,7 @@ function Create(ctx: any, messages: any[]): any {
|
|||
ctx := newStoreTestContext("disable-test-1", assistantID)
|
||||
messages := []context.Message{{Role: "user", Content: "disable_global prompts"}}
|
||||
|
||||
res, err := loaded.Script.Create(ctx, messages)
|
||||
res, _, err := loaded.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
require.NotNil(t, res.DisableGlobalPrompts)
|
||||
|
|
@ -928,7 +927,7 @@ function Create(ctx: any, messages: any[]): any {
|
|||
ctx := newStoreTestContext("disable-test-2", assistantID)
|
||||
messages := []context.Message{{Role: "user", Content: "enable_global prompts"}}
|
||||
|
||||
res, err := loaded.Script.Create(ctx, messages)
|
||||
res, _, err := loaded.Script.Create(ctx, messages, &context.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res)
|
||||
require.NotNil(t, res.DisableGlobalPrompts)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,10 @@ func (ast *Assistant) handleDelegation(
|
|||
// 2. Execute with the same Context (preserving ID, Space, Writer, etc.)
|
||||
// 3. Call done() to pop from Stack when finished
|
||||
// This ensures proper Stack tracing: parent assistant -> delegated assistant
|
||||
return targetAssistant.Stream(ctx, delegate.Messages, streamHandler)
|
||||
|
||||
// Convert options map from delegate config to Options struct
|
||||
delegateOpts := agentContext.OptionsFromMap(delegate.Options)
|
||||
return targetAssistant.Stream(ctx, delegate.Messages, delegateOpts)
|
||||
}
|
||||
|
||||
// buildStandardResponse builds the standard agent response when no custom Next hook processing is needed
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ func newTestContext(capabilities *openai.Capabilities) *agentContext.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: "test-chat",
|
||||
AssistantID: "test-assistant",
|
||||
Connector: "openai",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: agentContext.Client{
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
// AgentCaller interface for calling agents (to avoid circular dependency)
|
||||
type AgentCaller interface {
|
||||
Stream(ctx *agentContext.Context, messages []agentContext.Message) (interface{}, error)
|
||||
Stream(ctx *agentContext.Context, messages []agentContext.Message, options ...*agentContext.Options) (interface{}, error)
|
||||
}
|
||||
|
||||
// AgentGetterFunc is a function type that gets an agent by ID
|
||||
|
|
@ -35,12 +35,10 @@ func CallAgent(ctx *agentContext.Context, agentID string, message agentContext.M
|
|||
// Call the agent with the message
|
||||
messages := []agentContext.Message{message}
|
||||
|
||||
connectorBackup := ctx.Connector
|
||||
ctx.Connector = ""
|
||||
defer func() {
|
||||
ctx.Connector = connectorBackup
|
||||
}()
|
||||
response, err := agent.Stream(ctx, messages)
|
||||
// Note: Connector is now in Options (call-level parameter), not Context
|
||||
// For A2A calls, we use an empty Connector to let the agent use its default
|
||||
opts := &agentContext.Options{Skip: &agentContext.Skip{History: true}, Writer: nil} // Skip history and output to the caller
|
||||
response, err := agent.Stream(ctx, messages, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to call agent %s: %w", agentID, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,23 +259,6 @@ func (ctx *Context) Map() map[string]interface{} {
|
|||
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 != "" {
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ func TestGetCompletionRequest(t *testing.T) {
|
|||
c.Request = req
|
||||
|
||||
// Call GetCompletionRequest
|
||||
completionReq, ctx, err := GetCompletionRequest(c, cache)
|
||||
completionReq, ctx, opts, err := GetCompletionRequest(c, cache)
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
|
|
@ -189,6 +189,7 @@ func TestGetCompletionRequest(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NotNil(t, completionReq)
|
||||
assert.NotNil(t, ctx)
|
||||
assert.NotNil(t, opts)
|
||||
|
||||
// Verify CompletionRequest
|
||||
assert.Equal(t, tt.expectedModel, completionReq.Model)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ func newTestContextWithInterrupt(chatID, assistantID string) *Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: assistantID,
|
||||
Connector: "",
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: Client{
|
||||
|
|
|
|||
|
|
@ -36,13 +36,6 @@ func (ctx *Context) NewObject(v8ctx *v8go.Context) (*v8go.Value, error) {
|
|||
// Set primitive fields in template
|
||||
jsObject.Set("chat_id", ctx.ChatID)
|
||||
jsObject.Set("assistant_id", ctx.AssistantID)
|
||||
jsObject.Set("connector", ctx.Connector)
|
||||
if ctx.Search != nil {
|
||||
jsObject.Set("search", *ctx.Search)
|
||||
}
|
||||
|
||||
jsObject.Set("retry", ctx.Retry)
|
||||
jsObject.Set("retry_times", uint32(ctx.RetryTimes))
|
||||
jsObject.Set("locale", ctx.Locale)
|
||||
jsObject.Set("theme", ctx.Theme)
|
||||
jsObject.Set("referer", ctx.Referer)
|
||||
|
|
@ -97,15 +90,6 @@ func (ctx *Context) NewObject(v8ctx *v8go.Context) (*v8go.Value, error) {
|
|||
}
|
||||
|
||||
// Set complex objects (maps, arrays) after instance creation using bridge
|
||||
// Args array
|
||||
if ctx.Args != nil {
|
||||
argsVal, err := bridge.JsValue(v8ctx, ctx.Args)
|
||||
if err == nil {
|
||||
obj.Set("args", argsVal)
|
||||
argsVal.Release() // Release Go-side Persistent handle, V8 internal reference remains
|
||||
}
|
||||
}
|
||||
|
||||
// Client object
|
||||
clientData := map[string]interface{}{
|
||||
"type": ctx.Client.Type,
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ func TestMCPListResources(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -66,8 +67,9 @@ func TestMCPReadResource(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -108,8 +110,9 @@ func TestMCPListTools(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -154,8 +157,9 @@ func TestMCPCallTool(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -196,8 +200,9 @@ func TestMCPCallTools(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -243,8 +248,9 @@ func TestMCPCallToolsParallel(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -290,8 +296,9 @@ func TestMCPListPrompts(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -334,8 +341,9 @@ func TestMCPGetPrompt(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -376,8 +384,9 @@ func TestMCPListSamples(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -418,8 +427,9 @@ func TestMCPGetSample(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -462,8 +472,9 @@ func TestMCPJsApiWithTrace(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Locale: "en",
|
||||
Context: stdContext.Background(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
|
|||
|
|
@ -22,10 +22,11 @@ func TestContextRelease(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
// Initialize stack and trace
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -75,10 +76,11 @@ func TestTraceRelease(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
// Initialize stack and trace
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -137,10 +139,11 @@ func TestContextReleaseWithTrace(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
// Initialize stack and trace
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -185,10 +188,11 @@ func TestTryFinallyPattern(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
// Initialize stack and trace
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
res, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -287,10 +291,11 @@ func TestTryFinallyPatternWithError(t *testing.T) {
|
|||
AssistantID: "test-assistant-id",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
// Initialize stack and trace
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
_, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@ func TestStressContextCreationAndRelease(t *testing.T) {
|
|||
}
|
||||
|
||||
// Initialize stack and trace
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
cxt.Referer = context.RefererAPI
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
_, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -111,10 +112,11 @@ func TestStressTraceOperations(t *testing.T) {
|
|||
AssistantID: "test-assistant",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
// Initialize stack and trace
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
_, err := v8.Call(v8.CallOptions{}, fmt.Sprintf(`
|
||||
function test(ctx) {
|
||||
|
|
@ -188,9 +190,10 @@ func TestStressMCPOperations(t *testing.T) {
|
|||
AssistantID: "test-assistant",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
startMemory := getMemStats()
|
||||
|
|
@ -271,9 +274,10 @@ func TestStressConcurrentContexts(t *testing.T) {
|
|||
AssistantID: "test-assistant",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
_, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -421,9 +425,10 @@ func TestStressReleasePatterns(t *testing.T) {
|
|||
AssistantID: "test-assistant",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
_, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -459,9 +464,10 @@ func TestStressReleasePatterns(t *testing.T) {
|
|||
AssistantID: "test-assistant",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
_, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -499,9 +505,10 @@ func TestStressReleasePatterns(t *testing.T) {
|
|||
AssistantID: "test-assistant",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
_, err := v8.Call(v8.CallOptions{}, `
|
||||
|
|
@ -544,9 +551,10 @@ func TestStressLongRunningTrace(t *testing.T) {
|
|||
AssistantID: "test-assistant",
|
||||
Context: stdContext.Background(),
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", context.RefererAPI)
|
||||
stack, _, _ := context.EnterStack(cxt, "test-assistant", &context.Options{})
|
||||
cxt.Stack = stack
|
||||
|
||||
startMemory := getMemStats()
|
||||
|
|
|
|||
|
|
@ -219,15 +219,9 @@ func TestJsValueAllFields(t *testing.T) {
|
|||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
searchTrue := true
|
||||
cxt := &context.Context{
|
||||
ChatID: "test-chat-id",
|
||||
AssistantID: "test-assistant-id",
|
||||
Connector: "test-connector",
|
||||
Search: &searchTrue,
|
||||
Args: []interface{}{"arg1", "arg2", 123},
|
||||
Retry: true,
|
||||
RetryTimes: 3,
|
||||
Locale: "zh-cn",
|
||||
Theme: "dark",
|
||||
Context: stdContext.Background(),
|
||||
|
|
@ -279,21 +273,12 @@ func TestJsValueAllFields(t *testing.T) {
|
|||
// Verify all fields
|
||||
assert.Equal(t, "test-chat-id", result["chat_id"], "chat_id mismatch")
|
||||
assert.Equal(t, "test-assistant-id", result["assistant_id"], "assistant_id mismatch")
|
||||
assert.Equal(t, "test-connector", result["connector"], "connector mismatch")
|
||||
assert.Equal(t, true, result["search"], "search mismatch")
|
||||
assert.Equal(t, true, result["retry"], "retry mismatch")
|
||||
assert.Equal(t, float64(3), result["retry_times"], "retry_times mismatch")
|
||||
assert.Equal(t, "zh-cn", result["locale"], "locale mismatch")
|
||||
assert.Equal(t, "dark", result["theme"], "theme mismatch")
|
||||
assert.Equal(t, "api", result["referer"], "referer mismatch")
|
||||
assert.Equal(t, "cui-web", result["accept"], "accept mismatch")
|
||||
assert.Equal(t, "/dashboard/home", result["route"], "route mismatch")
|
||||
|
||||
// Verify args array
|
||||
args, ok := result["args"].([]interface{})
|
||||
assert.True(t, ok, "args should be an array")
|
||||
assert.Equal(t, 3, len(args), "args length mismatch")
|
||||
|
||||
// Verify client object
|
||||
client, ok := result["client"].(map[string]interface{})
|
||||
assert.True(t, ok, "client should be an object")
|
||||
|
|
@ -373,21 +358,6 @@ func testAllFieldsFunction(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
|||
if val, ok := getField("assistant_id"); ok {
|
||||
result["assistant_id"] = val
|
||||
}
|
||||
if val, ok := getField("connector"); ok {
|
||||
result["connector"] = val
|
||||
}
|
||||
if val, ok := getField("search"); ok {
|
||||
result["search"] = val
|
||||
}
|
||||
if val, ok := getField("args"); ok {
|
||||
result["args"] = val
|
||||
}
|
||||
if val, ok := getField("retry"); ok {
|
||||
result["retry"] = val
|
||||
}
|
||||
if val, ok := getField("retry_times"); ok {
|
||||
result["retry_times"] = val
|
||||
}
|
||||
if val, ok := getField("locale"); ok {
|
||||
result["locale"] = val
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,11 @@ func newTestMCPContext() *context.Context {
|
|||
ChatID: "test-chat",
|
||||
AssistantID: "test-assistant",
|
||||
Locale: "en",
|
||||
Referer: context.RefererAPI,
|
||||
}
|
||||
|
||||
// Initialize stack and trace
|
||||
stack, traceID, _ := context.EnterStack(ctx, "test-assistant", context.RefererAPI)
|
||||
stack, traceID, _ := context.EnterStack(ctx, "test-assistant", &context.Options{})
|
||||
ctx.Stack = stack
|
||||
_ = traceID // traceID is set in stack
|
||||
|
||||
|
|
|
|||
|
|
@ -15,21 +15,21 @@ import (
|
|||
)
|
||||
|
||||
// GetCompletionRequest parse completion request and create context from openapi request
|
||||
// Returns: *CompletionRequest, *Context, error
|
||||
func GetCompletionRequest(c *gin.Context, cache store.Store) (*CompletionRequest, *Context, error) {
|
||||
// Returns: *CompletionRequest, *Context, *Options, error
|
||||
func GetCompletionRequest(c *gin.Context, cache store.Store) (*CompletionRequest, *Context, *Options, error) {
|
||||
// Get authorized information
|
||||
authInfo := authorized.GetInfo(c)
|
||||
|
||||
// Parse completion request from payload or query first
|
||||
completionReq, err := parseCompletionRequestData(c)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse completion request: %w", err)
|
||||
return nil, nil, nil, fmt.Errorf("failed to parse completion request: %w", err)
|
||||
}
|
||||
|
||||
// Extract assistant ID using completionReq (can extract from model field)
|
||||
assistantID, err := GetAssistantID(c, completionReq)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get assistant ID: %w", err)
|
||||
return nil, nil, nil, fmt.Errorf("failed to get assistant ID: %w", err)
|
||||
}
|
||||
|
||||
// Extract chat ID (may generate from messages if not provided)
|
||||
|
|
@ -47,26 +47,10 @@ func GetCompletionRequest(c *gin.Context, cache store.Store) (*CompletionRequest
|
|||
// Create context with unique ID using New() to ensure proper initialization
|
||||
ctx := New(c.Request.Context(), authInfo, chatID)
|
||||
|
||||
// Set additional fields
|
||||
// Set context fields (session-level state)
|
||||
ctx.Cache = cache
|
||||
ctx.Writer = c.Writer
|
||||
ctx.AssistantID = assistantID
|
||||
|
||||
// Try to extract custom connector from model field
|
||||
// If model is a valid connector ID, set it to ctx.Connector
|
||||
// Otherwise, keep the standard OpenAI-compatible behavior (model as assistant ID)
|
||||
if completionReq != nil && completionReq.Model != "" {
|
||||
// Check if model is a valid connector (not containing "-yao_" which indicates assistant ID format)
|
||||
if !strings.Contains(completionReq.Model, "-yao_") {
|
||||
// Try to validate if it's a real connector
|
||||
if _, err := connector.Select(completionReq.Model); err == nil {
|
||||
// It's a valid connector, use it
|
||||
ctx.Connector = completionReq.Model
|
||||
}
|
||||
// If not a valid connector, ignore it (keep ctx.Connector empty to use assistant's default)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Locale = GetLocale(c, completionReq)
|
||||
ctx.Theme = GetTheme(c, completionReq)
|
||||
ctx.Referer = GetReferer(c, completionReq)
|
||||
|
|
@ -78,14 +62,34 @@ func GetCompletionRequest(c *gin.Context, cache store.Store) (*CompletionRequest
|
|||
}
|
||||
ctx.Route = GetRoute(c, completionReq)
|
||||
ctx.Metadata = GetMetadata(c, completionReq)
|
||||
ctx.Skip = GetSkip(c, completionReq)
|
||||
|
||||
// Create Options (call-level parameters)
|
||||
opts := &Options{
|
||||
Context: c.Request.Context(),
|
||||
Skip: GetSkip(c, completionReq),
|
||||
}
|
||||
|
||||
// Try to extract custom connector from model field
|
||||
// If model is a valid connector ID, set it to opts.Connector
|
||||
// Otherwise, keep the standard OpenAI-compatible behavior (model as assistant ID)
|
||||
if completionReq != nil && completionReq.Model != "" {
|
||||
// Check if model is a valid connector (not containing "-yao_" which indicates assistant ID format)
|
||||
if !strings.Contains(completionReq.Model, "-yao_") {
|
||||
// Try to validate if it's a real connector
|
||||
if _, err := connector.Select(completionReq.Model); err == nil {
|
||||
// It's a valid connector, use it
|
||||
opts.Connector = completionReq.Model
|
||||
}
|
||||
// If not a valid connector, ignore it (keep opts.Connector empty to use assistant's default)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize interrupt controller
|
||||
ctx.Interrupt = NewInterruptController()
|
||||
|
||||
// Register context to global registry first (required for interrupt handler callback)
|
||||
if err := Register(ctx); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to register context: %w", err)
|
||||
return nil, nil, nil, fmt.Errorf("failed to register context: %w", err)
|
||||
}
|
||||
|
||||
// Start interrupt listener after registration
|
||||
|
|
@ -93,7 +97,7 @@ func GetCompletionRequest(c *gin.Context, cache store.Store) (*CompletionRequest
|
|||
// HTTP context cancellation is handled by LLM/Agent layers naturally
|
||||
ctx.Interrupt.Start(ctx.ID)
|
||||
|
||||
return completionReq, ctx, nil
|
||||
return completionReq, ctx, opts, nil
|
||||
}
|
||||
|
||||
// getClientType parses the client type from User-Agent header
|
||||
|
|
|
|||
|
|
@ -851,7 +851,7 @@ func TestGetCompletionRequest_WriterInitialized(t *testing.T) {
|
|||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
|
||||
completionReq, ctx, err := GetCompletionRequest(c, cache)
|
||||
completionReq, ctx, opts, err := GetCompletionRequest(c, cache)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get completion request: %v", err)
|
||||
}
|
||||
|
|
@ -867,6 +867,11 @@ func TestGetCompletionRequest_WriterInitialized(t *testing.T) {
|
|||
t.Error("Expected ctx.Writer to be the same as gin context writer")
|
||||
}
|
||||
|
||||
// Check that Options is initialized
|
||||
if opts == nil {
|
||||
t.Error("Expected opts to be initialized, got nil")
|
||||
}
|
||||
|
||||
// Check other fields
|
||||
if completionReq.Model != "gpt-4-yao_test" {
|
||||
t.Errorf("Expected model 'gpt-4-yao_test', got '%s'", completionReq.Model)
|
||||
|
|
@ -914,12 +919,17 @@ func TestGetCompletionRequest_ChatIDFallback(t *testing.T) {
|
|||
c, _ := gin.CreateTestContext(w)
|
||||
c.Request = req
|
||||
|
||||
_, ctx, err := GetCompletionRequest(c, cache)
|
||||
_, ctx, opts, err := GetCompletionRequest(c, cache)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get completion request: %v", err)
|
||||
}
|
||||
defer ctx.Release()
|
||||
|
||||
// Check that Options is initialized
|
||||
if opts == nil {
|
||||
t.Error("Expected opts to be initialized, got nil")
|
||||
}
|
||||
|
||||
// ChatID should be generated (not empty)
|
||||
if ctx.ChatID == "" {
|
||||
t.Error("Expected ChatID to be generated via fallback, got empty string")
|
||||
|
|
|
|||
71
agent/context/options.go
Normal file
71
agent/context/options.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package context
|
||||
|
||||
// ToMap converts Options struct to map for JSON serialization
|
||||
func (opts *Options) ToMap() map[string]interface{} {
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
|
||||
// Add configurable fields (with json tags)
|
||||
if opts.Connector != "" {
|
||||
result["connector"] = opts.Connector
|
||||
}
|
||||
if opts.Mode != "" {
|
||||
result["mode"] = opts.Mode
|
||||
}
|
||||
if opts.Search != nil {
|
||||
result["search"] = *opts.Search
|
||||
}
|
||||
if opts.Skip != nil {
|
||||
result["skip"] = opts.Skip
|
||||
}
|
||||
// Only add DisableGlobalPrompts if true (avoid false values in map)
|
||||
if opts.DisableGlobalPrompts {
|
||||
result["disable_global_prompts"] = opts.DisableGlobalPrompts
|
||||
}
|
||||
|
||||
// Note: Runtime fields (Context, Writer) are not serialized (json:"-")
|
||||
// They should not be included in the map
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// OptionsFromMap creates Options struct from map (e.g., from JS Hook)
|
||||
func OptionsFromMap(m map[string]interface{}) *Options {
|
||||
if m == nil {
|
||||
return &Options{}
|
||||
}
|
||||
|
||||
opts := &Options{}
|
||||
|
||||
// Extract configurable fields
|
||||
if connector, ok := m["connector"].(string); ok {
|
||||
opts.Connector = connector
|
||||
}
|
||||
if mode, ok := m["mode"].(string); ok {
|
||||
opts.Mode = mode
|
||||
}
|
||||
if search, ok := m["search"].(bool); ok {
|
||||
opts.Search = &search
|
||||
}
|
||||
if skipMap, ok := m["skip"].(map[string]interface{}); ok {
|
||||
skip := &Skip{}
|
||||
if history, ok := skipMap["history"].(bool); ok {
|
||||
skip.History = history
|
||||
}
|
||||
if trace, ok := skipMap["trace"].(bool); ok {
|
||||
skip.Trace = trace
|
||||
}
|
||||
opts.Skip = skip
|
||||
}
|
||||
if disableGlobalPrompts, ok := m["disable_global_prompts"].(bool); ok {
|
||||
opts.DisableGlobalPrompts = disableGlobalPrompts
|
||||
}
|
||||
|
||||
// Note: Context and Writer are runtime fields, not restored from map
|
||||
// They should be set by the caller if needed
|
||||
|
||||
return opts
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
// NewStack creates a new root stack with the given trace ID and assistant ID
|
||||
func NewStack(traceID, assistantID, referer string) *Stack {
|
||||
func NewStack(traceID, assistantID, referer string, opts *Options) *Stack {
|
||||
if traceID == "" {
|
||||
traceID = uuid.New().String()
|
||||
}
|
||||
|
|
@ -25,13 +25,14 @@ func NewStack(traceID, assistantID, referer string) *Stack {
|
|||
Depth: 0,
|
||||
ParentID: "",
|
||||
Path: []string{stackID},
|
||||
Options: opts,
|
||||
CreatedAt: now,
|
||||
Status: StackStatusRunning,
|
||||
}
|
||||
}
|
||||
|
||||
// NewChildStack creates a child stack from the current stack
|
||||
func (s *Stack) NewChildStack(assistantID, referer string) *Stack {
|
||||
func (s *Stack) NewChildStack(assistantID, referer string, opts *Options) *Stack {
|
||||
stackID := uuid.New().String()
|
||||
now := time.Now().UnixMilli()
|
||||
|
||||
|
|
@ -48,6 +49,7 @@ func (s *Stack) NewChildStack(assistantID, referer string) *Stack {
|
|||
Depth: s.Depth + 1,
|
||||
ParentID: s.ID,
|
||||
Path: path,
|
||||
Options: opts,
|
||||
CreatedAt: now,
|
||||
Status: StackStatusRunning,
|
||||
}
|
||||
|
|
@ -134,6 +136,7 @@ func (s *Stack) Clone() *Stack {
|
|||
Depth: s.Depth,
|
||||
ParentID: s.ParentID,
|
||||
Path: make([]string, len(s.Path)),
|
||||
Options: s.Options, // Shallow copy of Options pointer
|
||||
CreatedAt: s.CreatedAt,
|
||||
Status: s.Status,
|
||||
Error: s.Error,
|
||||
|
|
@ -165,14 +168,17 @@ func (s *Stack) Clone() *Stack {
|
|||
//
|
||||
// Usage:
|
||||
//
|
||||
// stack, traceID, done := context.EnterStack(ctx, assistantID, referer)
|
||||
// stack, traceID, done := context.EnterStack(ctx, assistantID, opts)
|
||||
// defer done()
|
||||
// // ... your code here ...
|
||||
func EnterStack(ctx *Context, assistantID, referer string) (*Stack, string, func()) {
|
||||
func EnterStack(ctx *Context, assistantID string, opts *Options) (*Stack, string, func()) {
|
||||
var stack *Stack
|
||||
var parentStack *Stack
|
||||
var traceID string
|
||||
|
||||
// Get referer from ctx (request source)
|
||||
referer := ctx.Referer
|
||||
|
||||
// Initialize Stacks map if not exists
|
||||
if ctx.Stacks == nil {
|
||||
ctx.Stacks = make(map[string]*Stack)
|
||||
|
|
@ -182,14 +188,14 @@ func EnterStack(ctx *Context, assistantID, referer string) (*Stack, string, func
|
|||
// Create root stack for this assistant call (entry point)
|
||||
// Generate a new trace ID for root
|
||||
traceID = trace.GenTraceID()
|
||||
stack = NewStack(traceID, assistantID, referer)
|
||||
stack = NewStack(traceID, assistantID, referer, opts)
|
||||
ctx.Stack = stack
|
||||
} else {
|
||||
// Create child stack for nested agent call
|
||||
// Inherit trace ID from parent
|
||||
parentStack = ctx.Stack
|
||||
traceID = parentStack.TraceID
|
||||
stack = ctx.Stack.NewChildStack(assistantID, referer)
|
||||
stack = ctx.Stack.NewChildStack(assistantID, referer, opts)
|
||||
ctx.Stack = stack
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ func TestNewStack(t *testing.T) {
|
|||
traceID := "12345678"
|
||||
assistantID := "test-assistant"
|
||||
referer := RefererAPI
|
||||
opts := &Options{}
|
||||
|
||||
stack := NewStack(traceID, assistantID, referer)
|
||||
stack := NewStack(traceID, assistantID, referer, opts)
|
||||
|
||||
if stack == nil {
|
||||
t.Fatal("Expected stack to be created, got nil")
|
||||
|
|
@ -57,7 +58,7 @@ func TestNewStack_GenerateTraceID(t *testing.T) {
|
|||
defer test.Clean()
|
||||
|
||||
// Empty traceID should generate a UUID
|
||||
stack := NewStack("", "test-assistant", RefererAPI)
|
||||
stack := NewStack("", "test-assistant", RefererAPI, &Options{})
|
||||
|
||||
if stack.TraceID == "" {
|
||||
t.Error("Expected TraceID to be generated, got empty string")
|
||||
|
|
@ -74,10 +75,10 @@ func TestNewChildStack(t *testing.T) {
|
|||
defer test.Clean()
|
||||
|
||||
// Create parent stack
|
||||
parentStack := NewStack("12345678", "parent-assistant", RefererAPI)
|
||||
parentStack := NewStack("12345678", "parent-assistant", RefererAPI, &Options{})
|
||||
|
||||
// Create child stack
|
||||
childStack := parentStack.NewChildStack("child-assistant", RefererAgent)
|
||||
childStack := parentStack.NewChildStack("child-assistant", RefererAgent, &Options{})
|
||||
|
||||
if childStack == nil {
|
||||
t.Fatal("Expected child stack to be created, got nil")
|
||||
|
|
@ -121,7 +122,7 @@ func TestStackComplete(t *testing.T) {
|
|||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
stack := NewStack("12345678", "test-assistant", RefererAPI)
|
||||
stack := NewStack("12345678", "test-assistant", RefererAPI, &Options{})
|
||||
|
||||
// Wait a bit to have measurable duration
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
|
@ -157,7 +158,7 @@ func TestStackFail(t *testing.T) {
|
|||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
stack := NewStack("12345678", "test-assistant", RefererAPI)
|
||||
stack := NewStack("12345678", "test-assistant", RefererAPI, &Options{})
|
||||
|
||||
testError := "test error message"
|
||||
stack.Fail(nil)
|
||||
|
|
@ -180,7 +181,7 @@ func TestStackTimeout(t *testing.T) {
|
|||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
stack := NewStack("12345678", "test-assistant", RefererAPI)
|
||||
stack := NewStack("12345678", "test-assistant", RefererAPI, &Options{})
|
||||
|
||||
stack.Timeout()
|
||||
|
||||
|
|
@ -199,9 +200,10 @@ func TestEnterStack_RootCreation(t *testing.T) {
|
|||
|
||||
ctx := &Context{
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: RefererAPI,
|
||||
}
|
||||
|
||||
stack, traceID, done := EnterStack(ctx, "test-assistant", RefererAPI)
|
||||
stack, traceID, done := EnterStack(ctx, "test-assistant", &Options{})
|
||||
defer done()
|
||||
|
||||
if stack == nil {
|
||||
|
|
@ -244,10 +246,11 @@ func TestEnterStack_ChildCreation(t *testing.T) {
|
|||
|
||||
ctx := &Context{
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: RefererAPI,
|
||||
}
|
||||
|
||||
// Create parent
|
||||
parentStack, parentTraceID, parentDone := EnterStack(ctx, "parent-assistant", RefererAPI)
|
||||
parentStack, parentTraceID, parentDone := EnterStack(ctx, "parent-assistant", &Options{})
|
||||
defer parentDone()
|
||||
|
||||
if parentStack == nil {
|
||||
|
|
@ -255,7 +258,7 @@ func TestEnterStack_ChildCreation(t *testing.T) {
|
|||
}
|
||||
|
||||
// Create child
|
||||
childStack, childTraceID, childDone := EnterStack(ctx, "child-assistant", RefererAgent)
|
||||
childStack, childTraceID, childDone := EnterStack(ctx, "child-assistant", &Options{})
|
||||
defer childDone()
|
||||
|
||||
if childStack == nil {
|
||||
|
|
@ -289,13 +292,14 @@ func TestEnterStack_DoneCallback(t *testing.T) {
|
|||
|
||||
ctx := &Context{
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: RefererAPI,
|
||||
}
|
||||
|
||||
// Create parent
|
||||
parentStack, _, parentDone := EnterStack(ctx, "parent-assistant", RefererAPI)
|
||||
parentStack, _, parentDone := EnterStack(ctx, "parent-assistant", &Options{})
|
||||
|
||||
// Create child
|
||||
childStack, _, childDone := EnterStack(ctx, "child-assistant", RefererAgent)
|
||||
childStack, _, childDone := EnterStack(ctx, "child-assistant", &Options{})
|
||||
|
||||
// Child should be current
|
||||
if ctx.Stack != childStack {
|
||||
|
|
@ -330,16 +334,17 @@ func TestContextGetAllStacks(t *testing.T) {
|
|||
|
||||
ctx := &Context{
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: RefererAPI,
|
||||
}
|
||||
|
||||
// Create multiple stacks
|
||||
_, _, done1 := EnterStack(ctx, "assistant1", RefererAPI)
|
||||
_, _, done1 := EnterStack(ctx, "assistant1", &Options{})
|
||||
defer done1()
|
||||
|
||||
_, _, done2 := EnterStack(ctx, "assistant2", RefererAgent)
|
||||
_, _, done2 := EnterStack(ctx, "assistant2", &Options{})
|
||||
defer done2()
|
||||
|
||||
_, _, done3 := EnterStack(ctx, "assistant3", RefererAgent)
|
||||
_, _, done3 := EnterStack(ctx, "assistant3", &Options{})
|
||||
defer done3()
|
||||
|
||||
// Get all stacks
|
||||
|
|
@ -356,9 +361,10 @@ func TestContextGetStackByID(t *testing.T) {
|
|||
|
||||
ctx := &Context{
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: RefererAPI,
|
||||
}
|
||||
|
||||
stack, _, done := EnterStack(ctx, "test-assistant", RefererAPI)
|
||||
stack, _, done := EnterStack(ctx, "test-assistant", &Options{})
|
||||
defer done()
|
||||
|
||||
// Get stack by ID
|
||||
|
|
@ -385,13 +391,14 @@ func TestContextGetStacksByTraceID(t *testing.T) {
|
|||
|
||||
ctx := &Context{
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: RefererAPI,
|
||||
}
|
||||
|
||||
// Create parent and child (same trace ID)
|
||||
_, traceID, done1 := EnterStack(ctx, "parent-assistant", RefererAPI)
|
||||
_, traceID, done1 := EnterStack(ctx, "parent-assistant", &Options{})
|
||||
defer done1()
|
||||
|
||||
_, _, done2 := EnterStack(ctx, "child-assistant", RefererAgent)
|
||||
_, _, done2 := EnterStack(ctx, "child-assistant", &Options{})
|
||||
defer done2()
|
||||
|
||||
// Get stacks by trace ID
|
||||
|
|
@ -415,14 +422,15 @@ func TestContextGetRootStack(t *testing.T) {
|
|||
|
||||
ctx := &Context{
|
||||
IDGenerator: message.NewIDGenerator(),
|
||||
Referer: RefererAPI,
|
||||
}
|
||||
|
||||
// Create parent
|
||||
parentStack, _, done1 := EnterStack(ctx, "parent-assistant", RefererAPI)
|
||||
parentStack, _, done1 := EnterStack(ctx, "parent-assistant", &Options{})
|
||||
defer done1()
|
||||
|
||||
// Create child
|
||||
_, _, done2 := EnterStack(ctx, "child-assistant", RefererAgent)
|
||||
_, _, done2 := EnterStack(ctx, "child-assistant", &Options{})
|
||||
defer done2()
|
||||
|
||||
// Get root stack
|
||||
|
|
@ -445,7 +453,7 @@ func TestStackClone(t *testing.T) {
|
|||
test.Prepare(t, config.Conf)
|
||||
defer test.Clean()
|
||||
|
||||
original := NewStack("12345678", "test-assistant", RefererAPI)
|
||||
original := NewStack("12345678", "test-assistant", RefererAPI, &Options{})
|
||||
original.Complete()
|
||||
|
||||
clone := original.Clone()
|
||||
|
|
|
|||
|
|
@ -235,9 +235,6 @@ type Context struct {
|
|||
output *output.Output `json:"-"` // Output, it will be used to write response data to the client
|
||||
messageMetadata *messageMetadataStore `json:"-"` // Thread-safe message metadata store for delta operations
|
||||
|
||||
// Skip configuration (history, trace, etc.), nil means don't skip anything
|
||||
Skip *Skip `json:"skip,omitempty"` // Skip configuration (history, trace, etc.), nil means don't skip anything
|
||||
|
||||
// Model capabilities (set by assistant, used by output adapters)
|
||||
Capabilities *openai.Capabilities `json:"-"` // Model capabilities for the current connector
|
||||
|
||||
|
|
@ -248,13 +245,6 @@ type Context struct {
|
|||
Authorized *types.AuthorizedInfo `json:"authorized,omitempty"` // Authorized information
|
||||
ChatID string `json:"chat_id,omitempty"` // Chat ID, use to select chat
|
||||
AssistantID string `json:"assistant_id,omitempty"` // Assistant ID, use to select assistant
|
||||
Connector string `json:"connector,omitempty"` // Connector, use to select the connector of the LLM Model, Default is Assistant.Connector
|
||||
Search *bool `json:"search,omitempty"` // Search mode, default is true
|
||||
|
||||
// Arguments for call
|
||||
Args []interface{} `json:"args,omitempty"` // Arguments for call, it will be used to pass data to the call
|
||||
Retry bool `json:"retry,omitempty"` // Retry mode
|
||||
RetryTimes uint8 `json:"retry_times,omitempty"` // Retry times
|
||||
|
||||
// Locale information
|
||||
Locale string `json:"locale,omitempty"` // Locale
|
||||
|
|
@ -270,6 +260,31 @@ type Context struct {
|
|||
Metadata map[string]interface{} `json:"metadata,omitempty"` // The metadata of the request, it will be used to pass data to the page
|
||||
}
|
||||
|
||||
// Options represents the options for the context
|
||||
type Options struct {
|
||||
|
||||
// Original context, override the default context
|
||||
Context context.Context `json:"-"` // Context, it will be used to pass the context to the call
|
||||
|
||||
// Writer, use to write response data to the client (override the default writer)
|
||||
Writer Writer `json:"writer,omitempty"` // Writer, use to write response data to the client
|
||||
|
||||
// Skip configuration (history, trace, etc.), nil means don't skip anything
|
||||
Skip *Skip `json:"skip,omitempty"` // Skip configuration (history, trace, etc.), nil means don't skip anything
|
||||
|
||||
// Connector, use to select the connector of the LLM Model, Default is Assistant.Connector
|
||||
Connector string `json:"connector,omitempty"` // Connector, use to select the connector of the LLM Model, Default is Assistant.Connector
|
||||
|
||||
// Disable global prompts, default is false
|
||||
DisableGlobalPrompts bool `json:"disable_global_prompts,omitempty"` // Temporarily disable global prompts for this request
|
||||
|
||||
// Search mode, default is true
|
||||
Search *bool `json:"search,omitempty"` // Search mode, default is true
|
||||
|
||||
// Agent mode, use to select the mode of the request, default is "chat"
|
||||
Mode string `json:"mode,omitempty"` // Agent mode, use to select the mode of the request, default is "chat"
|
||||
}
|
||||
|
||||
// Stack represents the call stack node for tracing agent-to-agent calls
|
||||
// Uses a flat structure to avoid circular references and memory overhead
|
||||
type Stack struct {
|
||||
|
|
@ -277,6 +292,9 @@ type Stack struct {
|
|||
ID string `json:"id"` // Unique stack node ID, used to identify this specific call
|
||||
TraceID string `json:"trace_id"` // Shared trace ID for entire call tree, inherited from root
|
||||
|
||||
// Options
|
||||
Options *Options `json:"options,omitempty"` // Options for the call
|
||||
|
||||
// Call context
|
||||
AssistantID string `json:"assistant_id"` // Assistant handling this call
|
||||
Referer string `json:"referer,omitempty"` // Call source: api, agent, tool, process, etc.
|
||||
|
|
@ -331,12 +349,11 @@ type HookCreateResponse struct {
|
|||
DisableGlobalPrompts *bool `json:"disable_global_prompts,omitempty"` // Temporarily disable global prompts for this request
|
||||
|
||||
// Context adjustments - allow hook to modify context fields
|
||||
AssistantID string `json:"assistant_id,omitempty"` // Override assistant ID
|
||||
Connector string `json:"connector,omitempty"` // Override connector
|
||||
Locale string `json:"locale,omitempty"` // Override locale
|
||||
Theme string `json:"theme,omitempty"` // Override theme
|
||||
Route string `json:"route,omitempty"` // Override route
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"` // Override or merge metadata
|
||||
Connector string `json:"connector,omitempty"` // Override connector (call-level)
|
||||
Locale string `json:"locale,omitempty"` // Override locale (session-level)
|
||||
Theme string `json:"theme,omitempty"` // Override theme (session-level)
|
||||
Route string `json:"route,omitempty"` // Override route (session-level)
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"` // Override or merge metadata (session-level)
|
||||
}
|
||||
|
||||
// NextHookPayload payload for the next hook
|
||||
|
|
@ -372,9 +389,9 @@ type NextHookResponse struct {
|
|||
|
||||
// DelegateConfig configuration for delegating to another agent
|
||||
type DelegateConfig struct {
|
||||
AgentID string `json:"agent_id"` // Required: target agent ID
|
||||
Messages []Message `json:"messages"` // Messages to send to target agent
|
||||
|
||||
AgentID string `json:"agent_id"` // Required: target agent ID
|
||||
Messages []Message `json:"messages"` // Messages to send to target agent
|
||||
Options map[string]interface{} `json:"options,omitempty"` // Optional: call-level options for delegation
|
||||
}
|
||||
|
||||
// NextAction defines the action determined by Next hook response
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ func newClaudeTestContext(chatID, connectorID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: "test-assistant",
|
||||
Connector: connectorID,
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -401,7 +401,6 @@ func newDeepSeekTestContext(chatID, connectorID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: "test-assistant",
|
||||
Connector: connectorID,
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -373,7 +373,6 @@ func newDeepSeekV3TestContext(chatID, connectorID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: "test-assistant",
|
||||
Connector: connectorID,
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -388,7 +388,6 @@ func newGPT5TestContext(chatID, connectorID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: "test-assistant",
|
||||
Connector: connectorID,
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -1508,7 +1508,6 @@ func newTestContext(chatID, connectorID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: "test-assistant",
|
||||
Connector: connectorID,
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -340,7 +340,6 @@ func newTemperatureTestContext(chatID, connectorID string) *context.Context {
|
|||
Space: plan.NewMemorySharedSpace(),
|
||||
ChatID: chatID,
|
||||
AssistantID: "test-assistant",
|
||||
Connector: connectorID,
|
||||
Locale: "en-us",
|
||||
Theme: "light",
|
||||
Client: context.Client{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ func GinCreateCompletions(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
completionReq, ctx, err := context.GetCompletionRequest(c, cache)
|
||||
completionReq, ctx, opts, err := context.GetCompletionRequest(c, cache)
|
||||
if err != nil {
|
||||
fmt.Println("-----------------------------------------------")
|
||||
fmt.Println("Error: ", err.Error())
|
||||
|
|
@ -61,7 +61,7 @@ func GinCreateCompletions(c *gin.Context) {
|
|||
// Stream the completion (uses default handler which sends to ctx.Writer)
|
||||
// The Stream method will automatically close the writer and send [DONE] marker
|
||||
log.Trace("[HTTP] Calling ast.Stream()")
|
||||
_, err = ast.Stream(ctx, completionReq.Messages)
|
||||
_, err = ast.Stream(ctx, completionReq.Messages, opts)
|
||||
log.Trace("[HTTP] ast.Stream() returned, err=%v", err)
|
||||
if err != nil {
|
||||
response.RespondWithError(c, response.StatusInternalServerError, &response.ErrorResponse{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue