From f7f4bc4bd9556ff5912bb18144de492192d8c335 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 06:07:56 +0000 Subject: [PATCH 1/2] [dotnet-port-api] Add Responses reasoning include control Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- provider/foundryprovider/agent.go | 4 ++ provider/foundryprovider/agent_test.go | 29 +++++++++++++ provider/openaiprovider/chat.go | 5 ++- provider/openaiprovider/responses.go | 24 ++++++++++- provider/openaiprovider/responses_test.go | 50 +++++++++++++++++++++++ 5 files changed, 110 insertions(+), 2 deletions(-) diff --git a/provider/foundryprovider/agent.go b/provider/foundryprovider/agent.go index 79f508d6..f1195cc5 100644 --- a/provider/foundryprovider/agent.go +++ b/provider/foundryprovider/agent.go @@ -30,6 +30,10 @@ type AgentConfig struct { // DisableStoreOutput disables service-side Responses output storage. // Use this when local session history providers own conversation state. + // By default, store-disabled runs also request `reasoning.encrypted_content` + // so reasoning items can be replayed statelessly; use + // [openaiprovider.ResponsesIncludeReasoningEncryptedContent] in RunOptions to + // opt out. DisableStoreOutput bool // OpenAIOptions are appended to the OpenAI-compatible per-agent client options. diff --git a/provider/foundryprovider/agent_test.go b/provider/foundryprovider/agent_test.go index 2d4340f2..a40a398f 100644 --- a/provider/foundryprovider/agent_test.go +++ b/provider/foundryprovider/agent_test.go @@ -10,6 +10,7 @@ import ( "github.com/microsoft/agent-framework-go/agent" "github.com/microsoft/agent-framework-go/provider/foundryprovider" + "github.com/microsoft/agent-framework-go/provider/openaiprovider" ) func TestNewAgentUsesProjectResponsesEndpointAndConfig(t *testing.T) { @@ -81,6 +82,34 @@ func TestNewAgentDisableStoreOutputSetsStoreFalse(t *testing.T) { } } +func TestNewAgentRunOptionCanDisableReasoningEncryptedContentInclude(t *testing.T) { + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body := jsonMap(t, mustReadBody(t, r)) + if body["store"] != false { + t.Fatalf("store = %#v, want false", body["store"]) + } + if _, ok := body["include"]; ok { + t.Fatalf("include = %#v, want omitted", body["include"]) + } + writeResponsesOK(w) + })) + defer server.Close() + + foundryAgent := newFoundryAgent(t, server, foundryprovider.ModelDeployment("gpt-4o-mini"), foundryprovider.AgentConfig{ + DisableStoreOutput: true, + Config: agent.Config{ + DisableFuncAutoCall: true, + RunOptions: []agent.Option{ + openaiprovider.ResponsesIncludeReasoningEncryptedContent(false), + }, + }, + }) + + if _, err := foundryAgent.RunText(t.Context(), "hello").Collect(); err != nil { + t.Fatalf("RunText error = %v", err) + } +} + func TestNewAgentRunsAgainstFoundryAgentEndpoint(t *testing.T) { server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/projects/proj/agents/my-agent/endpoint/protocols/openai/responses" { diff --git a/provider/openaiprovider/chat.go b/provider/openaiprovider/chat.go index 945bfd73..ba63b3d1 100644 --- a/provider/openaiprovider/chat.go +++ b/provider/openaiprovider/chat.go @@ -56,7 +56,10 @@ type AgentConfig struct { // DisableStoreOutput is used only by [NewResponsesAgent]. It disables // service-side output storage and prevents response IDs from being saved into - // agent sessions for later continuation. + // agent sessions for later continuation. While enabled, Responses requests + // also include `reasoning.encrypted_content` by default so reasoning items + // can be replayed statelessly; use [ResponsesIncludeReasoningEncryptedContent] + // to opt out per run. // It is ignored by [NewChatCompletionsAgent]. DisableStoreOutput bool diff --git a/provider/openaiprovider/responses.go b/provider/openaiprovider/responses.go index 64ec74e7..f199055f 100644 --- a/provider/openaiprovider/responses.go +++ b/provider/openaiprovider/responses.go @@ -73,11 +73,25 @@ func (o responsesNewParamsOpt) Value() any { return responses.ResponseNewParams(o) } +type responsesIncludeReasoningEncryptedContentOpt bool + +func (o responsesIncludeReasoningEncryptedContentOpt) Value() any { + return bool(o) +} + // ResponsesNewParams allows passing custom parameters to the underlying OpenAI Responses API calls. func ResponsesNewParams(params responses.ResponseNewParams) agent.Option { return responsesNewParamsOpt(params) } +// ResponsesIncludeReasoningEncryptedContent controls whether [NewResponsesAgent] +// automatically requests `reasoning.encrypted_content` while Responses output +// storage is disabled. The default is true so reasoning items can be replayed +// across stateless turns. +func ResponsesIncludeReasoningEncryptedContent(enabled bool) agent.Option { + return responsesIncludeReasoningEncryptedContentOpt(enabled) +} + func (a *responsesClient) formatOf(v any) (agent.ResponseFormat, error) { return jsonformat.ForType(reflect.TypeOf(v)) } @@ -231,7 +245,8 @@ func responsesBuildCompletionParams(config AgentConfig, messages []*message.Mess if param.IsOmitted(params.Store) { params.Store = openai.Bool(false) } - if !slices.Contains(params.Include, responses.ResponseIncludableReasoningEncryptedContent) { + if responsesIncludeReasoningEncryptedContent(opts) && + !slices.Contains(params.Include, responses.ResponseIncludableReasoningEncryptedContent) { params.Include = append(params.Include, responses.ResponseIncludableReasoningEncryptedContent) } } @@ -467,6 +482,13 @@ func responsesDisableStoreOutput(config AgentConfig, opts []agent.Option) bool { return config.DisableStoreOutput } +func responsesIncludeReasoningEncryptedContent(opts []agent.Option) bool { + if enabled, ok := agent.GetOption(opts, ResponsesIncludeReasoningEncryptedContent); ok { + return enabled + } + return true +} + // responsesBuildMessageParam converts an agent.Message to one or more OpenAI message parameters. // Returns a slice because some agent messages (like RoleTool) need to be split into multiple OpenAI messages. func responsesBuildMessageParam(msg *message.Message, resp responses.ResponseInputParam) (responses.ResponseInputParam, error) { diff --git a/provider/openaiprovider/responses_test.go b/provider/openaiprovider/responses_test.go index 3979c869..ce327f46 100644 --- a/provider/openaiprovider/responses_test.go +++ b/provider/openaiprovider/responses_test.go @@ -5817,6 +5817,56 @@ func TestResponsesNewParamsStoreFalseDoesNotDuplicateReasoningInclude(t *testing } } +func TestResponsesIncludeReasoningEncryptedContentFalseSkipsAutomaticInclude(t *testing.T) { + const input = ` + { + "store":false, + "model":"gpt-4o-mini", + "input":[{ + "type":"message", + "role":"user", + "content":[{"type":"input_text","text":"hello"}] + }] + } + ` + + const output = ` + { + "id": "resp_67890", + "object": "response", + "created_at": 1741891428, + "status": "completed", + "model": "gpt-4o-mini-2024-07-18", + "output": [{ + "type": "message", + "id": "msg_67d32764fcdc8191bcf2e444d4088804058a5e08c46a181d", + "status": "completed", + "role": "assistant", + "content": [{"type": "output_text", "text": "Hello!", "annotations": []}] + }] + } + ` + + server := newTestResponsesServer(t, input, output) + defer server.Close() + + a := openaiprovider.NewResponsesAgent( + openai.NewClient(option.WithBaseURL(server.URL)), + openaiprovider.AgentConfig{ + Model: "gpt-4o-mini", + Config: agent.Config{DisableFuncAutoCall: true}, + }, + ) + + _, err := a.RunText(t.Context(), "hello", + openaiprovider.ResponsesNewParams(responses.ResponseNewParams{Store: openai.Bool(false)}), + openaiprovider.ResponsesIncludeReasoningEncryptedContent(false), + ).Collect() + if err != nil { + t.Fatalf("error = %v", err) + } +} + func TestResponsesConversationId_AsConversationId_NonStreaming(t *testing.T) { const input = ` { From 02cecbfd3778b7819b3a3c37fb145809b04a5568 Mon Sep 17 00:00:00 2001 From: michelle-clayton-work Date: Wed, 2 Sep 2026 14:13:12 -0700 Subject: [PATCH 2/2] Inline responsesIncludeReasoningEncryptedContent into its only call site --- provider/openaiprovider/responses.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/provider/openaiprovider/responses.go b/provider/openaiprovider/responses.go index 862a162e..38f98d71 100644 --- a/provider/openaiprovider/responses.go +++ b/provider/openaiprovider/responses.go @@ -247,7 +247,7 @@ func responsesBuildCompletionParams(config AgentConfig, messages []*message.Mess if param.IsOmitted(params.Store) { params.Store = openai.Bool(false) } - if responsesIncludeReasoningEncryptedContent(opts) && + if include, ok := agent.GetOption(opts, ResponsesIncludeReasoningEncryptedContent); (!ok || include) && !slices.Contains(params.Include, responses.ResponseIncludableReasoningEncryptedContent) { params.Include = append(params.Include, responses.ResponseIncludableReasoningEncryptedContent) } @@ -469,13 +469,6 @@ func responsesDisableStoreOutput(config AgentConfig, opts []agent.Option) bool { return config.DisableStoreOutput } -func responsesIncludeReasoningEncryptedContent(opts []agent.Option) bool { - if enabled, ok := agent.GetOption(opts, ResponsesIncludeReasoningEncryptedContent); ok { - return enabled - } - return true -} - func responseInputItemParamOfFunctionCallOutput[T string | responses.ResponseFunctionCallOutputItemListParam](callID string, output T) responses.ResponseInputItemUnionParam { item := responses.ResponseInputItemParamOfFunctionCallOutput(output) if callID != "" {