From 8aa5bc54260d4335749bc2b9180e22d50938ddfb Mon Sep 17 00:00:00 2001 From: saladday <1203511142@qq.com> Date: Thu, 24 Sep 2026 01:28:07 +0800 Subject: [PATCH] Remove optional provider configuration discovery --- CONTRIBUTING.md | 19 +-- .../internal/agent/provider_configuration.go | 24 ---- .../agent/provider_configuration_test.go | 84 ----------- apps/parsar-daemon/internal/agent/registry.go | 26 +--- .../internal/cli/claude_sdk_test.go | 7 - .../agents-api/execution-configuration.md | 40 ++---- contracts/agents-api/openapi.yaml | 132 ------------------ .../v1/configuration_capabilities.go | 63 --------- contracts/agents-api/v1/model_execution.go | 4 +- .../agents-api/v1/model_provider_admission.go | 10 -- .../agents-api/v1/startup_configuration.go | 9 +- .../retired_provider_configuration_test.go | 27 ++++ internal/agentdaemon/proto/inbound.go | 9 +- .../proto/provider_configuration.go | 25 ---- internal/harnessconfig/builtin/registry.go | 4 +- internal/harnessconfig/configuration.go | 9 -- .../agents-client/saved-agent-defaults.md | 13 +- packages/agents-client/src/client.ts | 15 +- .../src/execution-configuration-projection.ts | 47 +------ .../src/execution-configuration.test.ts | 30 +--- .../src/startup-configuration.test.ts | 30 +--- packages/agents-client/src/types.ts | 29 +--- .../cmd/server/configuration_capabilities.go | 50 ------- .../server/configuration_capabilities_test.go | 109 --------------- .../cmd/server/startup_configuration.go | 5 +- .../internal/api/startup_configuration.go | 18 +-- .../api/startup_configuration_test.go | 75 +--------- 27 files changed, 86 insertions(+), 827 deletions(-) delete mode 100644 apps/parsar-daemon/internal/agent/provider_configuration.go delete mode 100644 apps/parsar-daemon/internal/agent/provider_configuration_test.go delete mode 100644 contracts/agents-api/v1/configuration_capabilities.go create mode 100644 internal/agentdaemon/gateway/retired_provider_configuration_test.go delete mode 100644 internal/agentdaemon/proto/provider_configuration.go delete mode 100644 services/agents-api/cmd/server/configuration_capabilities.go delete mode 100644 services/agents-api/cmd/server/configuration_capabilities_test.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c6479925..516d28137 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1964,18 +1964,13 @@ replaced; do not carry obsolete compatibility code forward to satisfy this secti identity; retries cannot replace it. Keep this query separate from runtime observations and do not touch activity or wake sandboxes. The versioned contract is `contracts/agents-api/execution-configuration.md`. -- Provider configuration support belongs to shared adapter-owned declarations in - `internal/harnessconfig`. One immutable declaration registry supplies public - provider validation, startup discovery and the optional safe provider descriptor - on native adapter registration. The separate engine catalog qualifies operations, - not a duplicate table of provider facts. Core owns credential environment and - endpoint admission policy, deployment enablement and safe HTTP projection. No parallel - HTTP harness table or readiness gate is permitted. The startup query includes - these declarations only with `include=configuration_capabilities`, preserving - its legacy default response for installed strict clients; the extension has an - independent schema version. Advertise build scope and unknown live availability, - not universal support across deployed Runtime versions. Operation-specific - admission remains authoritative; an extra registry declaration cannot enable it. +- Provider input validation uses the adapter-owned rules in `internal/harnessconfig`. + Keep one internal registry for protocol and token-limit validation; Core owns + credential environment and endpoint admission policy. These rules are not a + public discovery API or Runtime registration descriptor. Operation qualification + and live readiness retain their existing owners. The Core startup view keeps its + basic supported/configured deployment snapshot and accepts no query parameters. + Session frozen execution-configuration reads remain a separate Core extension. - Public Agent updates use `POST /v1/agents/{agent_id}` with the same tenant/Beta boundary and shared saved-field validation. Preserve omission separately from null; only supplied fields replace saved values. Metadata is a separate whole-map diff --git a/apps/parsar-daemon/internal/agent/provider_configuration.go b/apps/parsar-daemon/internal/agent/provider_configuration.go deleted file mode 100644 index 25ddbaba3..000000000 --- a/apps/parsar-daemon/internal/agent/provider_configuration.go +++ /dev/null @@ -1,24 +0,0 @@ -package agent - -import ( - "cmp" - "slices" - - "github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto" - "github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig" -) - -func providerConfigurationDescriptor(registry harnessconfig.Registry, kind string) *proto.AgentProviderConfiguration { - configuration, known := registry.Lookup(kind) - if !known || len(configuration.Providers) == 0 { - return nil - } - descriptor := &proto.AgentProviderConfiguration{SchemaVersion: 1, Providers: make([]proto.AgentProviderProtocol, 0, len(configuration.Providers))} - for _, provider := range configuration.Providers { - descriptor.Providers = append(descriptor.Providers, proto.AgentProviderProtocol{ - Protocol: provider.Protocol, RequiresTokenLimits: provider.RequiresTokenLimits, - }) - } - slices.SortFunc(descriptor.Providers, func(a, b proto.AgentProviderProtocol) int { return cmp.Compare(a.Protocol, b.Protocol) }) - return descriptor -} diff --git a/apps/parsar-daemon/internal/agent/provider_configuration_test.go b/apps/parsar-daemon/internal/agent/provider_configuration_test.go deleted file mode 100644 index 766423783..000000000 --- a/apps/parsar-daemon/internal/agent/provider_configuration_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package agent_test - -import ( - "encoding/json" - "reflect" - "strings" - "testing" - - "github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/agent" - "github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto" - "github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig" - "github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig/builtin" -) - -func TestRegisteredAdaptersShareProviderDeclarations(t *testing.T) { - registry := agent.NewRegistry() - for _, kind := range builtin.Registry().Kinds() { - registry.RegisterKind(proto.SupportedAgentKind{Kind: kind, Available: false, Version: "fixture"}, stubFactory(kind)) - } - for _, kind := range registry.SupportedAgentKinds() { - declaration, _ := builtin.Registry().Lookup(kind.Kind) - descriptor := kind.ProviderConfiguration - if kind.Available || descriptor == nil || descriptor.SchemaVersion != 1 || len(descriptor.Providers) != len(declaration.Providers) { - t.Fatalf("declaration changed readiness or was omitted: %#v", kind) - } - for _, provider := range descriptor.Providers { - rule, ok := declaration.Provider(provider.Protocol) - if !ok || rule.RequiresTokenLimits != provider.RequiresTokenLimits { - t.Fatalf("adapter registration disagrees with Core admission: %#v", provider) - } - } - } -} - -func TestProviderDescriptorIsSafeDefensiveAndWireCompatible(t *testing.T) { - configuration := harnessconfig.Configuration{Providers: []harnessconfig.Provider{{Protocol: "fixture", RequiresTokenLimits: true}}} - declarations := harnessconfig.NewRegistry(map[string]harnessconfig.Configuration{"additional-adapter": configuration}) - registry := agent.NewRegistryWithConfigurations(declarations) - registry.RegisterKind(proto.SupportedAgentKind{ - Kind: "additional-adapter", Available: true, - ProviderConfiguration: &proto.AgentProviderConfiguration{SchemaVersion: 99, Providers: []proto.AgentProviderProtocol{{Protocol: "private-canary"}}}, - }, stubFactory("additional-adapter")) - configuration.Providers[0].Protocol = "private-canary" - first := registry.SupportedAgentKinds() - first[0].ProviderConfiguration.Providers[0].Protocol = "private-canary" - first[0].ProviderConfiguration.SchemaVersion = 99 - next := registry.SupportedAgentKinds() - if next[0].ProviderConfiguration.SchemaVersion != 1 || next[0].ProviderConfiguration.Providers[0].Protocol != "fixture" { - t.Fatal("caller mutated the adapter descriptor") - } - envelope, err := proto.NewEnvelope(proto.TypeHeartbeat, "", proto.HeartbeatPayload{SupportedAgentKinds: next}) - if err != nil { - t.Fatal(err) - } - if strings.Contains(string(envelope.Payload), "private-canary") { - t.Fatal("unregistered configuration leaked") - } - var roundTrip proto.HeartbeatPayload - if err := envelope.DecodePayload(&roundTrip); err != nil || !reflect.DeepEqual(roundTrip.SupportedAgentKinds, next) { - t.Fatalf("descriptor round trip failed: %v", err) - } - // The existing envelope decoder ignores added JSON fields; a peer built - // before this optional descriptor therefore still accepts the heartbeat. - var oldPeer struct { - Kinds []struct { - Kind string `json:"kind"` - Available bool `json:"available"` - Capabilities proto.AgentKindCapabilities `json:"capabilities"` - } `json:"supported_agent_kinds"` - } - if err := envelope.DecodePayload(&oldPeer); err != nil || len(oldPeer.Kinds) != 1 || oldPeer.Kinds[0].Kind != "additional-adapter" || !oldPeer.Kinds[0].Available { - t.Fatalf("older peer rejected the optional declaration: %v", err) - } - var oldDescriptor proto.SupportedAgentKind - if json.Unmarshal([]byte(`{"kind":"additional-adapter","available":true}`), &oldDescriptor) != nil || oldDescriptor.ProviderConfiguration != nil { - t.Fatal("missing declaration must remain unknown") - } - registry.Register("undeclared", stubFactory("undeclared")) - for _, kind := range registry.SupportedAgentKinds() { - if kind.Kind == "undeclared" && kind.ProviderConfiguration != nil { - t.Fatal("unknown adapter inherited support") - } - } -} diff --git a/apps/parsar-daemon/internal/agent/registry.go b/apps/parsar-daemon/internal/agent/registry.go index 176e5acf4..d9f6459f5 100644 --- a/apps/parsar-daemon/internal/agent/registry.go +++ b/apps/parsar-daemon/internal/agent/registry.go @@ -24,8 +24,6 @@ import ( "sync" "github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto" - "github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig" - "github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig/builtin" ) // Factory builds a Session for one prompt_request. out is the upstream @@ -57,25 +55,17 @@ var ErrUnsupportedKind = errors.New("agent: unsupported agent_kind") // Registry maps agent_kind → Factory and keeps the daemon-advertised // capability descriptor for each kind. Safe for concurrent use. type Registry struct { - mu sync.RWMutex - factories map[string]Factory - preparers map[string]PreparationFactory - kinds map[string]proto.SupportedAgentKind - configurations harnessconfig.Registry + mu sync.RWMutex + factories map[string]Factory + preparers map[string]PreparationFactory + kinds map[string]proto.SupportedAgentKind } func NewRegistry() *Registry { - return NewRegistryWithConfigurations(builtin.Registry()) -} - -// NewRegistryWithConfigurations shares an immutable adapter declaration snapshot -// with discovery and validation; factory registration still owns availability. -func NewRegistryWithConfigurations(configurations harnessconfig.Registry) *Registry { return &Registry{ - factories: make(map[string]Factory), - preparers: make(map[string]PreparationFactory), - kinds: make(map[string]proto.SupportedAgentKind), - configurations: configurations, + factories: make(map[string]Factory), + preparers: make(map[string]PreparationFactory), + kinds: make(map[string]proto.SupportedAgentKind), } } @@ -102,7 +92,6 @@ func (r *Registry) RegisterKind(info proto.SupportedAgentKind, f Factory) { delete(r.preparers, kind) info.Capabilities.Preparation = false info.Capabilities.WorkspaceReadPreparation = false - info.ProviderConfiguration = providerConfigurationDescriptor(r.configurations, kind) r.kinds[kind] = info } @@ -140,7 +129,6 @@ func (r *Registry) SupportedAgentKinds() []proto.SupportedAgentKind { if info.Kind == "" { info = proto.SupportedAgentKind{Kind: kind, Available: true} } - info.ProviderConfiguration = info.ProviderConfiguration.Clone() out = append(out, info) } slices.SortFunc(out, func(a, b proto.SupportedAgentKind) int { diff --git a/apps/parsar-daemon/internal/cli/claude_sdk_test.go b/apps/parsar-daemon/internal/cli/claude_sdk_test.go index e576fe51c..030d6c80f 100644 --- a/apps/parsar-daemon/internal/cli/claude_sdk_test.go +++ b/apps/parsar-daemon/internal/cli/claude_sdk_test.go @@ -82,13 +82,6 @@ func TestClaudeSDKDiscoveryAndRegistration(t *testing.T) { t.Fatal(info) } for _, registered := range reg.SupportedAgentKinds() { - if registered.Kind == "claude_sdk" { - configuration := registered.ProviderConfiguration - if configuration == nil || configuration.SchemaVersion != 1 || len(configuration.Providers) != 1 || configuration.Providers[0].Protocol != "anthropic" || configuration.Providers[0].RequiresTokenLimits { - t.Fatal("SDK registration lost its provider declaration") - } - registered.ProviderConfiguration = nil - } if registered.Kind == "claude_sdk" && registered != info { t.Fatalf("SDK descriptor changed: %+v", registered) } diff --git a/contracts/agents-api/execution-configuration.md b/contracts/agents-api/execution-configuration.md index fc8c1c6ca..7fe6e4a6c 100644 --- a/contracts/agents-api/execution-configuration.md +++ b/contracts/agents-api/execution-configuration.md @@ -58,35 +58,17 @@ provenance. Missing, deleted and foreign-tenant Session IDs share the existing not-found response. Responses are `Cache-Control: no-store`. Keys, ciphertext, secret references, native headers, query parameters and permissions are excluded. -## Supported provider configuration +## Discovery boundary -`GET /v1/agents/core/startup-configuration?include=configuration_capabilities` -adds a `configuration_capabilities` field to the existing startup view. Without -that exact optional query, the legacy response shape is unchanged, including its -schema version. Duplicate, unknown or malformed startup queries are rejected. -The extension has its own `schema_version: 1`: +Provider configuration discovery is not exposed. The Core startup-configuration +extension retains its basic supported/configured deployment snapshot and accepts +no query parameters, including the retired `include=configuration_capabilities`. +Provider inputs are still validated against internal adapter-owned rules and Core +admission policy. Removing discovery does not change the supported inputs or +create/update/execute behavior. -- `scope: core_build_provider_configuration` identifies the limited subject. -- `runtime_availability: unknown` explicitly excludes live peer readiness. -- `admission` declares Core's hosted-only credential boundary, HTTPS endpoint - restrictions and nonnegative ordered token limits. -- `harnesses` is sorted by registered harness name. Each entry contains - `support` (`supported` or `unknown`), deployment `enabled` and `default` flags, - plus provider protocol declarations. An unknown declaration has no providers. -- Each provider lists `required_fields` and `positive_fields`. Codex declares - Responses; Claude SDK and MiniMax Code declare Anthropic. MiniMax Code also - requires positive `context_window` and `max_output_tokens`. - -Adapter-owned declarations in `internal/harnessconfig` have one immutable registry -used by provider validation and discovery. Native adapter registration attaches -the same optional safe provider descriptor to the common Runtime contract. The -separate engine catalog still owns qualification of public operations. Core -composition adds deployment enablement and Core-owned admission restrictions, then projects -only public fields. It does not serialize native capability or operator objects. -Adding a declaration does not authorize a new public operation. Runtime versions -may differ; actual selection/admission still checks the chosen Runtime's features. -No node, binary, endpoint, model or key readiness is implied by support or enablement. - -The endpoints do not mutate configuration, rotate keys, migrate Sessions, expose -a model catalog or accept arbitrary native options. See +This Session query and the startup view are Core extensions, not OpenAI Agents +API operations. Ordinary Agent and Session operations retain their pinned upstream +contracts. Neither query mutates configuration, rotates keys, migrates Sessions, +exposes a model catalog or accepts arbitrary native options. See [model-execution.md](model-execution.md) for write and inheritance semantics. diff --git a/contracts/agents-api/openapi.yaml b/contracts/agents-api/openapi.yaml index 9dfc10217..7ca9bcdde 100644 --- a/contracts/agents-api/openapi.yaml +++ b/contracts/agents-api/openapi.yaml @@ -128,33 +128,6 @@ definitions: required: - harness type: object - v1.CoreConfigurationCapabilities: - properties: - admission: - $ref: '#/definitions/v1.CoreModelProviderAdmission' - harnesses: - items: - $ref: '#/definitions/v1.CoreHarnessConfigurationSupport' - type: array - runtime_availability: - enum: - - unknown - type: string - schema_version: - enum: - - 1 - type: integer - scope: - enum: - - core_build_provider_configuration - type: string - required: - - admission - - harnesses - - runtime_availability - - schema_version - - scope - type: object v1.CoreConfiguredStartupConfiguration: properties: daemon_gateway: @@ -189,30 +162,6 @@ definitions: - model_providers - self_hosted type: object - v1.CoreHarnessConfigurationSupport: - properties: - default: - type: boolean - enabled: - type: boolean - harness: - type: string - providers: - items: - $ref: '#/definitions/v1.CoreProviderProtocolSupport' - type: array - support: - enum: - - supported - - unknown - type: string - required: - - default - - enabled - - harness - - providers - - support - type: object v1.CoreHarnessModelProviderConfiguration: properties: endpoint_configured: @@ -244,83 +193,8 @@ definitions: - maintenance - provider type: object - v1.CoreModelProviderAdmission: - properties: - base_url: - $ref: '#/definitions/v1.CoreProviderEndpointRules' - credential_environment_types: - items: - enum: - - openai_hosted - type: string - type: array - token_limits: - $ref: '#/definitions/v1.CoreProviderTokenLimitRules' - required: - - base_url - - credential_environment_types - - token_limits - type: object - v1.CoreProviderEndpointRules: - properties: - fragment: - type: boolean - query: - type: boolean - schemes: - items: - enum: - - https - type: string - type: array - user_info: - type: boolean - required: - - fragment - - query - - schemes - - user_info - type: object - v1.CoreProviderProtocolSupport: - properties: - positive_fields: - items: - enum: - - context_window - - max_output_tokens - type: string - type: array - protocol: - type: string - required_fields: - items: - enum: - - protocol - - base_url - - api_key - - context_window - - max_output_tokens - type: string - type: array - required: - - positive_fields - - protocol - - required_fields - type: object - v1.CoreProviderTokenLimitRules: - properties: - max_output_not_above_context: - type: boolean - minimum: - type: integer - required: - - max_output_not_above_context - - minimum - type: object v1.CoreStartupConfiguration: properties: - configuration_capabilities: - $ref: '#/definitions/v1.CoreConfigurationCapabilities' configured: $ref: '#/definitions/v1.CoreConfiguredStartupConfiguration' object: @@ -3266,12 +3140,6 @@ paths: name: OpenAI-Beta required: true type: string - - description: Opt in to versioned provider configuration capabilities - enum: - - configuration_capabilities - in: query - name: include - type: string produces: - application/json responses: diff --git a/contracts/agents-api/v1/configuration_capabilities.go b/contracts/agents-api/v1/configuration_capabilities.go deleted file mode 100644 index f104eb332..000000000 --- a/contracts/agents-api/v1/configuration_capabilities.go +++ /dev/null @@ -1,63 +0,0 @@ -package v1 - -// CoreConfigurationCapabilities is the safe provider-configuration subset of -// this Core build's adapter declarations. It does not describe all Agent options -// or guarantee support across deployed Runtime versions. -type CoreConfigurationCapabilities struct { - SchemaVersion int `json:"schema_version" binding:"required" enums:"1"` - Scope string `json:"scope" binding:"required" enums:"core_build_provider_configuration"` - RuntimeAvailability string `json:"runtime_availability" binding:"required" enums:"unknown"` - Admission CoreModelProviderAdmission `json:"admission" binding:"required"` - Harnesses []CoreHarnessConfigurationSupport `json:"harnesses" binding:"required"` -} - -type CoreHarnessConfigurationSupport struct { - Harness string `json:"harness" binding:"required"` - Support string `json:"support" binding:"required" enums:"supported,unknown"` - Enabled bool `json:"enabled" binding:"required"` - Default bool `json:"default" binding:"required"` - Providers []CoreProviderProtocolSupport `json:"providers" binding:"required"` -} - -type CoreProviderProtocolSupport struct { - Protocol string `json:"protocol" binding:"required"` - RequiredFields []string `json:"required_fields" binding:"required" enums:"protocol,base_url,api_key,context_window,max_output_tokens"` - PositiveFields []string `json:"positive_fields" binding:"required" enums:"context_window,max_output_tokens"` -} - -type CoreModelProviderAdmission struct { - CredentialEnvironmentTypes []string `json:"credential_environment_types" binding:"required" enums:"openai_hosted"` - BaseURL CoreProviderEndpointRules `json:"base_url" binding:"required"` - TokenLimits CoreProviderTokenLimitRules `json:"token_limits" binding:"required"` -} - -type CoreProviderEndpointRules struct { - Schemes []string `json:"schemes" binding:"required" enums:"https"` - UserInfo bool `json:"user_info" binding:"required"` - Query bool `json:"query" binding:"required"` - Fragment bool `json:"fragment" binding:"required"` -} - -type CoreProviderTokenLimitRules struct { - Minimum int32 `json:"minimum" binding:"required"` - MaxOutputNotAboveContext bool `json:"max_output_not_above_context" binding:"required"` -} - -func (c *CoreConfigurationCapabilities) Clone() *CoreConfigurationCapabilities { - if c == nil { - return nil - } - copy := *c - copy.Admission.CredentialEnvironmentTypes = append([]string{}, c.Admission.CredentialEnvironmentTypes...) - copy.Admission.BaseURL.Schemes = append([]string{}, c.Admission.BaseURL.Schemes...) - copy.Harnesses = append([]CoreHarnessConfigurationSupport{}, c.Harnesses...) - for i := range copy.Harnesses { - copy.Harnesses[i].Providers = append([]CoreProviderProtocolSupport{}, c.Harnesses[i].Providers...) - for j := range copy.Harnesses[i].Providers { - provider := ©.Harnesses[i].Providers[j] - provider.RequiredFields = append([]string{}, provider.RequiredFields...) - provider.PositiveFields = append([]string{}, provider.PositiveFields...) - } - } - return © -} diff --git a/contracts/agents-api/v1/model_execution.go b/contracts/agents-api/v1/model_execution.go index 451a69559..72e6c5e78 100644 --- a/contracts/agents-api/v1/model_execution.go +++ b/contracts/agents-api/v1/model_execution.go @@ -49,8 +49,8 @@ func (p *ModelProviderInput) ValidateHarness(harness string) error { return p.ValidateHarnessWithRegistry(harness, builtin.Registry()) } -// ValidateHarnessWithRegistry uses the same adapter registration snapshot as -// capability discovery. It does not enable an execution engine or placement. +// ValidateHarnessWithRegistry validates provider input against adapter-owned rules. +// It does not enable an execution engine or placement. func (p *ModelProviderInput) ValidateHarnessWithRegistry(harness string, registry harnessconfig.Registry) error { if err := p.validate(registry); err != nil { return err diff --git a/contracts/agents-api/v1/model_provider_admission.go b/contracts/agents-api/v1/model_provider_admission.go index c9e12f352..c9e6141f0 100644 --- a/contracts/agents-api/v1/model_provider_admission.go +++ b/contracts/agents-api/v1/model_provider_admission.go @@ -12,16 +12,6 @@ func ModelProviderEnvironmentSupported(environment string) bool { return environment == providerEnvironment } -// ModelProviderConfigurationAdmission reports Core policy, independently of the -// adapter declaration and deployment enablement. -func ModelProviderConfigurationAdmission() CoreModelProviderAdmission { - return CoreModelProviderAdmission{ - CredentialEnvironmentTypes: []string{providerEnvironment}, - BaseURL: CoreProviderEndpointRules{Schemes: []string{providerScheme}}, - TokenLimits: CoreProviderTokenLimitRules{Minimum: 0, MaxOutputNotAboveContext: true}, - } -} - func validModelProviderBaseURL(base string) bool { u, err := url.Parse(base) return err == nil && u.Scheme == providerScheme && u.Hostname() != "" && u.User == nil && u.RawQuery == "" && u.Fragment == "" && !strings.ContainsAny(base, "\x00\r\n") diff --git a/contracts/agents-api/v1/startup_configuration.go b/contracts/agents-api/v1/startup_configuration.go index 3fb8faf12..6036bbbc4 100644 --- a/contracts/agents-api/v1/startup_configuration.go +++ b/contracts/agents-api/v1/startup_configuration.go @@ -3,11 +3,10 @@ package v1 // CoreStartupConfiguration is a secret-free snapshot of the validated process // configuration. It never describes a Session, Environment or live Runtime. type CoreStartupConfiguration struct { - Object string `json:"object" binding:"required" enums:"agents.core.startup_configuration"` - SchemaVersion int `json:"schema_version" binding:"required" enums:"1"` - Supported CoreSupportedConfiguration `json:"supported" binding:"required"` - Configured CoreConfiguredStartupConfiguration `json:"configured" binding:"required"` - ConfigurationCapabilities *CoreConfigurationCapabilities `json:"configuration_capabilities,omitempty"` + Object string `json:"object" binding:"required" enums:"agents.core.startup_configuration"` + SchemaVersion int `json:"schema_version" binding:"required" enums:"1"` + Supported CoreSupportedConfiguration `json:"supported" binding:"required"` + Configured CoreConfiguredStartupConfiguration `json:"configured" binding:"required"` } type CoreSupportedConfiguration struct { diff --git a/internal/agentdaemon/gateway/retired_provider_configuration_test.go b/internal/agentdaemon/gateway/retired_provider_configuration_test.go new file mode 100644 index 000000000..052e4c13b --- /dev/null +++ b/internal/agentdaemon/gateway/retired_provider_configuration_test.go @@ -0,0 +1,27 @@ +package gateway + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto" +) + +func TestRetiredProviderConfigurationDoesNotAffectHeartbeatAdmission(t *testing.T) { + const oldHeartbeat = `{"supported_agent_kinds":[{"kind":"codex","available":true,"capabilities":{"function_tools":true},"provider_configuration":{"schema_version":1,"providers":[{"protocol":"responses"}]}}]}` + var heartbeat proto.HeartbeatPayload + if err := json.Unmarshal([]byte(oldHeartbeat), &heartbeat); err != nil { + t.Fatal(err) + } + s := &Session{} + s.setSupportedAgentKinds(deviceKindsFromHeartbeat(heartbeat)) + info, found, known := s.AgentKindStatus("codex") + if !found || !known || !info.Available || !info.Capabilities.FunctionTools { + t.Fatal("retired metadata changed native availability or execution capabilities") + } + wire, err := json.Marshal(heartbeat) + if err != nil || strings.Contains(string(wire), "provider_configuration") { + t.Fatal("retired provider metadata is still advertised", err) + } +} diff --git a/internal/agentdaemon/proto/inbound.go b/internal/agentdaemon/proto/inbound.go index c62b37896..1b2770ee0 100644 --- a/internal/agentdaemon/proto/inbound.go +++ b/internal/agentdaemon/proto/inbound.go @@ -294,11 +294,10 @@ type AgentKindCapabilities struct { // can report unavailable kinds with Available=false when the adapter // exists but the underlying CLI binary is missing. type SupportedAgentKind struct { - Kind string `json:"kind"` - Available bool `json:"available"` - Version string `json:"version,omitempty"` - Capabilities AgentKindCapabilities `json:"capabilities,omitempty"` - ProviderConfiguration *AgentProviderConfiguration `json:"provider_configuration,omitempty"` + Kind string `json:"kind"` + Available bool `json:"available"` + Version string `json:"version,omitempty"` + Capabilities AgentKindCapabilities `json:"capabilities,omitempty"` } // HeartbeatPayload is the daemon's liveness ping. supported_agent_kinds diff --git a/internal/agentdaemon/proto/provider_configuration.go b/internal/agentdaemon/proto/provider_configuration.go deleted file mode 100644 index efac01ce8..000000000 --- a/internal/agentdaemon/proto/provider_configuration.go +++ /dev/null @@ -1,25 +0,0 @@ -package proto - -import "slices" - -// AgentProviderConfiguration describes the adapter build's provider inputs, -// independently of binary availability and live operation admission. Older -// peers may omit this optional descriptor, which means unknown support. -type AgentProviderConfiguration struct { - SchemaVersion int `json:"schema_version"` - Providers []AgentProviderProtocol `json:"providers"` -} - -type AgentProviderProtocol struct { - Protocol string `json:"protocol"` - RequiresTokenLimits bool `json:"requires_token_limits"` -} - -func (c *AgentProviderConfiguration) Clone() *AgentProviderConfiguration { - if c == nil { - return nil - } - copy := *c - copy.Providers = slices.Clone(c.Providers) - return © -} diff --git a/internal/harnessconfig/builtin/registry.go b/internal/harnessconfig/builtin/registry.go index edac027b2..27de5d0b3 100644 --- a/internal/harnessconfig/builtin/registry.go +++ b/internal/harnessconfig/builtin/registry.go @@ -1,5 +1,5 @@ -// Package builtin composes configuration declarations shared by Core admission -// and the adapter profile registry. It contains no native launch configuration. +// Package builtin composes adapter-owned provider validation rules for Core. +// It contains no native launch configuration or public discovery contract. package builtin import ( diff --git a/internal/harnessconfig/configuration.go b/internal/harnessconfig/configuration.go index d638349ef..785058fd6 100644 --- a/internal/harnessconfig/configuration.go +++ b/internal/harnessconfig/configuration.go @@ -69,15 +69,6 @@ func (r Registry) Lookup(kind string) (Configuration, bool) { return configuration.Clone(), ok } -func (r Registry) Kinds() []string { - kinds := make([]string, 0, len(r.configurations)) - for kind := range r.configurations { - kinds = append(kinds, kind) - } - slices.Sort(kinds) - return kinds -} - func (r Registry) ValidateProtocol(kind, protocol string) error { configuration, _ := r.Lookup(kind) return configuration.ValidateProtocol(protocol) diff --git a/packages/agents-client/saved-agent-defaults.md b/packages/agents-client/saved-agent-defaults.md index fdd85d319..5ef22df5f 100644 --- a/packages/agents-client/saved-agent-defaults.md +++ b/packages/agents-client/saved-agent-defaults.md @@ -54,16 +54,7 @@ console.log(frozen.model.value, frozen.model.source, frozen.harness.value); if (frozen.model_provider.status === "available") { console.log(frozen.model_provider.configuration?.protocol); } - -const startup = await client.retrieveStartupConfiguration({ - includeConfigurationCapabilities: true, -}); -const declarations = startup.configuration_capabilities; -// Build support and deployment enablement do not prove live Runtime readiness. -console.log(declarations?.harnesses, declarations?.runtime_availability); ``` -The startup option is explicit to preserve the legacy response for older clients. -On servers predating this extension, use the default startup read; requesting the -new include parameter is unsupported. Configuration reads do not execute or wake -Sessions. See [the query contract](../../contracts/agents-api/execution-configuration.md). +Configuration reads do not execute or wake Sessions. See +[the query contract](../../contracts/agents-api/execution-configuration.md). diff --git a/packages/agents-client/src/client.ts b/packages/agents-client/src/client.ts index fb2137fdc..ea24988ee 100644 --- a/packages/agents-client/src/client.ts +++ b/packages/agents-client/src/client.ts @@ -1,4 +1,4 @@ -import { projectExecutionConfiguration, projectConfigurationCapabilities } from "./execution-configuration-projection"; +import { projectExecutionConfiguration } from "./execution-configuration-projection"; import { exactFields, onlyFields, isRecord, hasOwn, canonicalUuid, isNonnegativeInteger, sameResourceId } from "./response-projection"; import { projectTokenUsage } from "./usage-projection"; import { projectAgentTurn, projectSessionItem, projectItemContent, projectHistoryPage, validateHistoryPageOptions } from "./history-projection"; @@ -29,7 +29,6 @@ import type { CreateSessionStreamOptions, CoreStartupConfiguration, SessionExecutionConfiguration, - StartupConfigurationReadOptions, CoreHarnessKind, CoreManagedSandboxProvider, FunctionResultContent, @@ -161,7 +160,7 @@ function invalidStartupConfiguration(): never { } function projectStartupConfiguration(value: unknown): CoreStartupConfiguration { - if (!isRecord(value) || !onlyFields(value, new Set([...startupConfigurationFields, "configuration_capabilities"])) || value.object !== "agents.core.startup_configuration" || value.schema_version !== 1 || + if (!isRecord(value) || !exactFields(value, startupConfigurationFields) || value.object !== "agents.core.startup_configuration" || value.schema_version !== 1 || !isRecord(value.supported) || !exactFields(value.supported, startupSupportedFields) || !sortedUnique(value.supported.harnesses, isHarnessKind) || !sortedUnique(value.supported.managed_sandbox_providers, isSandboxProvider) || !isRecord(value.configured) || !exactFields(value.configured, startupConfiguredFields) || @@ -196,15 +195,9 @@ function projectStartupConfiguration(value: unknown): CoreStartupConfiguration { return invalidStartupConfiguration(); } const projectedProviders = modelProviders as Array>; - const capabilities = hasOwn(value, "configuration_capabilities") - ? projectConfigurationCapabilities(value.configuration_capabilities, invalidStartupConfiguration) : undefined; - if (capabilities && (supportedHarnesses.some((harness) => !capabilities.harnesses.some((entry) => entry.harness === harness)) || capabilities.harnesses.some((entry) => - entry.enabled !== enabledHarnesses.includes(entry.harness as CoreHarnessKind) || - entry.default !== (entry.harness === configured.default_harness)))) return invalidStartupConfiguration(); return { object: "agents.core.startup_configuration", schema_version: 1, - ...(capabilities ? { configuration_capabilities: capabilities } : {}), supported: { harnesses: [...supportedHarnesses], managed_sandbox_providers: [...supportedSandboxProviders], @@ -1863,8 +1856,8 @@ function projectEnvironmentFileList( } export class OpenAIAgentsClient implements AgentCore { - async retrieveStartupConfiguration(options?: StartupConfigurationReadOptions): Promise { - const value = await this.request(`/agents/core/startup-configuration${options?.includeConfigurationCapabilities ? "?include=configuration_capabilities" : ""}`, { signal: options?.signal }, 200); + async retrieveStartupConfiguration(options?: ReadOptions): Promise { + const value = await this.request("/agents/core/startup-configuration", { signal: options?.signal }, 200); return projectStartupConfiguration(value); } diff --git a/packages/agents-client/src/execution-configuration-projection.ts b/packages/agents-client/src/execution-configuration-projection.ts index 4fc66619c..8d577b7a5 100644 --- a/packages/agents-client/src/execution-configuration-projection.ts +++ b/packages/agents-client/src/execution-configuration-projection.ts @@ -1,5 +1,5 @@ import { canonicalUuid, exactFields, isNonnegativeInteger, isRecord, onlyFields, sameResourceId } from "./response-projection"; -import type { CoreConfigurationCapabilities, ExecutionConfigurationSource, ModelProviderView, SessionExecutionConfiguration } from "./types"; +import type { ExecutionConfigurationSource, ModelProviderView, SessionExecutionConfiguration } from "./types"; type Invalid = () => never; const sources = new Set(["session", "agent", "deployment", "unknown"]); @@ -48,48 +48,3 @@ export function projectExecutionConfiguration(value: unknown, sessionId: string, model_provider: { source: provider.source as ExecutionConfigurationSource, status: provider.status as SessionExecutionConfiguration["model_provider"]["status"], configuration }, }; } - -function exactList(value: unknown, expected: string[]): boolean { - return Array.isArray(value) && value.length === expected.length && value.every((entry, index) => entry === expected[index]); -} - -// Public declarations are projected explicitly; private adapter fields cannot -// become public accidentally when an internal capability object grows. -export function projectConfigurationCapabilities(value: unknown, invalid: Invalid): CoreConfigurationCapabilities { - if (!isRecord(value) || !exactFields(value, new Set(["schema_version", "scope", "runtime_availability", "admission", "harnesses"])) || - value.schema_version !== 1 || value.scope !== "core_build_provider_configuration" || value.runtime_availability !== "unknown" || - !isRecord(value.admission) || !exactFields(value.admission, new Set(["credential_environment_types", "base_url", "token_limits"])) || - !Array.isArray(value.harnesses)) return invalid(); - const admission = value.admission; - if (!exactList(admission.credential_environment_types, ["openai_hosted"]) || - !isRecord(admission.base_url) || !exactFields(admission.base_url, new Set(["schemes", "user_info", "query", "fragment"])) || - !exactList(admission.base_url.schemes, ["https"]) || admission.base_url.user_info !== false || admission.base_url.query !== false || admission.base_url.fragment !== false || - !isRecord(admission.token_limits) || !exactFields(admission.token_limits, new Set(["minimum", "max_output_not_above_context"])) || - admission.token_limits.minimum !== 0 || admission.token_limits.max_output_not_above_context !== true) return invalid(); - const harnesses: CoreConfigurationCapabilities["harnesses"] = []; - for (const entry of value.harnesses) { - if (!isRecord(entry) || !exactFields(entry, new Set(["harness", "support", "enabled", "default", "providers"])) || - typeof entry.harness !== "string" || !/^[a-z][a-z0-9_-]{0,63}$/u.test(entry.harness) || - (harnesses.length > 0 && harnesses[harnesses.length - 1]!.harness >= entry.harness) || - (entry.support !== "supported" && entry.support !== "unknown") || typeof entry.enabled !== "boolean" || typeof entry.default !== "boolean" || - !Array.isArray(entry.providers) || (entry.support === "unknown" && entry.providers.length !== 0) || - (entry.support === "supported" && entry.providers.length === 0)) return invalid(); - const providers: CoreConfigurationCapabilities["harnesses"][number]["providers"] = []; - for (const provider of entry.providers) { - if (!isRecord(provider) || !exactFields(provider, new Set(["protocol", "required_fields", "positive_fields"])) || - typeof provider.protocol !== "string" || !/^[a-z][a-z0-9_-]{0,63}$/u.test(provider.protocol) || - providers.some((p) => p.protocol === provider.protocol) || !Array.isArray(provider.required_fields) || !Array.isArray(provider.positive_fields) || - new Set(provider.required_fields).size !== provider.required_fields.length || new Set(provider.positive_fields).size !== provider.positive_fields.length || - !["protocol", "base_url", "api_key"].every((field) => (provider.required_fields as unknown[]).includes(field)) || - !provider.required_fields.every((field) => ["protocol", "base_url", "api_key", "context_window", "max_output_tokens"].includes(field)) || - !provider.positive_fields.every((field) => ["context_window", "max_output_tokens"].includes(field) && (provider.required_fields as unknown[]).includes(field))) return invalid(); - providers.push({ protocol: provider.protocol, required_fields: [...provider.required_fields], positive_fields: [...provider.positive_fields] }); - } - harnesses.push({ harness: entry.harness, support: entry.support, enabled: entry.enabled, default: entry.default, providers }); - } - return { - schema_version: 1, scope: "core_build_provider_configuration", runtime_availability: "unknown", - admission: { credential_environment_types: ["openai_hosted"], base_url: { schemes: ["https"], user_info: false, query: false, fragment: false }, token_limits: { minimum: 0, max_output_not_above_context: true } }, - harnesses, - }; -} diff --git a/packages/agents-client/src/execution-configuration.test.ts b/packages/agents-client/src/execution-configuration.test.ts index db79b0181..fe2f39298 100644 --- a/packages/agents-client/src/execution-configuration.test.ts +++ b/packages/agents-client/src/execution-configuration.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from "vitest"; import { OpenAIAgentsClient } from "./client"; -import { projectConfigurationCapabilities } from "./execution-configuration-projection"; -import type { CoreConfigurationCapabilities, SessionExecutionConfiguration } from "./types"; +import type { SessionExecutionConfiguration } from "./types"; const id = "013773a9-44b9-4f84-baca-b51c04a01201"; const snapshot: SessionExecutionConfiguration = { @@ -47,30 +46,3 @@ describe("frozen execution configuration", () => { await expect(clientReturning(value).retrieveSessionExecutionConfiguration(id)).rejects.not.toThrow(/secret-canary/u); }); }); - -const capabilities: CoreConfigurationCapabilities = { - schema_version: 1, scope: "core_build_provider_configuration", runtime_availability: "unknown", - admission: { credential_environment_types: ["openai_hosted"], base_url: { schemes: ["https"], user_info: false, query: false, fragment: false }, token_limits: { minimum: 0, max_output_not_above_context: true } }, - harnesses: [ - { harness: "extra_test_adapter", support: "supported", enabled: false, default: false, providers: [{ protocol: "anthropic", required_fields: ["protocol", "base_url", "api_key", "context_window", "max_output_tokens"], positive_fields: ["context_window", "max_output_tokens"] }] }, - { harness: "unknown_adapter", support: "unknown", enabled: false, default: false, providers: [] }, - ], -}; -const invalid = (): never => { throw new Error("Invalid safe capabilities"); }; -describe("provider configuration capabilities", () => { - it("accepts registry extensions and unknown declarations without claiming readiness", () => { - const result = projectConfigurationCapabilities(capabilities, invalid); - expect(result).toEqual(capabilities); - expect(result).not.toBe(capabilities); - }); - it.each([ - (value: any) => { value.harnesses[0].providers[0].api_key = "secret-canary"; }, - (value: any) => { value.runtime_availability = "ready"; }, - (value: any) => { value.admission.credential_environment_types.push("none"); }, - (value: any) => { value.harnesses[0].providers[0].required_fields = ["api_key"]; }, - (value: any) => { value.harnesses[1].providers = value.harnesses[0].providers; }, - ])("rejects private fields and contradictory capabilities", (mutate) => { - const value = structuredClone(capabilities); mutate(value); - expect(() => projectConfigurationCapabilities(value, invalid)).toThrow("Invalid safe capabilities"); - }); -}); diff --git a/packages/agents-client/src/startup-configuration.test.ts b/packages/agents-client/src/startup-configuration.test.ts index 200da6cfd..31054fba1 100644 --- a/packages/agents-client/src/startup-configuration.test.ts +++ b/packages/agents-client/src/startup-configuration.test.ts @@ -41,41 +41,19 @@ describe("Core startup configuration", () => { }) as typeof fetch, }); - const result = await client.retrieveStartupConfiguration(); + const abort = new AbortController(); + const result = await client.retrieveStartupConfiguration({ signal: abort.signal }); (body.supported as { harnesses: string[] }).harnesses[0] = "mutated"; expect(String(calls[0]?.input)).toBe("https://core.example/v1/agents/core/startup-configuration"); + expect(calls[0]?.init?.signal).toBe(abort.signal); expect(new Headers(calls[0]?.init?.headers).get("Authorization")).toBe("Bearer project-key"); expect(result.supported.harnesses).toEqual(["claude_sdk", "codex", "mcode"]); expect(result.configured.managed_sandbox).toEqual({ enabled: true, provider: "docker", maintenance: false }); }); - it("opts into versioned capabilities while retaining the legacy default", async () => { - const body = fixture() as any; - body.configuration_capabilities = { - schema_version: 1, scope: "core_build_provider_configuration", runtime_availability: "unknown", - admission: { credential_environment_types: ["openai_hosted"], base_url: { schemes: ["https"], user_info: false, query: false, fragment: false }, token_limits: { minimum: 0, max_output_not_above_context: true } }, - harnesses: body.supported.harnesses.map((harness: string) => ({ - harness, support: "supported", enabled: body.configured.enabled_harnesses.includes(harness), default: harness === body.configured.default_harness, - providers: [{ protocol: harness === "codex" ? "responses" : "anthropic", required_fields: ["protocol", "base_url", "api_key"], positive_fields: [] }], - })), - }; - body.configuration_capabilities.harnesses.push({ - harness: "zz_extra_adapter", support: "supported", enabled: false, default: false, - providers: [{ protocol: "anthropic", required_fields: ["protocol", "base_url", "api_key"], positive_fields: [] }], - }); - const urls: string[] = []; - const client = new OpenAIAgentsClient({ fetch: (async (url: RequestInfo | URL) => { - urls.push(String(url)); return response(body); - }) as typeof fetch }); - const result = await client.retrieveStartupConfiguration({ includeConfigurationCapabilities: true }); - expect(urls[0]).toContain("?include=configuration_capabilities"); - expect(result.configuration_capabilities).toEqual(body.configuration_capabilities); - body.configuration_capabilities.harnesses[0].enabled = false; - await expect(client.retrieveStartupConfiguration({ includeConfigurationCapabilities: true })).rejects.toMatchObject({ code: "invalid_startup_configuration" }); - }); - it.each([ + ["removed configuration discovery", (value: any) => { value.configuration_capabilities = {}; }], ["unknown field", (value: any) => { value.secret = "private"; }], ["unknown harness", (value: any) => { value.supported.harnesses[0] = "future"; }], ["unsorted harnesses", (value: any) => { value.supported.harnesses.reverse(); }], diff --git a/packages/agents-client/src/types.ts b/packages/agents-client/src/types.ts index 758891bcc..a15b9a4d3 100644 --- a/packages/agents-client/src/types.ts +++ b/packages/agents-client/src/types.ts @@ -1002,34 +1002,7 @@ export interface SessionExecutionConfiguration { }; } -export interface CoreConfigurationCapabilities { - schema_version: 1; - scope: "core_build_provider_configuration"; - runtime_availability: "unknown"; - admission: { - credential_environment_types: ["openai_hosted"]; - base_url: { schemes: ["https"]; user_info: false; query: false; fragment: false }; - token_limits: { minimum: 0; max_output_not_above_context: true }; - }; - harnesses: Array<{ - harness: string; - support: "supported" | "unknown"; - enabled: boolean; - default: boolean; - providers: Array<{ - protocol: string; - required_fields: Array<"protocol" | "base_url" | "api_key" | "context_window" | "max_output_tokens">; - positive_fields: Array<"context_window" | "max_output_tokens">; - }>; - }>; -} - -export interface StartupConfigurationReadOptions extends ReadOptions { - includeConfigurationCapabilities?: boolean; -} - export interface CoreStartupConfiguration { - configuration_capabilities?: CoreConfigurationCapabilities; object: "agents.core.startup_configuration"; schema_version: 1; supported: { @@ -1055,7 +1028,7 @@ export interface CoreStartupConfiguration { export interface AgentCore { retrieveSessionExecutionConfiguration(sessionId: string, options?: ReadOptions): Promise; - retrieveStartupConfiguration(options?: StartupConfigurationReadOptions): Promise; + retrieveStartupConfiguration(options?: ReadOptions): Promise; listAgents(options?: PageOptions): Promise>; createAgent(input: CreateAgentInput): Promise; retrieveAgent(agentId: string): Promise; diff --git a/services/agents-api/cmd/server/configuration_capabilities.go b/services/agents-api/cmd/server/configuration_capabilities.go deleted file mode 100644 index a47cd3b9d..000000000 --- a/services/agents-api/cmd/server/configuration_capabilities.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "slices" - - v1 "github.com/MiniMax-AI-Dev/parsar/contracts/agents-api/v1" - "github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig" - "github.com/MiniMax-AI-Dev/parsar/services/agents-api/internal/engine" -) - -// configurationCapabilities projects only provider compatibility facts. Runtime -// heartbeats and operation admission remain authoritative for actual execution. -func configurationCapabilities(catalog engine.Catalog, registry harnessconfig.Registry, configured v1.CoreConfiguredStartupConfiguration) *v1.CoreConfigurationCapabilities { - result := &v1.CoreConfigurationCapabilities{ - SchemaVersion: 1, Scope: "core_build_provider_configuration", RuntimeAvailability: "unknown", - Admission: v1.ModelProviderConfigurationAdmission(), Harnesses: []v1.CoreHarnessConfigurationSupport{}, - } - kinds := append(catalog.Kinds(), registry.Kinds()...) - slices.Sort(kinds) - for _, kind := range slices.Compact(kinds) { - entry := v1.CoreHarnessConfigurationSupport{ - Harness: kind, Support: "unknown", Enabled: slices.Contains(configured.EnabledHarnesses, kind), - Default: configured.DefaultHarness == kind, Providers: []v1.CoreProviderProtocolSupport{}, - } - if declaration, ok := registry.Lookup(kind); ok && len(declaration.Providers) != 0 { - entry.Support = "supported" - for _, provider := range declaration.Providers { - view := v1.CoreProviderProtocolSupport{ - Protocol: provider.Protocol, RequiredFields: []string{"protocol", "base_url", "api_key"}, PositiveFields: []string{}, - } - if provider.RequiresTokenLimits { - view.RequiredFields = append(view.RequiredFields, "context_window", "max_output_tokens") - view.PositiveFields = []string{"context_window", "max_output_tokens"} - } - entry.Providers = append(entry.Providers, view) - } - slices.SortFunc(entry.Providers, func(a, b v1.CoreProviderProtocolSupport) int { - if a.Protocol < b.Protocol { - return -1 - } - if a.Protocol > b.Protocol { - return 1 - } - return 0 - }) - } - result.Harnesses = append(result.Harnesses, entry) - } - return result -} diff --git a/services/agents-api/cmd/server/configuration_capabilities_test.go b/services/agents-api/cmd/server/configuration_capabilities_test.go deleted file mode 100644 index 5e64dd6d3..000000000 --- a/services/agents-api/cmd/server/configuration_capabilities_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package main - -import ( - "encoding/json" - "reflect" - "testing" - - v1 "github.com/MiniMax-AI-Dev/parsar/contracts/agents-api/v1" - "github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig" - "github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig/builtin" - "github.com/MiniMax-AI-Dev/parsar/services/agents-api/internal/engine" -) - -func TestConfigurationCapabilitiesFollowDeployment(t *testing.T) { - for _, gateway := range []bool{false, true} { - got := coreStartupConfiguration("codex", []string{"codex"}, gateway, nil, "", nil).ConfigurationCapabilities - if got.SchemaVersion != 1 || got.Scope != "core_build_provider_configuration" || got.RuntimeAvailability != "unknown" || len(got.Harnesses) != 3 { - t.Fatalf("unexpected capabilities: %#v", got) - } - for _, harness := range got.Harnesses { - if harness.Support != "supported" || harness.Enabled != (gateway && harness.Harness == "codex") || harness.Default != (harness.Harness == "codex") { - t.Fatalf("deployment state confused with support: %#v", harness) - } - if len(harness.Providers) != 1 { - t.Fatalf("missing provider: %#v", harness) - } - provider := harness.Providers[0] - input := v1.ModelProviderInput{Protocol: provider.Protocol, BaseURL: "https://example.test", APIKey: "private-fixture"} - needsLimits := len(provider.PositiveFields) > 0 - if (input.ValidateHarness(harness.Harness) != nil) != needsLimits { - t.Fatalf("advertised requirements disagree with admission: %#v", harness) - } - want := []string{"protocol", "base_url", "api_key"} - if needsLimits { - want = append(want, "context_window", "max_output_tokens") - } - if !reflect.DeepEqual(provider.RequiredFields, want) { - t.Fatalf("required fields = %v", provider.RequiredFields) - } - } - } -} - -func TestAdditionalAdapterAndUnknownDeclarationUseCommonProjection(t *testing.T) { - declaration := harnessconfig.Configuration{Providers: []harnessconfig.Provider{{Protocol: "zeta"}, {Protocol: "alpha", RequiresTokenLimits: true}}} - registry := harnessconfig.NewRegistry(map[string]harnessconfig.Configuration{"additional-adapter": declaration}) - catalog := engine.NewCatalog(map[string]engine.Profile{"undeclared": {}}) - configured := v1.CoreConfiguredStartupConfiguration{DefaultHarness: "undeclared", EnabledHarnesses: []string{"undeclared"}} - got := configurationCapabilities(catalog, registry, configured) - if len(got.Harnesses) != 2 || got.Harnesses[0].Harness != "additional-adapter" || got.Harnesses[0].Support != "supported" || got.Harnesses[0].Default || got.Harnesses[0].Enabled { - t.Fatalf("additional registration was not projected: %#v", got) - } - if got.Harnesses[0].Providers[0].Protocol != "alpha" || got.Harnesses[0].Providers[1].Protocol != "zeta" || len(got.Harnesses[0].Providers[0].PositiveFields) != 2 { - t.Fatal("provider order or requirements lost") - } - unknown := got.Harnesses[1] - if unknown.Support != "unknown" || unknown.Providers == nil || len(unknown.Providers) != 0 { - t.Fatalf("unknown support inferred: %#v", unknown) - } - input := &v1.ModelProviderInput{Protocol: "alpha", BaseURL: "https://example.test", APIKey: "fixture", ContextWindow: 100, MaxOutputTokens: 20} - if input.ValidateHarnessWithRegistry("additional-adapter", registry) != nil || input.SafeView().ValidateHarnessWithRegistry("additional-adapter", registry) != nil { - t.Fatal("discovered declaration did not validate through the shared admission path") - } - input.MaxOutputTokens = 0 - if input.ValidateHarnessWithRegistry("additional-adapter", registry) == nil || input.SafeView().ValidateHarnessWithRegistry("additional-adapter", registry) == nil { - t.Fatal("advertised required limit was not enforced") - } - input.Protocol = "zeta" - if input.ValidateHarnessWithRegistry("additional-adapter", registry) != nil { - t.Fatal("protocol without required limits was rejected") - } - if input.ValidateHarness("additional-adapter") == nil { - t.Fatal("test registry widened production provider admission") - } - if _, ok := catalog.Lookup("additional-adapter"); ok { - t.Fatal("provider registration enabled an execution profile") - } - first, _ := json.Marshal(got) - for i := 0; i < 10; i++ { - next, _ := json.Marshal(configurationCapabilities(catalog, registry, configured)) - if string(first) != string(next) { - t.Fatal("capabilities changed between reads") - } - } -} - -func TestConfigurationAdmissionMatchesCorePolicy(t *testing.T) { - admission := configurationCapabilities(engine.Catalog{}, builtin.Registry(), v1.CoreConfiguredStartupConfiguration{}).Admission - if !reflect.DeepEqual(admission.CredentialEnvironmentTypes, []string{"openai_hosted"}) || !reflect.DeepEqual(admission.BaseURL.Schemes, []string{"https"}) || admission.BaseURL.UserInfo || admission.BaseURL.Query || admission.BaseURL.Fragment || admission.TokenLimits.Minimum != 0 || !admission.TokenLimits.MaxOutputNotAboveContext { - t.Fatalf("unexpected Core restrictions: %#v", admission) - } - for _, environment := range []string{"none", "self_hosted", "openai_hosted", "unknown"} { - if v1.ModelProviderEnvironmentSupported(environment) != (environment == "openai_hosted") { - t.Fatal("credential environment admission disagrees") - } - } - for _, base := range []string{"http://example.test", "https://user:private@example.test", "https://example.test?private=value", "https://example.test#private"} { - input := v1.ModelProviderInput{Protocol: "responses", BaseURL: base, APIKey: "fixture"} - if input.Validate() == nil { - t.Fatal("advertised endpoint restriction not enforced") - } - } - for _, limits := range [][2]int32{{-1, 0}, {100, -1}, {100, 101}} { - input := v1.ModelProviderInput{Protocol: "responses", BaseURL: "https://example.test", APIKey: "fixture", ContextWindow: limits[0], MaxOutputTokens: limits[1]} - if input.Validate() == nil { - t.Fatal("advertised token restriction not enforced") - } - } -} diff --git a/services/agents-api/cmd/server/startup_configuration.go b/services/agents-api/cmd/server/startup_configuration.go index 91b4870b4..c9cc93d26 100644 --- a/services/agents-api/cmd/server/startup_configuration.go +++ b/services/agents-api/cmd/server/startup_configuration.go @@ -2,7 +2,6 @@ package main import ( v1 "github.com/MiniMax-AI-Dev/parsar/contracts/agents-api/v1" - "github.com/MiniMax-AI-Dev/parsar/internal/harnessconfig/builtin" "github.com/MiniMax-AI-Dev/parsar/services/agents-api/internal/engine" "github.com/MiniMax-AI-Dev/parsar/services/agents-api/internal/execution" ) @@ -21,7 +20,7 @@ func coreStartupConfiguration(defaultHarness string, enabledHarnesses []string, provider := managedProviderKind managedConfiguration = v1.CoreManagedSandboxConfiguration{Enabled: true, Provider: &provider, Maintenance: managed.Maintenance} } - result := v1.CoreStartupConfiguration{ + return v1.CoreStartupConfiguration{ Object: "agents.core.startup_configuration", SchemaVersion: 1, Supported: v1.CoreSupportedConfiguration{ Harnesses: (engine.Catalog{}).Kinds(), ManagedSandboxProviders: []string{"docker", "microsandbox"}, @@ -31,6 +30,4 @@ func coreStartupConfiguration(defaultHarness string, enabledHarnesses []string, ManagedSandbox: managedConfiguration, ModelProviders: modelProviders, }, } - result.ConfigurationCapabilities = configurationCapabilities(engine.Catalog{}, builtin.Registry(), result.Configured) - return result } diff --git a/services/agents-api/internal/api/startup_configuration.go b/services/agents-api/internal/api/startup_configuration.go index 4e9279aa0..15445a8bd 100644 --- a/services/agents-api/internal/api/startup_configuration.go +++ b/services/agents-api/internal/api/startup_configuration.go @@ -2,7 +2,6 @@ package api import ( "net/http" - "net/url" "slices" v1 "github.com/MiniMax-AI-Dev/parsar/contracts/agents-api/v1" @@ -17,7 +16,6 @@ func WithStartupConfiguration(configuration v1.CoreStartupConfiguration) Option copy.Supported.ManagedSandboxProviders = slices.Clone(configuration.Supported.ManagedSandboxProviders) copy.Configured.EnabledHarnesses = slices.Clone(configuration.Configured.EnabledHarnesses) copy.Configured.ModelProviders = slices.Clone(configuration.Configured.ModelProviders) - copy.ConfigurationCapabilities = configuration.ConfigurationCapabilities.Clone() if configuration.Configured.ManagedSandbox.Provider != nil { provider := *configuration.Configured.ManagedSandbox.Provider copy.Configured.ManagedSandbox.Provider = &provider @@ -32,28 +30,18 @@ func WithStartupConfiguration(configuration v1.CoreStartupConfiguration) Option // @Produce json // @Security BearerAuth // @Param OpenAI-Beta header string true "agents=v1" -// @Param include query string false "Opt in to versioned provider configuration capabilities" Enums(configuration_capabilities) // @Success 200 {object} v1.CoreStartupConfiguration // @Failure 400,401,503 {object} v1.ErrorResponse // @Router /agents/core/startup-configuration [get] func (h *Handler) getStartupConfiguration(w http.ResponseWriter, r *http.Request) { - includeCapabilities := false if r.URL.RawQuery != "" { - query, err := url.ParseQuery(r.URL.RawQuery) - if err != nil || len(query) != 1 || len(query["include"]) != 1 || query.Get("include") != "configuration_capabilities" { - writeError(w, http.StatusBadRequest, "unsupported_parameter", "Core startup configuration only accepts include=configuration_capabilities.") - return - } - includeCapabilities = true + writeError(w, http.StatusBadRequest, "unsupported_parameter", "Core startup configuration does not accept query parameters.") + return } if h.startup == nil { writeError(w, http.StatusServiceUnavailable, "startup_configuration_unavailable", "Core startup configuration is unavailable.") return } - view := *h.startup - if !includeCapabilities { - view.ConfigurationCapabilities = nil - } w.Header().Set("Cache-Control", "no-store") - writeJSON(w, http.StatusOK, view) + writeJSON(w, http.StatusOK, h.startup) } diff --git a/services/agents-api/internal/api/startup_configuration_test.go b/services/agents-api/internal/api/startup_configuration_test.go index 8b5202cc0..ad70b5202 100644 --- a/services/agents-api/internal/api/startup_configuration_test.go +++ b/services/agents-api/internal/api/startup_configuration_test.go @@ -41,6 +41,10 @@ func TestStartupConfigurationHTTP(t *testing.T) { if strings.Contains(w.Body.String(), "mutated") || strings.Contains(w.Body.String(), "base_url") || strings.Contains(w.Body.String(), "token") { t.Fatalf("mutable or private configuration leaked: %s", w.Body) } + var fields map[string]json.RawMessage + if json.Unmarshal(w.Body.Bytes(), &fields) != nil || len(fields) != 4 || fields["configuration_capabilities"] != nil { + t.Fatal("startup response includes provider discovery") + } if w.Header().Get("Cache-Control") != "no-store" { t.Fatalf("Cache-Control = %q", w.Header().Get("Cache-Control")) } @@ -56,13 +60,9 @@ func TestStartupConfigurationRejectsUnsupportedReads(t *testing.T) { status int }{ {name: "missing auth", option: []Option{WithStartupConfiguration(configured)}, path: "/v1/agents/core/startup-configuration", status: http.StatusUnauthorized}, + {name: "retired discovery", option: []Option{WithStartupConfiguration(configured)}, path: "/v1/agents/core/startup-configuration?include=configuration_capabilities", auth: "Bearer test-api-key", status: http.StatusBadRequest}, {name: "query", option: []Option{WithStartupConfiguration(configured)}, path: "/v1/agents/core/startup-configuration?raw=true", auth: "Bearer test-api-key", status: http.StatusBadRequest}, {name: "malformed query", option: []Option{WithStartupConfiguration(configured)}, path: "/v1/agents/core/startup-configuration?raw;private", auth: "Bearer test-api-key", status: http.StatusBadRequest}, - {name: "unknown include", option: []Option{WithStartupConfiguration(configured)}, path: "/v1/agents/core/startup-configuration?include=private", auth: "Bearer test-api-key", status: http.StatusBadRequest}, - {name: "empty include", option: []Option{WithStartupConfiguration(configured)}, path: "/v1/agents/core/startup-configuration?include=", auth: "Bearer test-api-key", status: http.StatusBadRequest}, - {name: "duplicate include", option: []Option{WithStartupConfiguration(configured)}, path: "/v1/agents/core/startup-configuration?include=configuration_capabilities&include=configuration_capabilities", auth: "Bearer test-api-key", status: http.StatusBadRequest}, - {name: "additional key", option: []Option{WithStartupConfiguration(configured)}, path: "/v1/agents/core/startup-configuration?include=configuration_capabilities&raw=true", auth: "Bearer test-api-key", status: http.StatusBadRequest}, - {name: "malformed encoding", option: []Option{WithStartupConfiguration(configured)}, path: "/v1/agents/core/startup-configuration?include=%GG", auth: "Bearer test-api-key", status: http.StatusBadRequest}, {name: "composition missing", path: "/v1/agents/core/startup-configuration", auth: "Bearer test-api-key", status: http.StatusServiceUnavailable}, } { t.Run(test.name, func(t *testing.T) { @@ -78,68 +78,3 @@ func TestStartupConfigurationRejectsUnsupportedReads(t *testing.T) { }) } } - -func TestConfigurationCapabilitiesHTTPIsStableAndDefensive(t *testing.T) { - capabilities := &v1.CoreConfigurationCapabilities{ - SchemaVersion: 1, Scope: "core_build_provider_configuration", RuntimeAvailability: "unknown", - Admission: v1.ModelProviderConfigurationAdmission(), - Harnesses: []v1.CoreHarnessConfigurationSupport{{ - Harness: "additional-adapter", Support: "supported", Enabled: true, Default: true, - Providers: []v1.CoreProviderProtocolSupport{{Protocol: "fixture", RequiredFields: []string{"protocol", "base_url", "api_key", "context_window", "max_output_tokens"}, PositiveFields: []string{"context_window", "max_output_tokens"}}}, - }}, - } - configuration := v1.CoreStartupConfiguration{Object: "agents.core.startup_configuration", SchemaVersion: 1, ConfigurationCapabilities: capabilities} - h, _, _ := testHandler(t, WithStartupConfiguration(configuration)) - capabilities.Admission.CredentialEnvironmentTypes[0] = "private-canary" - capabilities.Admission.BaseURL.Schemes[0] = "private-canary" - capabilities.Harnesses[0].Harness = "private-canary" - capabilities.Harnesses[0].Providers[0].Protocol = "private-canary" - capabilities.Harnesses[0].Providers[0].RequiredFields[0] = "private-canary" - capabilities.Harnesses[0].Providers[0].PositiveFields[0] = "private-canary" - - previous := "" - for i := 0; i < 2; i++ { - request := httptest.NewRequest(http.MethodGet, "/v1/agents/core/startup-configuration?include=configuration_capabilities", nil) - request.Header.Set("Authorization", "Bearer test-api-key") - request.Header.Set("OpenAI-Beta", "agents=v1") - w := httptest.NewRecorder() - h.ServeHTTP(w, request) - if w.Code != http.StatusOK || strings.Contains(w.Body.String(), "private-canary") { - t.Fatalf("unsafe response: %d %s", w.Code, w.Body) - } - if i > 0 && w.Body.String() != previous { - t.Fatal("startup response changed between reads") - } - previous = w.Body.String() - var got v1.CoreStartupConfiguration - if json.Unmarshal(w.Body.Bytes(), &got) != nil || got.ConfigurationCapabilities == nil { - t.Fatal("missing capability extension") - } - entry := got.ConfigurationCapabilities.Harnesses[0] - if entry.Harness != "additional-adapter" || entry.Providers[0].RequiredFields[0] != "protocol" || entry.Providers[0].PositiveFields[0] != "context_window" { - t.Fatal("shared snapshot was mutated") - } - if got.ConfigurationCapabilities.RuntimeAvailability != "unknown" { - t.Fatal("startup view advertised Runtime readiness") - } - } - request := httptest.NewRequest(http.MethodGet, "/v1/agents/core/startup-configuration", nil) - request.Header.Set("Authorization", "Bearer test-api-key") - request.Header.Set("OpenAI-Beta", "agents=v1") - w := httptest.NewRecorder() - h.ServeHTTP(w, request) - var legacy map[string]json.RawMessage - if w.Code != http.StatusOK || json.Unmarshal(w.Body.Bytes(), &legacy) != nil || len(legacy) != 4 || legacy["configuration_capabilities"] != nil { - t.Fatalf("default read broke the legacy startup shape: %d %s", w.Code, w.Body) - } - for _, auth := range []string{"", "Bearer wrong-private-canary"} { - request := httptest.NewRequest(http.MethodGet, "/v1/agents/core/startup-configuration?api_key=private-canary", nil) - request.Header.Set("Authorization", auth) - request.Header.Set("OpenAI-Beta", "agents=v1") - w := httptest.NewRecorder() - h.ServeHTTP(w, request) - if w.Code != http.StatusUnauthorized || strings.Contains(w.Body.String(), "private-canary") || strings.Contains(w.Body.String(), "additional-adapter") { - t.Fatalf("unauthorized configuration exposure: %d %s", w.Code, w.Body) - } - } -}