diff --git a/backends.go b/backends.go index 4491277..c3efd4c 100644 --- a/backends.go +++ b/backends.go @@ -9,6 +9,10 @@ import ( // backend. const BackendOpenRouter = backend.OpenRouterID +// BackendRakestrawHome is the reserved ID of Promptkit's built-in +// Rakestrawhome backend. +const BackendRakestrawHome = backend.RakestrawHomeID + // BackendLocal is the case-sensitive conventional ID used by [LocalBackend]. // It is not a built-in or reserved backend and must be registered with // [WithBackend]. @@ -20,7 +24,7 @@ const BackendLocal = "local" // to this configuration value do not break source compatibility. type Backend struct { // ID is the stable, case-sensitive registry key. NewEngine trims it and - // requires a non-blank value. BackendOpenRouter is reserved. + // requires a non-blank value. Built-in backend IDs are reserved. ID string // Endpoint is the OpenAI-compatible base endpoint. NewEngine trims it and // requires an absolute HTTP or HTTPS URL with a host and without user @@ -74,11 +78,11 @@ func LocalBackend(endpoint string, concurrencyLimit int) Backend { // // Registrations accumulate in option order. Every normalized ID must be unique // across consumer registrations and built-ins; a duplicate or invalid -// definition makes NewEngine fail with ErrInvalidConfig. In particular, -// BackendOpenRouter cannot be replaced. The immutable registration is scoped -// to the resulting Engine and cannot be enumerated, replaced, removed, or -// mutated after construction. WithBackend does not install package-global -// state. +// definition makes NewEngine fail with ErrInvalidConfig. Built-in IDs, +// including [BackendOpenRouter] and [BackendRakestrawHome], cannot be +// replaced. The immutable registration is scoped to the resulting Engine and +// cannot be enumerated, replaced, removed, or mutated after construction. +// WithBackend does not install package-global state. func WithBackend(backend Backend) Option { queueCapacity := 0 queueCapacitySet := backend.QueueCapacity != nil diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 8519ec8..c7e5861 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -123,6 +123,8 @@ The stage is complete when both exact built-ins are published through the same immutable registry path, both IDs are reserved, and existing custom-backend and OpenRouter behavior remains green. +**Status:** Complete. + ## Stage 2: Add the Built-In Profile and Prove Assembly ### Objective diff --git a/internal/backend/registry.go b/internal/backend/registry.go index 951a5b6..a754b6e 100644 --- a/internal/backend/registry.go +++ b/internal/backend/registry.go @@ -18,12 +18,20 @@ const ( // OpenRouterID is the reserved ID of Promptkit's built-in OpenRouter // backend. OpenRouterID = "openrouter" + // RakestrawHomeID is the reserved ID of Promptkit's built-in Rakestrawhome + // backend. + RakestrawHomeID = "rakestrawhome" openRouterEndpoint = "https://openrouter.ai/api/v1" openRouterAPIKeyEnv = "OPENROUTER_API_KEY" openRouterConcurrencyLimit = 16 - defaultQueueCapacity = 1024 + + rakestrawHomeEndpoint = "https://inference.ai.rakestrawhome.com/v1" + rakestrawHomeAPIKeyEnv = "RAKESTRAWHOME_INFERENCE_API_KEY" + + rakestrawHomeConcurrencyLimit = 4 + defaultQueueCapacity = 1024 ) // ErrBackendNotFound identifies a registry lookup for an unknown backend ID. @@ -36,20 +44,16 @@ type Registry struct { backends map[string]domain.Backend } -// NewRegistry constructs a registry containing the built-in OpenRouter -// definition followed by the supplied additions. Every ID must be unique. +// NewRegistry constructs a registry containing the built-in definitions +// followed by the supplied additions. Every ID must be unique. func NewRegistry(additions []domain.Backend) (*Registry, error) { + builtIns := builtInBackends() registry := &Registry{ - backends: make(map[string]domain.Backend, len(additions)+1), + backends: make(map[string]domain.Backend, len(builtIns)+len(additions)), } - definitions := make([]domain.Backend, 0, len(additions)+1) - definitions = append(definitions, domain.Backend{ - ID: OpenRouterID, - Endpoint: openRouterEndpoint, - APIKeyEnv: openRouterAPIKeyEnv, - ConcurrencyLimit: openRouterConcurrencyLimit, - }) + definitions := make([]domain.Backend, 0, len(builtIns)+len(additions)) + definitions = append(definitions, builtIns...) definitions = append(definitions, additions...) for _, definition := range definitions { @@ -71,6 +75,23 @@ func NewRegistry(additions []domain.Backend) (*Registry, error) { return registry, nil } +func builtInBackends() []domain.Backend { + return []domain.Backend{ + { + ID: OpenRouterID, + Endpoint: openRouterEndpoint, + APIKeyEnv: openRouterAPIKeyEnv, + ConcurrencyLimit: openRouterConcurrencyLimit, + }, + { + ID: RakestrawHomeID, + Endpoint: rakestrawHomeEndpoint, + APIKeyEnv: rakestrawHomeAPIKeyEnv, + ConcurrencyLimit: rakestrawHomeConcurrencyLimit, + }, + } +} + // GetBackend returns a defensive copy of the backend registered with id. func (r *Registry) GetBackend(id string) (domain.Backend, error) { if r == nil { diff --git a/internal/backend/registry_test.go b/internal/backend/registry_test.go index bc7b1c0..753454a 100644 --- a/internal/backend/registry_test.go +++ b/internal/backend/registry_test.go @@ -11,32 +11,63 @@ import ( const validEndpoint = "https://backend.example/v1" -func TestRegistryIncludesExactOpenRouterDefinition(t *testing.T) { +func TestRegistryIncludesExactBuiltInDefinitions(t *testing.T) { registry, err := backend.NewRegistry(nil) if err != nil { t.Fatalf("construct registry: %v", err) } - definition, err := registry.GetBackend(backend.OpenRouterID) - if err != nil { - t.Fatalf("look up OpenRouter: %v", err) + tests := []struct { + name string + id string + endpoint string + apiKeyEnv string + concurrent int + }{ + { + name: "OpenRouter", + id: backend.OpenRouterID, + endpoint: "https://openrouter.ai/api/v1", + apiKeyEnv: "OPENROUTER_API_KEY", + concurrent: 16, + }, + { + name: "Rakestrawhome", + id: backend.RakestrawHomeID, + endpoint: "https://inference.ai.rakestrawhome.com/v1", + apiKeyEnv: "RAKESTRAWHOME_INFERENCE_API_KEY", + concurrent: 4, + }, } - if definition.ID != "openrouter" || - definition.Endpoint != "https://openrouter.ai/api/v1" || - definition.APIKeyEnv != "OPENROUTER_API_KEY" || - definition.ConcurrencyLimit != 16 || - definition.QueueCapacity != 1024 || - !definition.QueueCapacitySet || - definition.ExtraParams != nil { - t.Fatalf("unexpected OpenRouter definition: %#v", definition) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + definition, err := registry.GetBackend(tc.id) + if err != nil { + t.Fatalf("look up built-in: %v", err) + } + if definition.ID != tc.id || + definition.Endpoint != tc.endpoint || + definition.APIKeyEnv != tc.apiKeyEnv || + definition.ConcurrencyLimit != tc.concurrent || + definition.QueueCapacity != 1024 || + !definition.QueueCapacitySet || + definition.ExtraParams != nil { + t.Fatalf("unexpected built-in definition: %#v", definition) + } + }) } + policies := registry.CapacityPolicies() - if len(policies) != 1 || - policies["openrouter"] != (domain.BackendCapacityPolicy{ + if len(policies) != 2 || + policies[backend.OpenRouterID] != (domain.BackendCapacityPolicy{ ConcurrencyLimit: 16, QueueCapacity: 1024, + }) || + policies[backend.RakestrawHomeID] != (domain.BackendCapacityPolicy{ + ConcurrencyLimit: 4, + QueueCapacity: 1024, }) { - t.Fatalf("unexpected OpenRouter capacity policies: %#v", policies) + t.Fatalf("unexpected built-in capacity policies: %#v", policies) } } @@ -109,11 +140,12 @@ func TestRegistryNormalizesUniqueAdditionsAndIsolatesMutations(t *testing.T) { } policies := registry.CapacityPolicies() - if len(policies) != 2 { + if len(policies) != 3 { t.Fatalf("unexpected capacity policy count: %#v", policies) } policies["custom"] = domain.BackendCapacityPolicy{} delete(policies, backend.OpenRouterID) + delete(policies, backend.RakestrawHomeID) againPolicies := registry.CapacityPolicies() if againPolicies["custom"] != (domain.BackendCapacityPolicy{ ConcurrencyLimit: 3, @@ -122,7 +154,10 @@ func TestRegistryNormalizesUniqueAdditionsAndIsolatesMutations(t *testing.T) { t.Fatalf("capacity policy map mutated registry state: %#v", againPolicies) } if _, ok := againPolicies[backend.OpenRouterID]; !ok { - t.Fatalf("capacity policy deletion mutated registry state: %#v", againPolicies) + t.Fatalf("OpenRouter capacity policy deletion mutated registry state: %#v", againPolicies) + } + if _, ok := againPolicies[backend.RakestrawHomeID]; !ok { + t.Fatalf("Rakestrawhome capacity policy deletion mutated registry state: %#v", againPolicies) } } @@ -242,11 +277,14 @@ func TestNewRegistryRejectsDuplicateIDs(t *testing.T) { wantID string }{ { - name: "built-in collision after normalization", - additions: []domain.Backend{{ - ID: " openrouter ", - }}, - wantID: "openrouter", + name: "OpenRouter collision after normalization", + additions: []domain.Backend{{ID: " openrouter "}}, + wantID: backend.OpenRouterID, + }, + { + name: "Rakestrawhome collision after normalization", + additions: []domain.Backend{{ID: " rakestrawhome "}}, + wantID: backend.RakestrawHomeID, }, { name: "consumer collision after normalization", diff --git a/public_contract_test.go b/public_contract_test.go index de095f2..56e366c 100644 --- a/public_contract_test.go +++ b/public_contract_test.go @@ -784,9 +784,12 @@ func TestBackendRegistrationRejectsInvalidAndDuplicateDefinitions(t *testing.T) {ID: " custom ", Endpoint: "http://one.example/v1"}, {ID: "custom", Endpoint: "http://two.example/v1"}, }}, - {name: "reserved built-in id", backends: []promptkit.Backend{{ + {name: "reserved OpenRouter ID", backends: []promptkit.Backend{{ ID: promptkit.BackendOpenRouter, Endpoint: "http://replacement.example/v1", }}}, + {name: "reserved Rakestrawhome ID", backends: []promptkit.Backend{{ + ID: promptkit.BackendRakestrawHome, Endpoint: "http://replacement.example/v1", + }}}, } for _, tt := range tests {