diff --git a/contracts/agents-api/harness-onboarding.md b/contracts/agents-api/harness-onboarding.md index 5599eec1..ea91e96d 100644 --- a/contracts/agents-api/harness-onboarding.md +++ b/contracts/agents-api/harness-onboarding.md @@ -210,7 +210,7 @@ Before starting, record the operation set, expected results, exclusions and stop 1. **Contract tests.** Call `agent/contracttest.TextLifecycle` from a test named `TestSharedTextLifecycle` with the adapter's prepared Executor and a deterministic native fixture; `claudesdk/executor_test.go` is the reference. It checks independent Turn streams, native owner and history continuity, durable write and application receipts, stale cancellation and healthy continuation after cancellation. `make check-runtime-contract` runs it together with the shared wire, gateway, transport and dispatcher tests, the declaration completeness check and each adapter's `TestUnsupportedExtensionsHaveNoNativeEffects`. Adapter tests also cover two ordinary Turns sharing one native process or connection and history, cancellation followed by another Turn, stale cancellation and late events, native exit, cleanup failure, input write and application receipts, unknown outcomes and fresh per-Turn usage, function, input and child-observation state. State whether a fixture is controlled or a real provider. 2. **Shared integration.** `TestThirdHarnessPublicOnboarding` runs the synthetic Harness through public Session and input admission, Worker device selection, the real WebSocket gateway, the daemon Registry and Router, neutral events and durable terminal projection. It uses a custom immutable `engine.Catalog` in the same `execution.Policy` given to the API handler and the dispatcher, and checks applied input receipts, saved native identity, continuation, cancellation, unsupported optional requests and missing mandatory Runtime support. The fixture has no workspace, MCP, public functions, permissions or user-choice handlers, and its registration stays local to the test. It proves the integration path, not native execution. -3. **Real acceptance.** Use the pinned official Python SDK and raw HTTP against Core, a real provider API, the native Harness and a dedicated database. Verify initial execution, a warm follow-up, cancellation and restart with continuation; record native owner identity and same-condition cold and warm timing. For workspace placements also verify Files and Artifacts, workspace identity, that no credentials appear in public responses and that foreign history is rejected. `services/core/tests/official_hosted_functions_native.py` holds the shared function assertions: success and error, native file output and public Artifact bytes, same-history continuation after restart, foreign result rejection and pending-call cancellation. Synthetic or failed runs never count. The opt-in tests below run the pinned-SDK fixtures in `services/core/tests` against a real daemon and model; each runs when `OAC_TEST_OFFICIAL_SDK_PYTHON`, `OAC_TEST_NATIVE_DAEMON_BIN`, `OAC_TEST_NATIVE_PROOF_DIR` and its private options file are set. +3. **Real acceptance.** Use the pinned official Python SDK and raw HTTP against Core, a real provider API, the native Harness and a dedicated database. Verify initial execution, a warm follow-up, cancellation and restart with continuation; record native owner identity and same-condition cold and warm timing. For workspace placements also verify Files and Artifacts, workspace identity, that no credentials appear in public responses and that foreign history is rejected. `services/core/tests/official_hosted_functions_native.py` holds the shared function assertions: success and error, native file output and public Artifact bytes, same-history continuation after restart, foreign result rejection and pending-call cancellation. Synthetic or failed runs never count. The opt-in tests below run the pinned-SDK fixtures in `services/core/tests` against a real daemon and model; each runs when `OAC_TEST_OFFICIAL_SDK_PYTHON`, `OAC_TEST_NATIVE_DAEMON_BIN`, `OAC_TEST_NATIVE_PROOF_DIR` and its private options file are set. The options file is a JSON object with exactly `model` and `model_provider` (the fields of `x_agents_core.model_provider`); the test sets it as the deployment default model provider, which the fixtures' `environment: none` Sessions freeze at creation. 4. **Regression.** Existing Harnesses keep working. Run targeted tests, then `make check`; run `make openapi` after API changes and `make sqlc-generate` after query changes. 5. **Review.** Follow the [blind review workflow](../../CONTRIBUTING.md#review). diff --git a/services/core/internal/api/session_environment_test.go b/services/core/internal/api/session_environment_test.go index 9b7cee0e..4dc8b0ba 100644 --- a/services/core/internal/api/session_environment_test.go +++ b/services/core/internal/api/session_environment_test.go @@ -15,7 +15,7 @@ const environmentOrigin = "wss://core.example/api/v1/agent-daemon/ws" func environmentSession() store.Session { return store.Session{ ID: "session", TenantID: "tenant", CreatedAt: time.Unix(1700000000, 0), Metadata: map[string]string{}, - Configuration: json.RawMessage(`{"agent":{"id":"agent_test","model":"model","tools":[]},"environment":{"type":"self_hosted"},"daemon":{"credential":"private"}}`), + Configuration: json.RawMessage(`{"agent":{"id":"agent_test","model":"model","tools":[]},"environment":{"type":"self_hosted"}}`), Environment: &store.Environment{ ID: "environment", SessionID: "session", TenantID: "tenant", Status: "pending", Configuration: json.RawMessage(`{"type":"self_hosted","workspace_directory":"/remote/workspace","capability_directories":["/remote/capabilities"],"id":"forged","remote_url":"https://secret@private","env":{"SECRET":"private"},"setup_commands":["private"]}`), diff --git a/services/core/internal/engine/claude.go b/services/core/internal/engine/claude.go index 28529f66..7a19d266 100644 --- a/services/core/internal/engine/claude.go +++ b/services/core/internal/engine/claude.go @@ -42,8 +42,8 @@ func claudeProfile() Profile { } } -func validateClaudeConfiguration(agent v1.Agent, environment *v1.Environment, hasDaemon bool) error { - if environment == nil || (environment.Type != "none" && environment.Type != "openai_hosted" && environment.Type != "self_hosted") || hasDaemon || strings.TrimSpace(agent.Model) == "" { +func validateClaudeConfiguration(agent v1.Agent, environment *v1.Environment) error { + if environment == nil || (environment.Type != "none" && environment.Type != "openai_hosted" && environment.Type != "self_hosted") || strings.TrimSpace(agent.Model) == "" { return ErrInvalidInput } if agent.Text.Verbosity != "" && agent.Text.Verbosity != "medium" { @@ -90,7 +90,7 @@ func validateClaudeConfiguration(agent v1.Agent, environment *v1.Environment, ha return rejectSubagentTools(agent, "function", "mcp") } -func validateClaudeTools(environment *v1.Environment, _ bool, tools []proto.FunctionTool, mcp []proto.MCPHTTPServer) error { +func validateClaudeTools(environment *v1.Environment, tools []proto.FunctionTool, mcp []proto.MCPHTTPServer) error { if err := validateClaudeMCP(mcp); err != nil { return err } diff --git a/services/core/internal/engine/codex.go b/services/core/internal/engine/codex.go index e5988200..a2f67bc0 100644 --- a/services/core/internal/engine/codex.go +++ b/services/core/internal/engine/codex.go @@ -20,7 +20,7 @@ func codexProfile() Profile { ConfigurationValidation: AdditionalValidation, ToolsValidation: CommonValidationOnly, FunctionResultValidation: CommonValidationOnly, - ValidateConfiguration: func(agent v1.Agent, _ *v1.Environment, _ bool) error { + ValidateConfiguration: func(agent v1.Agent, _ *v1.Environment) error { return rejectSubagentTools(agent, "function", "mcp") }, } diff --git a/services/core/internal/engine/declaration_test.go b/services/core/internal/engine/declaration_test.go index 33c3a836..23706708 100644 --- a/services/core/internal/engine/declaration_test.go +++ b/services/core/internal/engine/declaration_test.go @@ -101,7 +101,7 @@ func TestCatalogRejectsInvalidCombinationsBeforeCallbacks(t *testing.T) { t.Run(name, func(t *testing.T) { p := enginetest.Profile(change) p.ConfigurationValidation = engine.AdditionalValidation - p.ValidateConfiguration = func(v1.Agent, *v1.Environment, bool) error { + p.ValidateConfiguration = func(v1.Agent, *v1.Environment) error { t.Fatal("registration invoked request validator") return nil } diff --git a/services/core/internal/engine/mcode.go b/services/core/internal/engine/mcode.go index bbd1aeca..950cc470 100644 --- a/services/core/internal/engine/mcode.go +++ b/services/core/internal/engine/mcode.go @@ -25,12 +25,12 @@ func mcodeProfile() Profile { ConfigurationValidation: AdditionalValidation, ToolsValidation: AdditionalValidation, FunctionResultValidation: CommonValidationOnly, - ValidateConfiguration: func(a v1.Agent, e *v1.Environment, daemon bool) error { - if e == nil || (e.Type != "none" && e.Type != "openai_hosted" && e.Type != "self_hosted") || daemon || strings.TrimSpace(a.Model) == "" || a.Reasoning.Effort != nil || a.Reasoning.Summary != nil || (a.ServiceTier != "" && a.ServiceTier != "auto") || (a.Text.Format.Type != "" && a.Text.Format.Type != "text") || (a.Text.Verbosity != "" && a.Text.Verbosity != "medium") { + ValidateConfiguration: func(a v1.Agent, e *v1.Environment) error { + if e == nil || (e.Type != "none" && e.Type != "openai_hosted" && e.Type != "self_hosted") || strings.TrimSpace(a.Model) == "" || a.Reasoning.Effort != nil || a.Reasoning.Summary != nil || (a.ServiceTier != "" && a.ServiceTier != "auto") || (a.Text.Format.Type != "" && a.Text.Format.Type != "text") || (a.Text.Verbosity != "" && a.Text.Verbosity != "medium") { return ErrInvalidInput } return rejectSubagentTools(a, "mcp") - }, ValidateTools: func(_ *v1.Environment, _ bool, functions []proto.FunctionTool, mcp []proto.MCPHTTPServer) error { + }, ValidateTools: func(_ *v1.Environment, functions []proto.FunctionTool, mcp []proto.MCPHTTPServer) error { if len(functions) > 0 { return errors.New("The configured engine does not support public functions.") } diff --git a/services/core/internal/engine/mcp.go b/services/core/internal/engine/mcp.go index 89939ec4..79be8e6f 100644 --- a/services/core/internal/engine/mcp.go +++ b/services/core/internal/engine/mcp.go @@ -10,15 +10,15 @@ import ( // ValidateMCPOrigins preserves outbound authority independently of Harness names. // Workspace connections never stand in for service-network connections. -func (p Profile) ValidateMCPOrigins(environment *v1.Environment, hasDaemon bool, servers []proto.MCPHTTPServer) error { +func (p Profile) ValidateMCPOrigins(environment *v1.Environment, servers []proto.MCPHTTPServer) error { for _, server := range servers { switch server.ConnectionOrigin { case "service": - if environment == nil || environment.Type != "none" || hasDaemon { + if environment == nil || environment.Type != "none" { return errors.New("Service-origin MCP requires the service-side environment:none profile.") } case "environment": - if environment == nil || (environment.Type != "openai_hosted" && environment.Type != "self_hosted") || hasDaemon { + if environment == nil || (environment.Type != "openai_hosted" && environment.Type != "self_hosted") { return errors.New("Environment-origin MCP requires a managed or self-hosted execution Environment.") } default: diff --git a/services/core/internal/engine/mcp_test.go b/services/core/internal/engine/mcp_test.go index 0976107d..4f6711d2 100644 --- a/services/core/internal/engine/mcp_test.go +++ b/services/core/internal/engine/mcp_test.go @@ -19,12 +19,9 @@ func TestMCPOriginQualification(t *testing.T) { for _, origin := range []string{"service", "environment", "", "unknown"} { servers := []proto.MCPHTTPServer{{ConnectionOrigin: origin, ServerLabel: "proof", ServerURL: "https://example.test/mcp"}} allowed := origin == "environment" && placement != "none" || origin == "service" && placement == "none" && kind != "mcode" - if err := profile.ValidateMCPOrigins(&v1.Environment{Type: placement}, false, servers); (err == nil) != allowed { + if err := profile.ValidateMCPOrigins(&v1.Environment{Type: placement}, servers); (err == nil) != allowed { t.Fatalf("%s/%s/%s: %v", kind, placement, origin, err) } - if profile.ValidateMCPOrigins(&v1.Environment{Type: placement}, true, servers) == nil { - t.Fatal("legacy daemon placement admitted") - } } } } @@ -36,7 +33,7 @@ func TestMiniMaxMCPPoliciesRejectInsteadOfDropping(t *testing.T) { for _, allowed := range []*[]string{nil, &empty, &named} { for _, required := range []bool{false, true} { server := proto.MCPHTTPServer{ConnectionOrigin: "environment", ServerLabel: "proof", ServerURL: "https://example.test", AllowedTools: allowed, Required: required} - err := p.ValidateTools(&v1.Environment{Type: "self_hosted"}, false, nil, []proto.MCPHTTPServer{server}) + err := p.ValidateTools(&v1.Environment{Type: "self_hosted"}, nil, []proto.MCPHTTPServer{server}) if (err == nil) != (allowed == nil && !required) { t.Fatal("unsupported MCP policy accepted", err) } diff --git a/services/core/internal/engine/profile.go b/services/core/internal/engine/profile.go index dce06476..afb701cc 100644 --- a/services/core/internal/engine/profile.go +++ b/services/core/internal/engine/profile.go @@ -26,8 +26,8 @@ type Profile struct { ConfigurationValidation ValidationPolicy ToolsValidation ValidationPolicy FunctionResultValidation ValidationPolicy - ValidateConfiguration func(agent v1.Agent, environment *v1.Environment, hasDaemon bool) error - ValidateTools func(environment *v1.Environment, hasDaemon bool, functions []proto.FunctionTool, mcp []proto.MCPHTTPServer) error + ValidateConfiguration func(agent v1.Agent, environment *v1.Environment) error + ValidateTools func(environment *v1.Environment, functions []proto.FunctionTool, mcp []proto.MCPHTTPServer) error ValidateFunctionResult func(placement string, result proto.FunctionResultPayload) error // WhitespaceOnlyText qualifies messages without an image or non-whitespace // text. Unqualified harnesses reject them at admission; Core never trims or diff --git a/services/core/internal/engine/subagents_test.go b/services/core/internal/engine/subagents_test.go index 0be335d4..2788edcb 100644 --- a/services/core/internal/engine/subagents_test.go +++ b/services/core/internal/engine/subagents_test.go @@ -17,7 +17,7 @@ func TestSubagentProfilesPreserveQualifiedOperationBoundaries(t *testing.T) { agent := v1.Agent{Model: "real-model"} agent.MultiAgent.Enabled = true for _, placement := range []string{"none", "openai_hosted", "self_hosted"} { - if err := profile.ValidateConfiguration(agent, &v1.Environment{Type: placement}, false); err != nil { + if err := profile.ValidateConfiguration(agent, &v1.Environment{Type: placement}); err != nil { t.Fatal(placement, err) } } @@ -28,11 +28,11 @@ func TestSubagentProfilesPreserveQualifiedOperationBoundaries(t *testing.T) { } // Its existing tool profile rejects both for every Session. for _, tool := range []string{`{"type":"function","name":"f"}`, `{"type":"mcp","server_label":"s"}`} { agent.Tools = []json.RawMessage{json.RawMessage(tool)} - if err := profile.ValidateConfiguration(agent, &v1.Environment{Type: "none"}, false); err == nil { + if err := profile.ValidateConfiguration(agent, &v1.Environment{Type: "none"}); err == nil { t.Fatal("unqualified multi-agent combination accepted", tool) } agent.MultiAgent.Enabled = false - if err := profile.ValidateConfiguration(agent, &v1.Environment{Type: "none"}, false); err != nil { + if err := profile.ValidateConfiguration(agent, &v1.Environment{Type: "none"}); err != nil { t.Fatal("single-agent profile changed", err) } agent.MultiAgent.Enabled = true diff --git a/services/core/internal/execution/dispatcher.go b/services/core/internal/execution/dispatcher.go index 39276592..fdb297b9 100644 --- a/services/core/internal/execution/dispatcher.go +++ b/services/core/internal/execution/dispatcher.go @@ -14,31 +14,20 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" ) -// Snapshot is resolved internally; Daemon is not a public environment wire type. +// Snapshot is the Session configuration frozen at creation. type Snapshot struct { ModelProviderConfigured bool `json:"model_provider_configured,omitempty"` Agent v1.Agent `json:"agent"` - Daemon *DaemonConfig `json:"daemon"` Environment *v1.Environment `json:"environment"` VaultIDs []string `json:"vault_ids,omitempty"` MCPCredentials []store.MCPCredentialBinding `json:"mcp_credentials,omitempty"` } -type DaemonConfig struct { - WorkDir string `json:"work_dir"` -} - type Dispatcher struct { notifications *executionNotifications Policy Store *store.Store Registry *runtimegateway.Registry - // Options optionally supplies native adapter options for Sessions that need - // no frozen model provider (environment none and legacy daemon Sessions). - // The server command leaves it nil since the operator options file was - // retired; native tests use it to reach synthetic model servers. It is never - // consulted for openai_hosted or self_hosted Sessions. - Options func(context.Context, store.Session) (map[string]any, error) // ManagedRuntimes is optional internal provisioning; it does not admit hosted API requests. ManagedRuntimes *RuntimeProvider // MaxConcurrentExecutions bounds work admitted by this Core execution owner. @@ -77,9 +66,8 @@ func (d *Dispatcher) Run(ctx context.Context, tenantID, sessionID, turnID string if err != nil { return store.Turn{}, err } - workDir, noEnvironment, err := resolveExecutionEnvironment(snapshot) - if err != nil { - return store.Turn{}, err + if !environmentNone(snapshot) { + return store.Turn{}, store.ErrInvalidInput } text, through, err := d.initialInput(ctx, tenantID, sessionID, turnID) if err != nil { @@ -92,7 +80,7 @@ func (d *Dispatcher) Run(ctx context.Context, tenantID, sessionID, turnID string if err != nil { return store.Turn{}, err } - req.WorkDir, req.DisableExecutionEnvironment = workDir, noEnvironment + req.DisableExecutionEnvironment = true prepared, err := d.prepareTurnExecutor(ctx, peer, tenantID, sessionID, turnID, req, store.TurnQueued) if err != nil { return store.Turn{}, err diff --git a/services/core/internal/execution/engine_profile.go b/services/core/internal/execution/engine_profile.go index 0554cd22..7dec002a 100644 --- a/services/core/internal/execution/engine_profile.go +++ b/services/core/internal/execution/engine_profile.go @@ -21,7 +21,7 @@ func validateProfileConfiguration(profile engine.Profile, snapshot Snapshot) err return errors.New("Structured output is not qualified for this engine.") } if profile.ConfigurationValidation == engine.AdditionalValidation { - if err := profile.ValidateConfiguration(snapshot.Agent, snapshot.Environment, snapshot.Daemon != nil); err != nil { + if err := profile.ValidateConfiguration(snapshot.Agent, snapshot.Environment); err != nil { return profileError(err) } } @@ -31,7 +31,7 @@ func validateProfileConfiguration(profile engine.Profile, snapshot Snapshot) err return err } if err == nil { - if err := profile.ValidateMCPOrigins(snapshot.Environment, snapshot.Daemon != nil, tools.MCP); err != nil { + if err := profile.ValidateMCPOrigins(snapshot.Environment, tools.MCP); err != nil { return err } if tools.DisableProgrammatic && !profile.ProgrammaticToolCallingDisable.IsSupported() { @@ -43,7 +43,7 @@ func validateProfileConfiguration(profile engine.Profile, snapshot Snapshot) err } } if profile.ToolsValidation == engine.AdditionalValidation { - if validationErr := profile.ValidateTools(snapshot.Environment, snapshot.Daemon != nil, tools.Functions, tools.MCP); validationErr != nil { + if validationErr := profile.ValidateTools(snapshot.Environment, tools.Functions, tools.MCP); validationErr != nil { return profileError(validationErr) } } diff --git a/services/core/internal/execution/engine_profile_test.go b/services/core/internal/execution/engine_profile_test.go index a17ba31e..24df3c90 100644 --- a/services/core/internal/execution/engine_profile_test.go +++ b/services/core/internal/execution/engine_profile_test.go @@ -35,14 +35,14 @@ func TestAdditionalProfileUsesCommonAdmission(t *testing.T) { profile.ConfigurationValidation = engine.AdditionalValidation profile.ToolsValidation = engine.AdditionalValidation profile.FunctionResultValidation = engine.AdditionalValidation - profile.ValidateConfiguration = func(agent v1.Agent, environment *v1.Environment, hasDaemon bool) error { + profile.ValidateConfiguration = func(agent v1.Agent, environment *v1.Environment) error { configurationChecked = true - if agent.Model != "fixture" || environment == nil || hasDaemon { + if agent.Model != "fixture" || environment == nil { return engine.ErrInvalidInput } return nil } - profile.ValidateTools = func(_ *v1.Environment, _ bool, tools []proto.FunctionTool, mcp []proto.MCPHTTPServer) error { + profile.ValidateTools = func(_ *v1.Environment, tools []proto.FunctionTool, mcp []proto.MCPHTTPServer) error { toolsChecked = true if len(tools) != 1 || tools[0].Name != "echo" || len(mcp) != 0 { t.Fatal("common tool decoding did not reach profile") @@ -105,7 +105,7 @@ func TestExplicitValidationPoliciesPreserveErrorPrecedence(t *testing.T) { profile := enginetest.Profile(func(p *engine.Profile) { p.ConfigurationValidation = configuration if configuration == engine.AdditionalValidation { - p.ValidateConfiguration = func(v1.Agent, *v1.Environment, bool) error { + p.ValidateConfiguration = func(v1.Agent, *v1.Environment) error { if rejectConfiguration { return configurationErr } @@ -113,7 +113,7 @@ func TestExplicitValidationPoliciesPreserveErrorPrecedence(t *testing.T) { } } p.ToolsValidation = engine.AdditionalValidation - p.ValidateTools = func(*v1.Environment, bool, []proto.FunctionTool, []proto.MCPHTTPServer) error { + p.ValidateTools = func(*v1.Environment, []proto.FunctionTool, []proto.MCPHTTPServer) error { toolsCalled = true return toolsErr } diff --git a/services/core/internal/execution/environment.go b/services/core/internal/execution/environment.go index 20254bd3..42eb86f0 100644 --- a/services/core/internal/execution/environment.go +++ b/services/core/internal/execution/environment.go @@ -1,25 +1,8 @@ package execution -import ( - "path/filepath" - "strings" - - "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" -) - -func resolveExecutionEnvironment(snapshot Snapshot) (string, bool, error) { - if snapshot.Environment != nil { - if snapshot.Environment.Type != "none" || snapshot.Daemon != nil { - return "", false, store.ErrInvalidInput - } - return "", true, nil - } - if snapshot.Daemon == nil { - return "", false, store.ErrInvalidInput - } - workDir := snapshot.Daemon.WorkDir - if workDir != "" && !filepath.IsAbs(workDir) && !strings.HasPrefix(workDir, "~/") { - return "", false, store.ErrInvalidInput - } - return workDir, false, nil +// environmentNone reports whether a Session runs through Run and input +// admission: only environment:none Sessions do. Managed and self-hosted +// Environments run through RunEnvironmentInput. +func environmentNone(snapshot Snapshot) bool { + return snapshot.Environment != nil && snapshot.Environment.Type == "none" } diff --git a/services/core/internal/execution/environment_test.go b/services/core/internal/execution/environment_test.go index cf9c1052..a40ce75c 100644 --- a/services/core/internal/execution/environment_test.go +++ b/services/core/internal/execution/environment_test.go @@ -1,29 +1,26 @@ package execution import ( - v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" "testing" + + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" ) func TestExecutionEnvironmentDoesNotDefaultToLocal(t *testing.T) { cases := []struct { - name string - snapshot Snapshot - dir string - none, invalid bool + name string + snapshot Snapshot + none bool }{ - {"public none", Snapshot{Environment: &v1.Environment{Type: "none"}}, "", true, false}, - {"legacy device", Snapshot{Daemon: &DaemonConfig{WorkDir: "/workspace"}}, "/workspace", false, false}, - {"missing", Snapshot{}, "", false, true}, - {"conflicting", Snapshot{Environment: &v1.Environment{Type: "none"}, Daemon: &DaemonConfig{}}, "", false, true}, - {"unsupported", Snapshot{Environment: &v1.Environment{Type: "self_hosted"}}, "", false, true}, - {"relative", Snapshot{Daemon: &DaemonConfig{WorkDir: "workspace"}}, "", false, true}, + {"public none", Snapshot{Environment: &v1.Environment{Type: "none"}}, true}, + {"missing", Snapshot{}, false}, + {"self-hosted", Snapshot{Environment: &v1.Environment{Type: "self_hosted"}}, false}, + {"hosted", Snapshot{Environment: &v1.Environment{Type: "openai_hosted"}}, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - dir, none, err := resolveExecutionEnvironment(c.snapshot) - if (err != nil) != c.invalid || dir != c.dir || none != c.none { - t.Fatal(dir, none, err) + if environmentNone(c.snapshot) != c.none { + t.Fatal(c.snapshot) } }) } diff --git a/services/core/internal/execution/mcp_credentials_test.go b/services/core/internal/execution/mcp_credentials_test.go index a23d4f79..c9c80127 100644 --- a/services/core/internal/execution/mcp_credentials_test.go +++ b/services/core/internal/execution/mcp_credentials_test.go @@ -12,7 +12,7 @@ import ( func TestMCPFrozenCredentialAdmission(t *testing.T) { vault, credential := uuid.NewString(), uuid.NewString() - for _, mode := range []string{"implicit", "explicit", "oauth implicit", "oauth explicit", "anonymous", "missing", "unattached", "wrong URL", "wrong auth", "changed selection", "HTTP", "remote", "self-hosted explicit", "self-hosted implicit", "self-hosted anonymous"} { + for _, mode := range []string{"implicit", "explicit", "oauth implicit", "oauth explicit", "anonymous", "missing", "unattached", "wrong URL", "wrong auth", "changed selection", "HTTP", "self-hosted explicit", "self-hosted implicit", "self-hosted anonymous"} { t.Run(mode, func(t *testing.T) { tool := v1.MCPTool{Type: "mcp", ServerLabel: "tools", ConnectionOrigin: "service", Transport: v1.MCPHTTPTransport{Type: "http", ServerURL: "https://mcp.example/tools"}} binding := store.MCPCredentialBinding{ServerLabel: "tools", ServerURL: tool.Transport.ServerURL, VaultID: vault, CredentialID: credential, AuthType: "static_bearer"} @@ -30,8 +30,6 @@ func TestMCPFrozenCredentialAdmission(t *testing.T) { binding.AuthType = "other" case "HTTP": tool.Transport.ServerURL, binding.ServerURL = "http://mcp.example/tools", "http://mcp.example/tools" - case "remote": - snapshot.Daemon = &DaemonConfig{WorkDir: "/tmp"} } if strings.HasPrefix(mode, "oauth ") { binding.AuthType = "mcp_oauth" diff --git a/services/core/internal/execution/mcp_support.go b/services/core/internal/execution/mcp_support.go index 66a880f5..3f197436 100644 --- a/services/core/internal/execution/mcp_support.go +++ b/services/core/internal/execution/mcp_support.go @@ -27,7 +27,7 @@ func (p Policy) mcpExecutionCredentials(engine string, snapshot Snapshot, server return nil, errors.New(message) } profile, _ := p.Engines.Lookup(engine) - if err := profile.ValidateMCPOrigins(snapshot.Environment, snapshot.Daemon != nil, servers); err != nil { + if err := profile.ValidateMCPOrigins(snapshot.Environment, servers); err != nil { return nil, err } if len(servers) > 0 && !caps.MCPHTTPTools { diff --git a/services/core/internal/execution/mcp_support_test.go b/services/core/internal/execution/mcp_support_test.go index fffeef1f..f522dbe5 100644 --- a/services/core/internal/execution/mcp_support_test.go +++ b/services/core/internal/execution/mcp_support_test.go @@ -57,7 +57,7 @@ func TestMCPPublicBearerPolicyIsIndependentOfRuntimeCapabilities(t *testing.T) { func TestMCPExecutionChecksRequireVerifiedCapabilityCombinations(t *testing.T) { for _, placement := range []string{"none", "self_hosted"} { - for _, missing := range []string{"", "mcp", "bearer", "placement", "required", "preparation", "daemon", "environment"} { + for _, missing := range []string{"", "mcp", "bearer", "placement", "required", "preparation", "environment"} { t.Run(placement+"/"+missing, func(t *testing.T) { snapshot, servers, caps := mcpSupportFixture(t) snapshot.Environment.Type = placement @@ -80,8 +80,6 @@ func TestMCPExecutionChecksRequireVerifiedCapabilityCombinations(t *testing.T) { caps.MCPHTTPRequired = false case "preparation": caps.Preparation = false - case "daemon": - snapshot.Daemon = &DaemonConfig{WorkDir: "/work"} case "environment": snapshot.Environment = nil } diff --git a/services/core/internal/execution/model_execution_test.go b/services/core/internal/execution/model_execution_test.go index 3c17e5bd..c456865e 100644 --- a/services/core/internal/execution/model_execution_test.go +++ b/services/core/internal/execution/model_execution_test.go @@ -1,7 +1,6 @@ package execution import ( - "context" "errors" "reflect" "strings" @@ -13,25 +12,24 @@ import ( ) func TestSessionModelExecutionNeverFallsBack(t *testing.T) { - called := false - d := Dispatcher{Options: func(context.Context, store.Session) (map[string]any, error) { - called = true - return map[string]any{"model_provider": map[string]any{"base_url": "http://127.0.0.1:1/v1"}}, nil - }} - if _, err := d.executionRequest(t.Context(), store.Session{Engine: "codex"}, Snapshot{ModelProviderConfigured: true}, runtimedevice.KindCapabilities{}, store.SessionExecutionBinding{}); err == nil || called { + var d Dispatcher + if _, err := d.executionRequest(t.Context(), store.Session{Engine: "codex"}, Snapshot{ModelProviderConfigured: true}, runtimedevice.KindCapabilities{}, store.SessionExecutionBinding{}); err == nil { t.Fatal("missing Session credentials fell back") } // Hosted and self-hosted Runtimes have no model configuration of their own. for _, environment := range []string{"openai_hosted", "self_hosted"} { snapshot := Snapshot{Environment: &v1.Environment{Type: environment}} - if _, err := d.executionRequest(t.Context(), store.Session{Engine: "codex"}, snapshot, runtimedevice.KindCapabilities{}, store.SessionExecutionBinding{}); !errors.Is(err, store.ErrModelProviderRequired) || called { + if _, err := d.executionRequest(t.Context(), store.Session{Engine: "codex"}, snapshot, runtimedevice.KindCapabilities{}, store.SessionExecutionBinding{}); !errors.Is(err, store.ErrModelProviderRequired) { t.Fatal("provider-free Session dispatched", environment, err) } } - // A none device may supply its own provider environment. - request, err := d.executionRequest(t.Context(), store.Session{Engine: "codex"}, Snapshot{Environment: &v1.Environment{Type: "none"}}, runtimedevice.KindCapabilities{}, store.SessionExecutionBinding{}) - if err != nil || !called || request.AgentOptions["model_provider"] == nil { - t.Fatal("none Session lost its adapter options", err) + // A none device without a frozen provider uses its own provider environment: + // Core sends only the Agent's model and instructions. + instructions := "Keep this instruction." + snapshot := Snapshot{Agent: v1.Agent{Model: "device-model", Instructions: &instructions}, Environment: &v1.Environment{Type: "none"}} + request, err := d.executionRequest(t.Context(), store.Session{Engine: "codex"}, snapshot, runtimedevice.KindCapabilities{}, store.SessionExecutionBinding{}) + if err != nil || !reflect.DeepEqual(request.AgentOptions, map[string]any{"model": "device-model", "system_prompt": &instructions}) { + t.Fatal("none Session received adapter options Core does not own", request.AgentOptions, err) } } diff --git a/services/core/internal/execution/prepared_dispatch.go b/services/core/internal/execution/prepared_dispatch.go index 5bb62f01..6a59799b 100644 --- a/services/core/internal/execution/prepared_dispatch.go +++ b/services/core/internal/execution/prepared_dispatch.go @@ -34,7 +34,7 @@ func (d *Dispatcher) RunEnvironmentInput(ctx context.Context, tenantID, sessionI return run, err } var snapshot Snapshot - if json.Unmarshal(session.Configuration, &snapshot) != nil || snapshot.Daemon != nil || strings.TrimSpace(snapshot.Agent.Model) == "" { + if json.Unmarshal(session.Configuration, &snapshot) != nil || strings.TrimSpace(snapshot.Agent.Model) == "" { return run, store.ErrInvalidInput } if !snapshot.ModelProviderConfigured && snapshot.Environment != nil && v1.ModelProviderRequired(snapshot.Environment.Type) { diff --git a/services/core/internal/execution/request.go b/services/core/internal/execution/request.go index f784783c..35319705 100644 --- a/services/core/internal/execution/request.go +++ b/services/core/internal/execution/request.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "errors" - "maps" v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" "github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto" @@ -31,19 +30,8 @@ func (d *Dispatcher) executionRequest(ctx context.Context, session store.Session // Require the frozen bundle before dispatch so the harness cannot // select an implicit provider endpoint. return proto.PromptRequestPayload{}, store.ErrModelProviderRequired - } else if d.Options != nil { - options, err = d.Options(ctx, session) - if err != nil { - return proto.PromptRequestPayload{}, err - } - options = maps.Clone(options) - if options == nil { - options = map[string]any{} - } } options["model"], options["system_prompt"] = snapshot.Agent.Model, snapshot.Agent.Instructions - delete(options, "override_system_prompt") - delete(options, "harness_config") if snapshot.Agent.XAgentsCore != nil && len(snapshot.Agent.XAgentsCore.HarnessConfig) > 0 { var native map[string]any if err := json.Unmarshal(snapshot.Agent.XAgentsCore.HarnessConfig, &native); err != nil { diff --git a/services/core/internal/execution/support.go b/services/core/internal/execution/support.go index c0eab56c..6caefc3c 100644 --- a/services/core/internal/execution/support.go +++ b/services/core/internal/execution/support.go @@ -26,14 +26,14 @@ func (p Policy) ValidateSessionConfiguration(engine string, configuration json.R return err } if snapshot.Environment != nil && snapshot.Environment.Type == "self_hosted" { - if snapshot.Daemon != nil || strings.TrimSpace(snapshot.Agent.Model) == "" || !validSelfHostedPlacement(environmentPlacement{WorkspaceDirectory: snapshot.Environment.WorkspaceDirectory, CapabilityDirectories: snapshot.Environment.CapabilityDirectories}) { + if strings.TrimSpace(snapshot.Agent.Model) == "" || !validSelfHostedPlacement(environmentPlacement{WorkspaceDirectory: snapshot.Environment.WorkspaceDirectory, CapabilityDirectories: snapshot.Environment.CapabilityDirectories}) { return store.ErrInvalidInput } } if snapshot.Environment != nil && snapshot.Environment.Type == "openai_hosted" { // Only this placement/engine combination has current native qualification. // Runtime capability checks still apply before any execution claim. - if snapshot.Daemon != nil || strings.TrimSpace(snapshot.Agent.Model) == "" { + if strings.TrimSpace(snapshot.Agent.Model) == "" { return store.ErrInvalidInput } configuration, err := json.Marshal(snapshot.Environment) @@ -54,7 +54,7 @@ func (p Policy) ValidateSessionConfiguration(engine string, configuration json.R func (p Policy) canAdmitInputs(engine string, configuration json.RawMessage) bool { var snapshot Snapshot - if json.Unmarshal(configuration, &snapshot) != nil || snapshot.Environment == nil || snapshot.Environment.Type != "none" || snapshot.Daemon != nil { + if json.Unmarshal(configuration, &snapshot) != nil || !environmentNone(snapshot) { return false } return p.ValidateSessionConfiguration(engine, configuration) == nil @@ -139,7 +139,7 @@ func (p Policy) engineCapabilities(peer *runtimegateway.Session, engine string, return fail("device must advertise local preparation, workspace reads and output export") } } - if snapshot.Environment != nil && snapshot.Environment.Type == "none" && !caps.EnvironmentNone { + if environmentNone(snapshot) && !caps.EnvironmentNone { return fail("device must advertise environment_none") } return caps, nil diff --git a/services/core/internal/store/dispatch_test.go b/services/core/internal/store/dispatch_test.go index 57f72c3b..f9434316 100644 --- a/services/core/internal/store/dispatch_test.go +++ b/services/core/internal/store/dispatch_test.go @@ -40,7 +40,7 @@ type dispatchHarness struct { func newDispatchHarness(t *testing.T) *dispatchHarness { t.Helper() - return newDispatchHarnessForSession(t, []byte(`{"agent":{"model":"test-model","instructions":"Keep this instruction."},"daemon":{"work_dir":"/tmp"}}`), false) + return newDispatchHarnessForSession(t, []byte(`{"agent":{"model":"test-model","instructions":"Keep this instruction."},"environment":{"type":"none"}}`), false) } func newDispatchHarnessForSession(t *testing.T, configuration []byte, local bool) *dispatchHarness { @@ -95,7 +95,7 @@ func newDispatchHarnessForSession(t *testing.T, configuration []byte, local bool t.Fatal("device connection failed") } t.Cleanup(func() { h.conn.Close() }) - h.write("", proto.TypeHeartbeat, proto.HeartbeatPayload{SupportedAgentKinds: []proto.SupportedAgentKind{{Kind: "codex", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{Streaming: proto.CapabilitySupported, Steering: proto.CapabilitySupported, Resume: proto.CapabilitySupported, DurableTurns: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported, WebSearchControl: proto.CapabilitySupported, TextVerbosity: proto.CapabilitySupported, ExecutionControls: proto.CapabilitySupported, SubagentControl: proto.CapabilitySupported, SubagentObservations: proto.CapabilitySupported, ToolObservations: proto.CapabilitySupported, NativeSessionRecovery: proto.CapabilitySupported, Preparation: proto.CapabilitySupported})}}}) + h.write("", proto.TypeHeartbeat, proto.HeartbeatPayload{SupportedAgentKinds: []proto.SupportedAgentKind{{Kind: "codex", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{Streaming: proto.CapabilitySupported, Steering: proto.CapabilitySupported, Resume: proto.CapabilitySupported, DurableTurns: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported, WebSearchControl: proto.CapabilitySupported, TextVerbosity: proto.CapabilitySupported, ExecutionControls: proto.CapabilitySupported, SubagentControl: proto.CapabilitySupported, SubagentObservations: proto.CapabilitySupported, ToolObservations: proto.CapabilitySupported, NativeSessionRecovery: proto.CapabilitySupported, Preparation: proto.CapabilitySupported, EnvironmentNone: proto.CapabilitySupported})}}}) deadline := time.Now().Add(3 * time.Second) for { peer, e := h.registry.LookupDevice(h.device.ID) @@ -375,30 +375,41 @@ func TestExecutionOutcomeAndNativeBindingCommitTogether(t *testing.T) { } } -func TestExecutionRejectsLegacyDaemonBeforeClaim(t *testing.T) { - for _, missing := range []string{"execution_controls", "durable_input_receipts", "durable_turns", "web_search_control", "text_verbosity", "subagent_control", "tool_observations"} { +func TestExecutionRejectsRuntimeMissingCapabilityBeforeClaim(t *testing.T) { + for _, tc := range []struct{ missing, message string }{ + {"durable_turns", "device must advertise streaming, steering and durable turns for this engine"}, + {"durable_input_receipts", "device must advertise streaming, steering and durable turns for this engine"}, + {"preparation", "device must advertise executor preparation"}, + {"execution_controls", "device must advertise execution_controls"}, + {"web_search_control", "device must advertise web_search_control"}, + {"text_verbosity", "device must advertise text_verbosity"}, + {"tool_observations", "device must advertise tool_observations"}, + {"subagent_control", "device must advertise subagent_control"}, + {"environment_none", "device must advertise environment_none"}, + } { + missing := tc.missing t.Run(missing, func(t *testing.T) { h := newDispatchHarness(t) - h.write("", proto.TypeHeartbeat, proto.HeartbeatPayload{SupportedAgentKinds: []proto.SupportedAgentKind{{Kind: "codex", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{Streaming: proto.CapabilitySupported, Steering: proto.CapabilitySupported, Resume: proto.CapabilitySupported, DurableTurns: proto.CapabilityFromBool(missing != "durable_turns"), DurableInputReceipts: proto.CapabilityFromBool(missing != "durable_input_receipts"), WebSearchControl: proto.CapabilityFromBool(missing != "web_search_control"), TextVerbosity: proto.CapabilityFromBool(missing != "text_verbosity"), ExecutionControls: proto.CapabilityFromBool(missing != "execution_controls"), SubagentControl: proto.CapabilityFromBool(missing != "subagent_control"), ToolObservations: proto.CapabilityFromBool(missing != "tool_observations")})}}}) + h.write("", proto.TypeHeartbeat, proto.HeartbeatPayload{SupportedAgentKinds: []proto.SupportedAgentKind{{Kind: "codex", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{Streaming: proto.CapabilitySupported, Steering: proto.CapabilitySupported, Resume: proto.CapabilitySupported, DurableTurns: proto.CapabilityFromBool(missing != "durable_turns"), DurableInputReceipts: proto.CapabilityFromBool(missing != "durable_input_receipts"), Preparation: proto.CapabilityFromBool(missing != "preparation"), WebSearchControl: proto.CapabilityFromBool(missing != "web_search_control"), TextVerbosity: proto.CapabilityFromBool(missing != "text_verbosity"), ExecutionControls: proto.CapabilityFromBool(missing != "execution_controls"), SubagentControl: proto.CapabilityFromBool(missing != "subagent_control"), ToolObservations: proto.CapabilityFromBool(missing != "tool_observations"), EnvironmentNone: proto.CapabilityFromBool(missing != "environment_none")})}}}) deadline := time.Now().Add(3 * time.Second) for { peer, _ := h.registry.LookupDevice(h.device.ID) info, _, _ := peer.AgentKindStatus("codex") - if info.Capabilities.DurableInputReceipts == (missing != "durable_input_receipts") && info.Capabilities.ExecutionControls == (missing != "execution_controls") && info.Capabilities.DurableTurns == (missing != "durable_turns") && info.Capabilities.WebSearchControl == (missing != "web_search_control") && info.Capabilities.TextVerbosity == (missing != "text_verbosity") && info.Capabilities.SubagentControl == (missing != "subagent_control") && info.Capabilities.ToolObservations == (missing != "tool_observations") { + if info.Capabilities.DurableInputReceipts == (missing != "durable_input_receipts") && info.Capabilities.Preparation == (missing != "preparation") && info.Capabilities.ExecutionControls == (missing != "execution_controls") && info.Capabilities.DurableTurns == (missing != "durable_turns") && info.Capabilities.WebSearchControl == (missing != "web_search_control") && info.Capabilities.TextVerbosity == (missing != "text_verbosity") && info.Capabilities.SubagentControl == (missing != "subagent_control") && info.Capabilities.ToolObservations == (missing != "tool_observations") && info.Capabilities.EnvironmentNone == (missing != "environment_none") { break } if time.Now().After(deadline) { - t.Fatal("legacy heartbeat not registered") + t.Fatal("heartbeat not registered") } time.Sleep(10 * time.Millisecond) } - first := h.message("legacy", "Run") - if _, err := h.d.Run(context.Background(), h.tenant, h.session.ID, first.TurnID); err == nil { - t.Fatal("legacy daemon accepted") + first := h.message("missing-capability", "Run") + if _, err := h.d.Run(context.Background(), h.tenant, h.session.ID, first.TurnID); err == nil || err.Error() != tc.message { + t.Fatalf("Run error = %v, want %q", err, tc.message) } turn, err := h.s.GetTurn(context.Background(), h.tenant, h.session.ID, first.TurnID) if err != nil || turn.Status != store.TurnQueued { - t.Fatal("unsupported daemon claimed work") + t.Fatal("Runtime without a required capability claimed work") } }) } diff --git a/services/core/internal/store/environment_directory_active_test.go b/services/core/internal/store/environment_directory_active_test.go index 7cc1914e..bc7d128f 100644 --- a/services/core/internal/store/environment_directory_active_test.go +++ b/services/core/internal/store/environment_directory_active_test.go @@ -8,7 +8,7 @@ import ( ) func TestEnvironmentDirectoryActiveRunUsesExistingOwner(t *testing.T) { - h, w, environment := directoryWorker(t, true) + h, w, environment := directoryWorker(t) awaitFixtureCapabilities(t, h, workerEnvironmentCapabilities()) pending, err := h.s.ReserveEnvironmentInput(t.Context(), h.tenant, h.session.ID, "execute", []store.Input{{Kind: "message", Payload: []byte(`{"text":"work"}`)}}) if err != nil { diff --git a/services/core/internal/store/environment_directory_test.go b/services/core/internal/store/environment_directory_test.go index 27203267..f28339ae 100644 --- a/services/core/internal/store/environment_directory_test.go +++ b/services/core/internal/store/environment_directory_test.go @@ -18,7 +18,7 @@ type directoryResult struct { err error } -func directoryWorker(t *testing.T, execute ...bool) (*dispatchHarness, *execution.Worker, store.Environment) { +func directoryWorker(t *testing.T) (*dispatchHarness, *execution.Worker, store.Environment) { t.Helper() h := newDispatchHarnessForSession(t, []byte(`{"agent":{"model":"unavailable-model"},"environment":{"type":"self_hosted","workspace_directory":"/workspace"}}`), true) environment, err := h.s.GetSessionEnvironment(t.Context(), h.tenant, h.session.ID) @@ -34,13 +34,6 @@ func directoryWorker(t *testing.T, execute ...bool) (*dispatchHarness, *executio info, _, _ := peer.AgentKindStatus("codex") return info.Capabilities.WorkspaceReadPreparation }) - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { - if len(execute) > 0 && execute[0] { - return nil, nil - } - t.Error("read resolved model credentials") - return nil, errors.New("no credentials") - } w, err := execution.StartWorker(t.Context(), h.d) if err != nil { t.Fatal(err) diff --git a/services/core/internal/store/execution_messages_test.go b/services/core/internal/store/execution_messages_test.go index 56ee5698..d12e7072 100644 --- a/services/core/internal/store/execution_messages_test.go +++ b/services/core/internal/store/execution_messages_test.go @@ -24,7 +24,7 @@ func TestExecutionNegotiatesAndPersistsMessageObservations(t *testing.T) { } h.write(first.TurnID, proto.TypeDone, proto.DonePayload{}) h.finished(result, store.TurnCompleted) - h.write("", proto.TypeHeartbeat, proto.HeartbeatPayload{SupportedAgentKinds: []proto.SupportedAgentKind{{Kind: "codex", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{Streaming: proto.CapabilitySupported, Steering: proto.CapabilitySupported, Resume: proto.CapabilitySupported, DurableTurns: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported, WebSearchControl: proto.CapabilitySupported, TextVerbosity: proto.CapabilitySupported, ExecutionControls: proto.CapabilitySupported, SubagentControl: proto.CapabilitySupported, ToolObservations: proto.CapabilitySupported, MessageItems: proto.CapabilitySupported, NativeSessionRecovery: proto.CapabilitySupported, Preparation: proto.CapabilitySupported})}}}) + h.write("", proto.TypeHeartbeat, proto.HeartbeatPayload{SupportedAgentKinds: []proto.SupportedAgentKind{{Kind: "codex", Available: true, Capabilities: prototest.Capabilities(proto.AgentKindCapabilities{Streaming: proto.CapabilitySupported, Steering: proto.CapabilitySupported, Resume: proto.CapabilitySupported, DurableTurns: proto.CapabilitySupported, DurableInputReceipts: proto.CapabilitySupported, WebSearchControl: proto.CapabilitySupported, TextVerbosity: proto.CapabilitySupported, ExecutionControls: proto.CapabilitySupported, SubagentControl: proto.CapabilitySupported, ToolObservations: proto.CapabilitySupported, MessageItems: proto.CapabilitySupported, NativeSessionRecovery: proto.CapabilitySupported, Preparation: proto.CapabilitySupported, EnvironmentNone: proto.CapabilitySupported})}}}) deadline := time.Now().Add(3 * time.Second) for { peer, err := h.registry.LookupDevice(h.device.ID) diff --git a/services/core/internal/store/function_execution_native_test.go b/services/core/internal/store/function_execution_native_test.go index 86811241..8880a11e 100644 --- a/services/core/internal/store/function_execution_native_test.go +++ b/services/core/internal/store/function_execution_native_test.go @@ -1,29 +1,26 @@ package store_test import ( - "context" "encoding/json" "fmt" "testing" + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" ) func TestNativeFunctionExecutionPersistsCallsResultsAndContinuity(t *testing.T) { h, ctx, home := nativeDispatchHarness(t) + model, output, requests := nativeFunctionModel(t, home) + defer model.Close() var err error - h.session, err = h.s.CreateSession(ctx, h.tenant, store.CreateSessionInput{Creator: store.FixtureCreator(), Engine: "codex", IdempotencyKey: "native-functions", Configuration: json.RawMessage(functionConfiguration)}) + h.session, err = h.s.CreateSession(ctx, h.tenant, store.CreateSessionInput{Creator: store.FixtureCreator(), Engine: "codex", IdempotencyKey: "native-functions", Configuration: json.RawMessage(functionConfiguration), ModelProvider: nativeModelProvider(model), ModelProviderSource: v1.ModelProviderSourceDeployment}) if err != nil { t.Fatal(err) } if err := h.s.BindSessionDevice(ctx, h.tenant, h.session.ID, h.device.ID); err != nil { t.Fatal(err) } - model, output, requests := nativeFunctionModel(t, home) - defer model.Close() - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { - return map[string]any{"model_provider": map[string]any{"protocol": "responses", "base_url": model.URL + "/v1", "api_key": "synthetic-test-token"}}, nil - } nativeID := "" for index := range 3 { input := h.message(fmt.Sprint(index), "Look up ticket 42") diff --git a/services/core/internal/store/function_images_native_test.go b/services/core/internal/store/function_images_native_test.go index c266a37a..b3524e4e 100644 --- a/services/core/internal/store/function_images_native_test.go +++ b/services/core/internal/store/function_images_native_test.go @@ -13,7 +13,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/api" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/execution" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtimedevice" - "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" "github.com/google/uuid" ) @@ -22,24 +21,12 @@ func TestNativeFunctionImagePublicExecution(t *testing.T) { if python == "" || binary == "" || root == "" || optionsFile == "" { t.Skip("native daemon, fixed SDK, real model options and evidence directory required") } - raw, err := os.ReadFile(optionsFile) - if err != nil { - t.Fatal(err) - } - var options map[string]any - if json.Unmarshal(raw, &options) != nil { - t.Fatal("invalid private options") - } - model, _ := options["model"].(string) - if model == "" { - t.Fatal("real model required") - } + model, provider := readNativeModelDefaults(t, optionsFile) kind := os.Getenv("OAC_TEST_FUNCTION_IMAGE_ENGINE") if kind != "codex" && kind != "claude_sdk" { t.Fatal("image acceptance requires a specified native engine") } h := newDispatchHarness(t) - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { return options, nil } home, err := os.MkdirTemp(root, "function-image-public-") if err != nil { t.Fatal(err) @@ -68,7 +55,7 @@ func TestNativeFunctionImagePublicExecution(t *testing.T) { if err != nil { t.Fatal(err) } - handler, err := api.NewHandler(h.s, auth, kind, api.WithExecution(worker), api.WithExecutionPolicy(h.d.Policy)) + handler, err := api.NewHandler(h.s, auth, kind, api.WithExecution(worker), api.WithExecutionPolicy(h.d.Policy), nativeDeploymentDefaults(model, provider)) if err != nil { t.Fatal(err) } @@ -88,7 +75,7 @@ func TestNativeFunctionImagePublicExecution(t *testing.T) { Session string `json:"session"` Calls []struct{ Turn, Call string } `json:"calls"` } - raw, err = os.ReadFile(evidence) + raw, err := os.ReadFile(evidence) if err != nil || json.Unmarshal(raw, &proof) != nil || len(proof.Calls) != 4 { t.Fatal("invalid evidence", err) } diff --git a/services/core/internal/store/function_model_test.go b/services/core/internal/store/function_model_test.go index 814f477b..4272b211 100644 --- a/services/core/internal/store/function_model_test.go +++ b/services/core/internal/store/function_model_test.go @@ -38,7 +38,7 @@ func nativeFunctionModel(t *testing.T, home string) (*httptest.Server, []any, *a func nativeFunctionResultsModel(t *testing.T, home string, results []any) (*httptest.Server, *atomic.Int32) { t.Helper() var requests atomic.Int32 - model := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + model := nativeModelServer(t, home, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body map[string]any if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Error(err) diff --git a/services/core/internal/store/function_public_native_test.go b/services/core/internal/store/function_public_native_test.go index 76bf743e..3a293b97 100644 --- a/services/core/internal/store/function_public_native_test.go +++ b/services/core/internal/store/function_public_native_test.go @@ -10,10 +10,10 @@ import ( "testing" "time" + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/api" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/execution" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtimedevice" - "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" "github.com/google/uuid" ) @@ -25,10 +25,7 @@ func TestNativePublicFunctionExecution(t *testing.T) { h, ctx, home := nativeDispatchHarness(t) model, output, requests := nativeFunctionModel(t, home) defer model.Close() - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { - return map[string]any{"enable_features": []any{"multi_agent", "multi_agent_v2"}, "model_provider": map[string]any{"protocol": "responses", "base_url": model.URL + "/v1", "api_key": "synthetic-test-token"}}, nil - } - serverURL, token := nativePublicFunctionServer(t, h, ctx) + serverURL, token := nativePublicFunctionServer(t, h, ctx, nativeModelProvider(model)) outputPath, proofPath := filepath.Join(home, "function-output.json"), filepath.Join(home, "public-functions.json") raw, _ := json.Marshal(output) if err := os.WriteFile(outputPath, raw, 0600); err != nil { @@ -63,7 +60,7 @@ func TestNativePublicFunctionExecution(t *testing.T) { t.Logf("Official SDK configured functions, native text/image/error results, application receipts, next Turn and cancellation passed; evidence %s", home) } -func nativePublicFunctionServer(t *testing.T, h *dispatchHarness, ctx context.Context) (string, string) { +func nativePublicFunctionServer(t *testing.T, h *dispatchHarness, ctx context.Context, provider *v1.ModelProviderInput) (string, string) { t.Helper() worker, err := execution.StartWorker(ctx, h.d) if err != nil { @@ -86,7 +83,7 @@ func nativePublicFunctionServer(t *testing.T, h *dispatchHarness, ctx context.Co if err != nil { t.Fatal(err) } - handler, err := api.NewHandler(h.s, auth, "codex", api.WithExecution(worker)) + handler, err := api.NewHandler(h.s, auth, "codex", api.WithExecution(worker), nativeDeploymentDefaults("gpt-5.5", provider)) if err != nil { t.Fatal(err) } diff --git a/services/core/internal/store/function_stream_native_test.go b/services/core/internal/store/function_stream_native_test.go index c2197555..806f5491 100644 --- a/services/core/internal/store/function_stream_native_test.go +++ b/services/core/internal/store/function_stream_native_test.go @@ -1,14 +1,11 @@ package store_test import ( - "context" "encoding/json" "os" "os/exec" "path/filepath" "testing" - - "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" ) func TestNativePublicFunctionStreamHelper(t *testing.T) { @@ -24,10 +21,7 @@ func TestNativePublicFunctionStreamHelper(t *testing.T) { "Tool handler failed.", }) defer model.Close() - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { - return map[string]any{"model_provider": map[string]any{"protocol": "responses", "base_url": model.URL + "/v1", "api_key": "synthetic-test-token"}}, nil - } - serverURL, token := nativePublicFunctionServer(t, h, ctx) + serverURL, token := nativePublicFunctionServer(t, h, ctx, nativeModelProvider(model)) proofPath := filepath.Join(home, "public-function-stream.json") command := exec.CommandContext(ctx, python, "../../tests/official_function_stream.py", serverURL, token, proofPath) if log, err := command.CombinedOutput(); err != nil { diff --git a/services/core/internal/store/harness_onboarding_test.go b/services/core/internal/store/harness_onboarding_test.go index 89f64746..91f4b880 100644 --- a/services/core/internal/store/harness_onboarding_test.go +++ b/services/core/internal/store/harness_onboarding_test.go @@ -31,13 +31,13 @@ func TestThirdHarnessPublicOnboarding(t *testing.T) { profile := enginetest.Profile(nil) profile.ConfigurationValidation = engine.AdditionalValidation profile.ToolsValidation = engine.AdditionalValidation - profile.ValidateConfiguration = func(a v1.Agent, _ *v1.Environment, _ bool) error { + profile.ValidateConfiguration = func(a v1.Agent, _ *v1.Environment) error { if a.Text.Verbosity != "medium" || a.Text.Format.Type != "text" || a.MultiAgent.Enabled || a.Reasoning.Effort != nil || a.Reasoning.Summary != nil || a.ServiceTier != "auto" { return engine.ErrInvalidInput } return nil } - profile.ValidateTools = func(_ *v1.Environment, _ bool, f []proto.FunctionTool, m []proto.MCPHTTPServer) error { + profile.ValidateTools = func(_ *v1.Environment, f []proto.FunctionTool, m []proto.MCPHTTPServer) error { if len(f)+len(m) > 0 { return engine.ErrInvalidInput } diff --git a/services/core/internal/store/local_environment_worker_test.go b/services/core/internal/store/local_environment_worker_test.go index 0517f719..11350d10 100644 --- a/services/core/internal/store/local_environment_worker_test.go +++ b/services/core/internal/store/local_environment_worker_test.go @@ -31,11 +31,6 @@ func localWorker(t *testing.T, scoped, execute bool) (*dispatchHarness, *executi caps.Streaming, caps.Steering, caps.DurableTurns, caps.DurableInputReceipts = proto.CapabilitySupported, proto.CapabilitySupported, proto.CapabilitySupported, proto.CapabilitySupported caps.WebSearchControl, caps.TextVerbosity, caps.ExecutionControls = proto.CapabilitySupported, proto.CapabilitySupported, proto.CapabilitySupported caps.SubagentControl, caps.ToolObservations = proto.CapabilitySupported, proto.CapabilitySupported - } else { - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { - t.Error("directory read requested model credentials") - return nil, errors.New("model unavailable") - } } h.write("", proto.TypeHeartbeat, proto.HeartbeatPayload{SupportedAgentKinds: []proto.SupportedAgentKind{{Kind: "codex", Available: true, Capabilities: caps}}}) awaitDaemonRemoteCondition(t, t.Context(), 3*time.Second, "local capability", func() bool { diff --git a/services/core/internal/store/mcode_public_native_test.go b/services/core/internal/store/mcode_public_native_test.go index e80be824..4d616faf 100644 --- a/services/core/internal/store/mcode_public_native_test.go +++ b/services/core/internal/store/mcode_public_native_test.go @@ -14,7 +14,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/api" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/execution" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtimedevice" - "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" "github.com/google/uuid" ) @@ -25,20 +24,8 @@ func TestNativeMCodePublicExecution(t *testing.T) { if python == "" || binary == "" || root == "" || optionsFile == "" { t.Skip("native daemon, fixed SDK, private real-model options and proof directory required") } - raw, err := os.ReadFile(optionsFile) - if err != nil { - t.Fatal(err) - } - var options map[string]any - if json.Unmarshal(raw, &options) != nil { - t.Fatal("invalid private options") - } - model, _ := options["model"].(string) - if model == "" { - t.Fatal("real model required") - } + model, provider := readNativeModelDefaults(t, optionsFile) h := newDispatchHarness(t) - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { return options, nil } home, err := os.MkdirTemp(root, "mcode-public-") if err != nil { t.Fatal(err) @@ -67,7 +54,7 @@ func TestNativeMCodePublicExecution(t *testing.T) { if err != nil { t.Fatal(err) } - handler, err := api.NewHandler(h.s, auth, "mcode", api.WithExecution(worker)) + handler, err := api.NewHandler(h.s, auth, "mcode", api.WithExecution(worker), nativeDeploymentDefaults(model, provider)) if err != nil { t.Fatal(err) } @@ -86,12 +73,7 @@ func TestNativeMCodePublicExecution(t *testing.T) { _ = json.Unmarshal(data, &identity) if page, e := h.s.ListTurns(ctx, h.tenant, identity.Session, "", 100, true); e == nil { diagnostic, _ := json.Marshal(page) - text := string(diagnostic) - if provider, ok := options["model_provider"].(map[string]any); ok { - if key, ok := provider["api_key"].(string); ok && key != "" { - text = strings.ReplaceAll(text, key, "[REDACTED]") - } - } + text := strings.ReplaceAll(string(diagnostic), provider.APIKey, "[REDACTED]") _ = os.WriteFile(filepath.Join(home, "failed-turns.json"), []byte(text), 0600) } t.Fatalf("public mcode %s failed: %v %s; evidence %s", stage, err, log, home) diff --git a/services/core/internal/store/message_images_native_test.go b/services/core/internal/store/message_images_native_test.go index ad303483..ad9ce3b6 100644 --- a/services/core/internal/store/message_images_native_test.go +++ b/services/core/internal/store/message_images_native_test.go @@ -13,7 +13,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/api" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/execution" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtimedevice" - "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" "github.com/google/uuid" ) @@ -22,24 +21,12 @@ func TestNativeMessageImagePublicExecution(t *testing.T) { if python == "" || binary == "" || root == "" || optionsFile == "" { t.Skip("native daemon, fixed SDK, real model options and evidence directory required") } - raw, err := os.ReadFile(optionsFile) - if err != nil { - t.Fatal(err) - } - var options map[string]any - if json.Unmarshal(raw, &options) != nil { - t.Fatal("invalid private options") - } - model, _ := options["model"].(string) - if model == "" { - t.Fatal("real model required") - } + model, provider := readNativeModelDefaults(t, optionsFile) kind := os.Getenv("OAC_TEST_MESSAGE_IMAGE_ENGINE") if kind != "codex" && kind != "claude_sdk" { t.Fatal("image acceptance requires a specified native engine") } h := newDispatchHarness(t) - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { return options, nil } home, err := os.MkdirTemp(root, "message-image-public-") if err != nil { t.Fatal(err) @@ -68,7 +55,7 @@ func TestNativeMessageImagePublicExecution(t *testing.T) { if err != nil { t.Fatal(err) } - handler, err := api.NewHandler(h.s, auth, kind, api.WithExecution(worker), api.WithExecutionPolicy(h.d.Policy)) + handler, err := api.NewHandler(h.s, auth, kind, api.WithExecution(worker), api.WithExecutionPolicy(h.d.Policy), nativeDeploymentDefaults(model, provider)) if err != nil { t.Fatal(err) } @@ -89,7 +76,7 @@ func TestNativeMessageImagePublicExecution(t *testing.T) { Turn string `json:"turn"` Call string `json:"call"` } - raw, err = os.ReadFile(evidence) + raw, err := os.ReadFile(evidence) if err != nil || json.Unmarshal(raw, &proof) != nil { t.Fatal("invalid evidence", err) } diff --git a/services/core/internal/store/model_protocol_native_test.go b/services/core/internal/store/model_protocol_native_test.go index 71457276..9dcf766a 100644 --- a/services/core/internal/store/model_protocol_native_test.go +++ b/services/core/internal/store/model_protocol_native_test.go @@ -57,11 +57,6 @@ func TestNativeModelProtocolPublicExecution(t *testing.T) { t.Fatal("cannot resolve private model protocol options") } h := newDispatchHarness(t) - // Deliberately leave Dispatcher.Options unset. Only the public SDK Session - // creation freezes the deployment default used by the environment:none profile. - if h.d.Options != nil { - t.Fatal("fixture must not override public model provider admission") - } home, err := os.MkdirTemp(root, "model-protocol-public-") if err != nil { t.Fatal("cannot create controlled evidence directory") diff --git a/services/core/internal/store/native_daemon_test.go b/services/core/internal/store/native_daemon_test.go index bcc22203..db3e6305 100644 --- a/services/core/internal/store/native_daemon_test.go +++ b/services/core/internal/store/native_daemon_test.go @@ -1,13 +1,31 @@ package store_test import ( + "bytes" "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" "encoding/json" + "encoding/pem" + "io" + "math/big" + "net" + "net/http" + "net/http/httptest" "os" "os/exec" "path/filepath" "testing" "time" + + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" + "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/api" + "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" + "github.com/google/uuid" ) func nativeDispatchHarness(t *testing.T) (*dispatchHarness, context.Context, string) { @@ -52,7 +70,9 @@ func startNativeDispatchDaemon(t *testing.T, h *dispatchHarness, home, binary st } t.Cleanup(func() { _ = daemonLog.Close() }) cmd := exec.Command(binary, "connect", "--profile", "execution") - cmd.Env = append(os.Environ(), "OAC_RUNTIME_HOME="+home) + // The device trusts the synthetic model server's certificate, which + // nativeModelServer writes before the first Turn starts Codex. + cmd.Env = append(os.Environ(), "OAC_RUNTIME_HOME="+home, "CODEX_CA_CERTIFICATE="+nativeModelCertificate(home)) cmd.Stdout, cmd.Stderr = daemonLog, daemonLog if err = cmd.Start(); err != nil { t.Fatal(err) @@ -82,3 +102,83 @@ func startNativeDispatchDaemon(t *testing.T, h *dispatchHarness, home, binary st time.Sleep(50 * time.Millisecond) } } + +func nativeModelCertificate(home string) string { return filepath.Join(home, "model-ca.pem") } + +// nativeModelServer serves a synthetic model over HTTPS, as the model provider +// contract requires, and writes the issuing CA for the native daemon to trust. +// Codex verifies with webpki, which rejects the self-signed CA certificate that +// httptest serves by default, so the server gets a leaf issued by a test CA. +func nativeModelServer(t *testing.T, home string, handler http.Handler) *httptest.Server { + t.Helper() + now := time.Now() + caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + caTemplate := &x509.Certificate{SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "synthetic model CA"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(24 * time.Hour), IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign} + caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey) + if err != nil { + t.Fatal(err) + } + ca, err := x509.ParseCertificate(caDER) + if err != nil { + t.Fatal(err) + } + leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + leafTemplate := &x509.Certificate{SerialNumber: big.NewInt(2), Subject: pkix.Name{CommonName: "127.0.0.1"}, IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(24 * time.Hour), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}} + leafDER, err := x509.CreateCertificate(rand.Reader, leafTemplate, ca, &leafKey.PublicKey, caKey) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(nativeModelCertificate(home), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caDER}), 0600); err != nil { + t.Fatal(err) + } + model := httptest.NewUnstartedServer(handler) + model.TLS = &tls.Config{Certificates: []tls.Certificate{{Certificate: [][]byte{leafDER}, PrivateKey: leafKey}}} + model.StartTLS() + return model +} + +// nativeModelProvider is the provider bundle that reaches a synthetic model server. +func nativeModelProvider(model *httptest.Server) *v1.ModelProviderInput { + return &v1.ModelProviderInput{Protocol: "responses", BaseURL: model.URL + "/v1", APIKey: "synthetic-test-token"} +} + +// nativeDeploymentDefaults makes provider the deployment default model provider, +// which public environment:none Sessions freeze at creation. +func nativeDeploymentDefaults(model string, provider *v1.ModelProviderInput) api.Option { + revision := uuid.New() + return api.WithModelProviderDefaults(func(context.Context, string) (*store.DeploymentModelProviderSnapshot, error) { + return &store.DeploymentModelProviderSnapshot{Model: model, Provider: provider, Revision: revision}, nil + }) +} + +// readNativeModelDefaults reads a private real-model file holding exactly the +// deployment default an operator would configure: {"model", "model_provider"}. +func readNativeModelDefaults(t *testing.T, path string) (string, *v1.ModelProviderInput) { + t.Helper() + raw, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + var defaults struct { + Model string `json:"model"` + ModelProvider *v1.ModelProviderInput `json:"model_provider"` + } + decoder := json.NewDecoder(bytes.NewReader(raw)) + decoder.DisallowUnknownFields() + if decoder.Decode(&defaults) != nil || decoder.Decode(new(any)) != io.EOF || defaults.ModelProvider == nil { + t.Fatal("private options must hold only model and model_provider") + } + if defaults.ModelProvider.Validate() != nil { + t.Fatal("invalid private model_provider") + } + if defaults.Model == "" { + t.Fatal("real model required") + } + return defaults.Model, defaults.ModelProvider +} diff --git a/services/core/internal/store/native_environment_test.go b/services/core/internal/store/native_environment_test.go index 81582586..2796c084 100644 --- a/services/core/internal/store/native_environment_test.go +++ b/services/core/internal/store/native_environment_test.go @@ -1,11 +1,9 @@ package store_test import ( - "context" "encoding/json" "fmt" "net/http" - "net/http/httptest" "os" "path/filepath" "strings" @@ -13,24 +11,15 @@ import ( "testing" "time" + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" ) func TestNativeNoExecutionEnvironment(t *testing.T) { h, ctx, home := nativeDispatchHarness(t) - var err error - config, _ := json.Marshal(map[string]any{"agent": map[string]string{"model": "gpt-5.5", "instructions": "Keep this instruction."}, "environment": map[string]string{"type": "none"}}) - h.session, err = h.s.CreateSession(ctx, h.tenant, store.CreateSessionInput{Creator: store.FixtureCreator(), Engine: "codex", IdempotencyKey: "native-session", Configuration: config}) - if err != nil { - t.Fatal(err) - } - if err = h.s.BindSessionDevice(ctx, h.tenant, h.session.ID, h.device.ID); err != nil { - t.Fatal(err) - } - var requests atomic.Int32 marker := filepath.Join(home, "must-not-exist") - model := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + model := nativeModelServer(t, home, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" || !strings.HasSuffix(r.URL.Path, "/responses") { http.NotFound(w, r) return @@ -106,8 +95,15 @@ func TestNativeNoExecutionEnvironment(t *testing.T) { send("response.completed", map[string]any{"response": map[string]any{"id": fmt.Sprintf("response_%d", n), "object": "response", "created_at": time.Now().Unix(), "status": "completed", "model": "gpt-5.5", "output": []any{item}, "usage": map[string]any{"input_tokens": 10, "output_tokens": 3, "total_tokens": 13, "input_tokens_details": map[string]any{"cached_tokens": 4}, "output_tokens_details": map[string]any{"reasoning_tokens": 2}}}}) })) defer model.Close() - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { - return map[string]any{"enable_features": []any{"multi_agent", "multi_agent_v2"}, "model_verbosity": "high", "web_search": "live", "model_provider": map[string]any{"protocol": "responses", "base_url": model.URL + "/v1", "api_key": "synthetic-test-token"}}, nil + provider := nativeModelProvider(model) + config, _ := json.Marshal(map[string]any{"agent": map[string]string{"model": "gpt-5.5", "instructions": "Keep this instruction."}, "environment": map[string]string{"type": "none"}}) + var err error + h.session, err = h.s.CreateSession(ctx, h.tenant, store.CreateSessionInput{Creator: store.FixtureCreator(), Engine: "codex", IdempotencyKey: "native-session", Configuration: config, ModelProvider: provider, ModelProviderSource: v1.ModelProviderSourceDeployment}) + if err != nil { + t.Fatal(err) + } + if err = h.s.BindSessionDevice(ctx, h.tenant, h.session.ID, h.device.ID); err != nil { + t.Fatal(err) } first := h.message("first", "Return an answer.") h.finished(h.run(ctx, first.TurnID), store.TurnCompleted) @@ -140,6 +136,6 @@ func TestNativeNoExecutionEnvironment(t *testing.T) { if answers != 2 { t.Fatal(page) } - verifyNativePublicExecution(t, h, ctx, home) - t.Logf("Native environment none: command rejected, caller override ignored, two Turns resumed and recovered. Evidence: %s", home) + verifyNativePublicExecution(t, h, ctx, home, provider) + t.Logf("Native environment none: command rejected, two Turns resumed and recovered. Evidence: %s", home) } diff --git a/services/core/internal/store/native_public_execution_test.go b/services/core/internal/store/native_public_execution_test.go index 8b110a40..f8a95250 100644 --- a/services/core/internal/store/native_public_execution_test.go +++ b/services/core/internal/store/native_public_execution_test.go @@ -9,13 +9,14 @@ import ( "testing" "time" + v1 "github.com/MiniMax-AI/OpenAgentCore/contracts/agents-api/v1" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/api" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/execution" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtimedevice" "github.com/google/uuid" ) -func verifyNativePublicExecution(t *testing.T, h *dispatchHarness, parent context.Context, evidence string) { +func verifyNativePublicExecution(t *testing.T, h *dispatchHarness, parent context.Context, evidence string, provider *v1.ModelProviderInput) { t.Helper() python := os.Getenv("OAC_TEST_OFFICIAL_SDK_PYTHON") if python == "" { @@ -43,7 +44,7 @@ func verifyNativePublicExecution(t *testing.T, h *dispatchHarness, parent contex if err != nil { t.Fatal(err) } - handler, err := api.NewHandler(h.s, auth, "codex", api.WithExecution(worker)) + handler, err := api.NewHandler(h.s, auth, "codex", api.WithExecution(worker), nativeDeploymentDefaults("gpt-5.5", provider)) if err != nil { t.Fatal(err) } diff --git a/services/core/internal/store/structured_output_native_test.go b/services/core/internal/store/structured_output_native_test.go index 1d986ec2..7a5c19d0 100644 --- a/services/core/internal/store/structured_output_native_test.go +++ b/services/core/internal/store/structured_output_native_test.go @@ -13,7 +13,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/api" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/execution" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtimedevice" - "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" "github.com/google/uuid" ) @@ -22,20 +21,8 @@ func TestNativeStructuredOutputPublicExecution(t *testing.T) { if python == "" || binary == "" || root == "" || optionsFile == "" { t.Skip("native daemon, fixed SDK, real model options and evidence directory required") } - raw, err := os.ReadFile(optionsFile) - if err != nil { - t.Fatal(err) - } - var options map[string]any - if json.Unmarshal(raw, &options) != nil { - t.Fatal("invalid private options") - } - model, _ := options["model"].(string) - if model == "" { - t.Fatal("real model required") - } + model, provider := readNativeModelDefaults(t, optionsFile) h := newDispatchHarness(t) - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { return options, nil } home, err := os.MkdirTemp(root, "structured-public-") if err != nil { t.Fatal(err) @@ -64,7 +51,7 @@ func TestNativeStructuredOutputPublicExecution(t *testing.T) { if err != nil { t.Fatal(err) } - handler, err := api.NewHandler(h.s, auth, "claude_sdk", api.WithExecution(worker)) + handler, err := api.NewHandler(h.s, auth, "claude_sdk", api.WithExecution(worker), nativeDeploymentDefaults(model, provider)) if err != nil { t.Fatal(err) } @@ -85,7 +72,7 @@ func TestNativeStructuredOutputPublicExecution(t *testing.T) { Turn string `json:"turn"` Call string `json:"call"` } - raw, err = os.ReadFile(evidence) + raw, err := os.ReadFile(evidence) if err != nil || json.Unmarshal(raw, &proof) != nil { t.Fatal("invalid evidence", err) } diff --git a/services/core/internal/store/subagent_dispatch_test.go b/services/core/internal/store/subagent_dispatch_test.go index 4a2a2002..ebc8df53 100644 --- a/services/core/internal/store/subagent_dispatch_test.go +++ b/services/core/internal/store/subagent_dispatch_test.go @@ -16,8 +16,8 @@ func TestSubagentIdentityUsesLeasedDispatchJournal(t *testing.T) { h := newDispatchHarness(t) ctx := t.Context() configuration, _ := json.Marshal(map[string]any{ - "agent": map[string]any{"id": "agent_root", "model": "test-model", "multi_agent": map[string]any{"enabled": enabled, "max_concurrent_subagents": 3}}, - "daemon": map[string]string{"work_dir": "/tmp"}, + "agent": map[string]any{"id": "agent_root", "model": "test-model", "multi_agent": map[string]any{"enabled": enabled, "max_concurrent_subagents": 3}}, + "environment": map[string]string{"type": "none"}, }) var err error h.session, err = h.s.CreateSession(ctx, h.tenant, store.CreateSessionInput{Creator: store.FixtureCreator(), Engine: "codex", IdempotencyKey: "identity-dispatch", Configuration: configuration}) diff --git a/services/core/internal/store/tool_policy_native_test.go b/services/core/internal/store/tool_policy_native_test.go index 9b528c10..22982c98 100644 --- a/services/core/internal/store/tool_policy_native_test.go +++ b/services/core/internal/store/tool_policy_native_test.go @@ -13,7 +13,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/api" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/execution" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtimedevice" - "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" "github.com/google/uuid" ) @@ -28,20 +27,8 @@ func TestNativeToolPolicyPublicExecution(t *testing.T) { if kind != "codex" && kind != "claude_sdk" && kind != "mcode" { t.Fatal("tool policy acceptance requires codex, claude_sdk or mcode") } - raw, err := os.ReadFile(optionsFile) - if err != nil { - t.Fatal(err) - } - var options map[string]any - if json.Unmarshal(raw, &options) != nil { - t.Fatal("invalid private options") - } - model, _ := options["model"].(string) - if model == "" { - t.Fatal("real model required") - } + model, provider := readNativeModelDefaults(t, optionsFile) h := newDispatchHarness(t) - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { return options, nil } home, err := os.MkdirTemp(root, "tool-policy-"+kind+"-") if err != nil { t.Fatal(err) @@ -70,7 +57,7 @@ func TestNativeToolPolicyPublicExecution(t *testing.T) { if err != nil { t.Fatal(err) } - handler, err := api.NewHandler(h.s, auth, kind, api.WithExecution(worker), api.WithExecutionPolicy(h.d.Policy)) + handler, err := api.NewHandler(h.s, auth, kind, api.WithExecution(worker), api.WithExecutionPolicy(h.d.Policy), nativeDeploymentDefaults(model, provider)) if err != nil { t.Fatal(err) } @@ -92,7 +79,7 @@ func TestNativeToolPolicyPublicExecution(t *testing.T) { FirstTurn string `json:"first_turn"` } `json:"sessions"` } - raw, err = os.ReadFile(evidence) + raw, err := os.ReadFile(evidence) if err != nil || json.Unmarshal(raw, &proof) != nil || len(proof.Sessions) != 4 { t.Fatal("invalid public evidence", err) } diff --git a/services/core/internal/store/tool_search_native_test.go b/services/core/internal/store/tool_search_native_test.go index 4e98d44b..4e8e48a7 100644 --- a/services/core/internal/store/tool_search_native_test.go +++ b/services/core/internal/store/tool_search_native_test.go @@ -13,7 +13,6 @@ import ( "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/api" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/execution" "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/runtimedevice" - "github.com/MiniMax-AI/OpenAgentCore/services/core/internal/store" "github.com/google/uuid" ) @@ -22,20 +21,8 @@ func TestNativeToolSearchPublicExecution(t *testing.T) { if python == "" || binary == "" || root == "" || optionsFile == "" { t.Skip("native daemon, fixed SDK, real model options and evidence directory required") } - raw, err := os.ReadFile(optionsFile) - if err != nil { - t.Fatal(err) - } - var options map[string]any - if json.Unmarshal(raw, &options) != nil { - t.Fatal("invalid private options") - } - model, _ := options["model"].(string) - if model == "" { - t.Fatal("real model required") - } + model, provider := readNativeModelDefaults(t, optionsFile) h := newDispatchHarness(t) - h.d.Options = func(context.Context, store.Session) (map[string]any, error) { return options, nil } home, err := os.MkdirTemp(root, "tool-search-public-") if err != nil { t.Fatal(err) @@ -64,7 +51,7 @@ func TestNativeToolSearchPublicExecution(t *testing.T) { if err != nil { t.Fatal(err) } - handler, err := api.NewHandler(h.s, auth, "claude_sdk", api.WithExecution(worker)) + handler, err := api.NewHandler(h.s, auth, "claude_sdk", api.WithExecution(worker), nativeDeploymentDefaults(model, provider)) if err != nil { t.Fatal(err) } @@ -85,7 +72,7 @@ func TestNativeToolSearchPublicExecution(t *testing.T) { Turn string `json:"turn"` Call string `json:"call"` } - raw, err = os.ReadFile(evidence) + raw, err := os.ReadFile(evidence) if err != nil || json.Unmarshal(raw, &proof) != nil { t.Fatal("invalid evidence", err) }