diff --git a/agent/assistant/agent.go b/agent/assistant/agent.go index 4f991dec..111288c1 100644 --- a/agent/assistant/agent.go +++ b/agent/assistant/agent.go @@ -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 } diff --git a/agent/assistant/agent_interrupt_test.go b/agent/assistant/agent_interrupt_test.go index 716fdd6e..09a56752 100644 --- a/agent/assistant/agent_interrupt_test.go +++ b/agent/assistant/agent_interrupt_test.go @@ -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{ diff --git a/agent/assistant/assistant.go b/agent/assistant/assistant.go index df5f3baf..5238bd8b 100644 --- a/agent/assistant/assistant.go +++ b/agent/assistant/assistant.go @@ -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 diff --git a/agent/assistant/build_mcp_test.go b/agent/assistant/build_mcp_test.go index aa55b581..805df44e 100644 --- a/agent/assistant/build_mcp_test.go +++ b/agent/assistant/build_mcp_test.go @@ -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()) } diff --git a/agent/assistant/build_prompts_test.go b/agent/assistant/build_prompts_test.go index a96fda4c..529a015a 100644 --- a/agent/assistant/build_prompts_test.go +++ b/agent/assistant/build_prompts_test.go @@ -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) diff --git a/agent/assistant/build_test.go b/agent/assistant/build_test.go index ec0874a1..6cceddb3 100644 --- a/agent/assistant/build_test.go +++ b/agent/assistant/build_test.go @@ -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()) } diff --git a/agent/assistant/hook/create.go b/agent/assistant/hook/create.go index e48f4952..e29eba65 100644 --- a/agent/assistant/hook/create.go +++ b/agent/assistant/hook/create.go @@ -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 diff --git a/agent/assistant/hook/create_bench_test.go b/agent/assistant/hook/create_bench_test.go index 9432dbee..2d05375a 100644 --- a/agent/assistant/hook/create_bench_test.go +++ b/agent/assistant/hook/create_bench_test.go @@ -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{ diff --git a/agent/assistant/hook/create_mem_test.go b/agent/assistant/hook/create_mem_test.go index 2dd57ef0..0c8e62ee 100644 --- a/agent/assistant/hook/create_mem_test.go +++ b/agent/assistant/hook/create_mem_test.go @@ -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{ diff --git a/agent/assistant/hook/create_nested_test.go b/agent/assistant/hook/create_nested_test.go index c082324f..c12cc9e1 100644 --- a/agent/assistant/hook/create_nested_test.go +++ b/agent/assistant/hook/create_nested_test.go @@ -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"}, }) diff --git a/agent/assistant/hook/create_test.go b/agent/assistant/hook/create_test.go index 5e0a4131..7e5bad60 100644 --- a/agent/assistant/hook/create_test.go +++ b/agent/assistant/hook/create_test.go @@ -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) } diff --git a/agent/assistant/hook/goroutine_leak_test.go b/agent/assistant/hook/goroutine_leak_test.go index 706ad01c..2745e709 100644 --- a/agent/assistant/hook/goroutine_leak_test.go +++ b/agent/assistant/hook/goroutine_leak_test.go @@ -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{ diff --git a/agent/assistant/hook/next.go b/agent/assistant/hook/next.go index 7cb1c51b..64c8a74f 100644 --- a/agent/assistant/hook/next.go +++ b/agent/assistant/hook/next.go @@ -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 diff --git a/agent/assistant/hook/next_test.go b/agent/assistant/hook/next_test.go index c9f527ed..e3f69ed3 100644 --- a/agent/assistant/hook/next_test.go +++ b/agent/assistant/hook/next_test.go @@ -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()) } diff --git a/agent/assistant/hook/realworld_next_test.go b/agent/assistant/hook/realworld_next_test.go index 8b438435..85a9ab84 100644 --- a/agent/assistant/hook/realworld_next_test.go +++ b/agent/assistant/hook/realworld_next_test.go @@ -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) } diff --git a/agent/assistant/hook/realworld_stress_test.go b/agent/assistant/hook/realworld_stress_test.go index af4cbfb7..2690ed8a 100644 --- a/agent/assistant/hook/realworld_stress_test.go +++ b/agent/assistant/hook/realworld_stress_test.go @@ -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{ diff --git a/agent/assistant/load_store_test.go b/agent/assistant/load_store_test.go index 3eaba542..291d75e2 100644 --- a/agent/assistant/load_store_test.go +++ b/agent/assistant/load_store_test.go @@ -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) diff --git a/agent/assistant/next.go b/agent/assistant/next.go index 0baae34b..940d0739 100644 --- a/agent/assistant/next.go +++ b/agent/assistant/next.go @@ -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 diff --git a/agent/content/image_test.go b/agent/content/image_test.go index b28b345c..fb0e7633 100644 --- a/agent/content/image_test.go +++ b/agent/content/image_test.go @@ -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{ diff --git a/agent/content/tools.go b/agent/content/tools.go index 333a4092..c34330a0 100644 --- a/agent/content/tools.go +++ b/agent/content/tools.go @@ -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) } diff --git a/agent/context/context.go b/agent/context/context.go index 3af8f4da..0ed8fd32 100644 --- a/agent/context/context.go +++ b/agent/context/context.go @@ -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 != "" { diff --git a/agent/context/context_test.go b/agent/context/context_test.go index 0fd82779..f4c48aa3 100644 --- a/agent/context/context_test.go +++ b/agent/context/context_test.go @@ -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) diff --git a/agent/context/interrupt_test.go b/agent/context/interrupt_test.go index e4ac48ed..89bebddb 100644 --- a/agent/context/interrupt_test.go +++ b/agent/context/interrupt_test.go @@ -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{ diff --git a/agent/context/jsapi.go b/agent/context/jsapi.go index c8161175..93610438 100644 --- a/agent/context/jsapi.go +++ b/agent/context/jsapi.go @@ -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, diff --git a/agent/context/jsapi_mcp_test.go b/agent/context/jsapi_mcp_test.go index 9e306d6f..5f4b7363 100644 --- a/agent/context/jsapi_mcp_test.go +++ b/agent/context/jsapi_mcp_test.go @@ -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{}, ` diff --git a/agent/context/jsapi_release_test.go b/agent/context/jsapi_release_test.go index 5b0d3d14..74920f14 100644 --- a/agent/context/jsapi_release_test.go +++ b/agent/context/jsapi_release_test.go @@ -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{}, ` diff --git a/agent/context/jsapi_stress_test.go b/agent/context/jsapi_stress_test.go index c41fc10d..b970f878 100644 --- a/agent/context/jsapi_stress_test.go +++ b/agent/context/jsapi_stress_test.go @@ -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() diff --git a/agent/context/jsapi_test.go b/agent/context/jsapi_test.go index 61708dcb..2c430d9b 100644 --- a/agent/context/jsapi_test.go +++ b/agent/context/jsapi_test.go @@ -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 } diff --git a/agent/context/mcp_test.go b/agent/context/mcp_test.go index 16479ba6..0604d8df 100644 --- a/agent/context/mcp_test.go +++ b/agent/context/mcp_test.go @@ -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 diff --git a/agent/context/openapi.go b/agent/context/openapi.go index db656d5e..8f5f61fd 100644 --- a/agent/context/openapi.go +++ b/agent/context/openapi.go @@ -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 diff --git a/agent/context/openapi_test.go b/agent/context/openapi_test.go index 88a50474..d7829b22 100644 --- a/agent/context/openapi_test.go +++ b/agent/context/openapi_test.go @@ -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") diff --git a/agent/context/options.go b/agent/context/options.go new file mode 100644 index 00000000..9b9871ac --- /dev/null +++ b/agent/context/options.go @@ -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 +} diff --git a/agent/context/stack.go b/agent/context/stack.go index b0502497..de816768 100644 --- a/agent/context/stack.go +++ b/agent/context/stack.go @@ -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 } diff --git a/agent/context/stack_test.go b/agent/context/stack_test.go index 067f1c51..b678332c 100644 --- a/agent/context/stack_test.go +++ b/agent/context/stack_test.go @@ -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() diff --git a/agent/context/types.go b/agent/context/types.go index a2d5fce9..bea8390f 100644 --- a/agent/context/types.go +++ b/agent/context/types.go @@ -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 diff --git a/agent/llm/providers/openai/claude_test.go b/agent/llm/providers/openai/claude_test.go index d0b1e2bd..15fc39c1 100644 --- a/agent/llm/providers/openai/claude_test.go +++ b/agent/llm/providers/openai/claude_test.go @@ -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{ diff --git a/agent/llm/providers/openai/deepseek_r1_test.go b/agent/llm/providers/openai/deepseek_r1_test.go index 839fe642..bf790e3a 100644 --- a/agent/llm/providers/openai/deepseek_r1_test.go +++ b/agent/llm/providers/openai/deepseek_r1_test.go @@ -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{ diff --git a/agent/llm/providers/openai/deepseek_v3_test.go b/agent/llm/providers/openai/deepseek_v3_test.go index d593d473..3f944735 100644 --- a/agent/llm/providers/openai/deepseek_v3_test.go +++ b/agent/llm/providers/openai/deepseek_v3_test.go @@ -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{ diff --git a/agent/llm/providers/openai/gpt5_test.go b/agent/llm/providers/openai/gpt5_test.go index b71b0a8c..a04ea943 100644 --- a/agent/llm/providers/openai/gpt5_test.go +++ b/agent/llm/providers/openai/gpt5_test.go @@ -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{ diff --git a/agent/llm/providers/openai/openai_test.go b/agent/llm/providers/openai/openai_test.go index b4195137..111e7e3e 100644 --- a/agent/llm/providers/openai/openai_test.go +++ b/agent/llm/providers/openai/openai_test.go @@ -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{ diff --git a/agent/llm/providers/openai/temperature_test.go b/agent/llm/providers/openai/temperature_test.go index d648a74f..b0a8bd98 100644 --- a/agent/llm/providers/openai/temperature_test.go +++ b/agent/llm/providers/openai/temperature_test.go @@ -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{ diff --git a/openapi/chat/completions.go b/openapi/chat/completions.go index 68922afa..1262b39d 100644 --- a/openapi/chat/completions.go +++ b/openapi/chat/completions.go @@ -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{