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 // 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]. const BackendLocal = "local" // 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 and rejects excessively deep // or large values for safety. 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 or RunPrepared 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 } // LocalBackend returns a caller-owned Backend for a conventional local // OpenAI-compatible endpoint. It sets ID to BackendLocal and copies endpoint // and concurrencyLimit into Endpoint and ConcurrencyLimit without // normalization or validation. APIKeyEnv, ExtraParams, and QueueCapacity keep // their zero values. // // LocalBackend does not read environment variables, register the value, or // mutate engine or package state. Supply the returned value through // [WithBackend]; [NewEngine] then applies the ordinary backend validation and // concurrency semantics, including default queue capacity for a positive // limit, unlimited behavior for zero, and ErrInvalidConfig for a negative // limit. func LocalBackend(endpoint string, concurrencyLimit int) Backend { return Backend{ ID: BackendLocal, Endpoint: endpoint, ConcurrencyLimit: concurrencyLimit, } } // 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 }) }