package promptkit import ( "gitea.maximumdirect.net/eric/promptkit/internal/backend" "gitea.maximumdirect.net/eric/promptkit/internal/domain" ) // BackendOpenRouter is the reserved ID of Promptkit's built-in OpenRouter // backend. const BackendOpenRouter = backend.OpenRouterID // Backend configures one engine-scoped OpenAI-compatible backend. // // Backend has no stable JSON representation. Use keyed literals so additions // 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. 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 // information, a query string, or a fragment. Paths are allowed. Endpoint string // APIKeyEnv optionally names the environment variable containing the API // key. NewEngine trims it and requires the portable form // [A-Za-z_][A-Za-z0-9_]*. Store only the name, never a credential value. APIKeyEnv string // ExtraParams contains backend-wide request defaults. Values must be // JSON-compatible, finite, acyclic, and keyed by non-empty strings. Keys // must not be model, session_id, messages, temperature, max_tokens, top_p, // service_tier, reasoning_effort, or response_format. An empty map supplies // no defaults. NewEngine deeply copies the map. ExtraParams map[string]any // ConcurrencyLimit is the maximum number of simultaneous model-generation // calls allowed for this backend within one Engine. Zero leaves the backend // unlimited. A negative value makes NewEngine fail with ErrInvalidConfig. ConcurrencyLimit int // QueueCapacity controls how many additional Run calls may be admitted // beyond ConcurrencyLimit. Nil uses 1024 when ConcurrencyLimit is positive; // a pointer uses its exact value, including zero. The pointed-to value must // be non-negative, and QueueCapacity must be nil when ConcurrencyLimit is // zero. Their sum must fit in an int. WithBackend copies the value and does // not retain the pointer. QueueCapacity *int } // WithBackend adds one Backend registration to the constructed Engine. // // 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. func WithBackend(backend Backend) Option { queueCapacity := 0 queueCapacitySet := backend.QueueCapacity != nil if queueCapacitySet { queueCapacity = *backend.QueueCapacity } return optionFunc(func(options *engineOptions) error { options.backends = append(options.backends, domain.Backend{ ID: backend.ID, Endpoint: backend.Endpoint, APIKeyEnv: backend.APIKeyEnv, ExtraParams: backend.ExtraParams, ConcurrencyLimit: backend.ConcurrencyLimit, QueueCapacity: queueCapacity, QueueCapacitySet: queueCapacitySet, }) return nil }) }