Add projects

This commit is contained in:
Alois 2026-08-24 00:10:41 +02:00
commit 8b607dd700
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
1802 changed files with 503346 additions and 2 deletions

View file

@ -0,0 +1,42 @@
package executor
import "context"
type downstreamWebsocketContextKey struct{}
type requireUpstreamWebsocketContextKey struct{}
// WithDownstreamWebsocket marks the current request as coming from a downstream websocket connection.
func WithDownstreamWebsocket(ctx context.Context) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, downstreamWebsocketContextKey{}, true)
}
// DownstreamWebsocket reports whether the current request originates from a downstream websocket connection.
func DownstreamWebsocket(ctx context.Context) bool {
if ctx == nil {
return false
}
raw := ctx.Value(downstreamWebsocketContextKey{})
enabled, ok := raw.(bool)
return ok && enabled
}
// WithRequiredUpstreamWebsocket marks a request whose incremental context is valid only on the current upstream websocket.
func WithRequiredUpstreamWebsocket(ctx context.Context) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, requireUpstreamWebsocketContextKey{}, true)
}
// RequiredUpstreamWebsocket reports whether falling back to an HTTP upstream would lose request context.
func RequiredUpstreamWebsocket(ctx context.Context) bool {
if ctx == nil {
return false
}
raw := ctx.Value(requireUpstreamWebsocketContextKey{})
enabled, ok := raw.(bool)
return ok && enabled
}

View file

@ -0,0 +1,33 @@
package executor
import (
"errors"
"io"
"sync"
)
// ExecutionLifecycle owns resources associated with an execution attempt.
type ExecutionLifecycle interface {
Bind(func() error) error
End(string)
}
// BindExecutionResource binds a closer to the execution lifecycle.
func BindExecutionResource(opts Options, closer io.Closer) error {
if opts.ExecutionLifecycle == nil || closer == nil {
return nil
}
var closeOnce sync.Once
var closeErr error
closeResource := func() error {
closeOnce.Do(func() {
closeErr = closer.Close()
})
return closeErr
}
if errBind := opts.ExecutionLifecycle.Bind(closeResource); errBind != nil {
return errors.Join(errBind, closeResource())
}
return nil
}

View file

@ -0,0 +1,69 @@
package executor
import (
"errors"
"sync/atomic"
"testing"
)
type lifecycleRecorder struct {
closeFn func() error
}
func (r *lifecycleRecorder) Bind(closeFn func() error) error {
r.closeFn = closeFn
return nil
}
func (*lifecycleRecorder) End(string) {}
type lifecycleCloser struct {
calls atomic.Int32
}
func (c *lifecycleCloser) Close() error {
c.calls.Add(1)
return nil
}
func TestBindExecutionResourceClosesResourceOnce(t *testing.T) {
lifecycle := &lifecycleRecorder{}
closer := &lifecycleCloser{}
if errBind := BindExecutionResource(Options{ExecutionLifecycle: lifecycle}, closer); errBind != nil {
t.Fatalf("BindExecutionResource() error = %v", errBind)
}
if lifecycle.closeFn == nil {
t.Fatal("BindExecutionResource() did not bind a closer")
}
if errClose := lifecycle.closeFn(); errClose != nil {
t.Fatalf("first close error = %v", errClose)
}
if errClose := lifecycle.closeFn(); errClose != nil {
t.Fatalf("second close error = %v", errClose)
}
if got := closer.calls.Load(); got != 1 {
t.Fatalf("closer calls = %d, want 1", got)
}
}
func TestBindExecutionResourceClosesWhenBindFails(t *testing.T) {
want := errors.New("selection ended")
lifecycle := &failingLifecycle{err: want}
closer := &lifecycleCloser{}
errBind := BindExecutionResource(Options{ExecutionLifecycle: lifecycle}, closer)
if !errors.Is(errBind, want) {
t.Fatalf("BindExecutionResource() error = %v, want %v", errBind, want)
}
if got := closer.calls.Load(); got != 1 {
t.Fatalf("closer calls = %d, want 1", got)
}
}
type failingLifecycle struct {
err error
}
func (l *failingLifecycle) Bind(func() error) error { return l.err }
func (*failingLifecycle) End(string) {}

View file

@ -0,0 +1,229 @@
package executor
import (
"context"
"net/http"
"net/url"
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
)
// RequestedModelMetadataKey stores the client-requested model name in Options.Metadata.
const RequestedModelMetadataKey = "requested_model"
// RequestPathMetadataKey stores the inbound HTTP request path (e.g. "/v1/images/generations") in Options.Metadata.
// It is optional and may be absent for non-HTTP executions.
const RequestPathMetadataKey = "request_path"
// DisallowFreeAuthMetadataKey instructs auth selection to skip known free-tier credentials.
const DisallowFreeAuthMetadataKey = "disallow_free_auth"
// AuthSelectionModelMetadataKey overrides the model used only for auth selection.
const AuthSelectionModelMetadataKey = "auth_selection_model"
// ReasoningEffortMetadataKey stores the client-requested reasoning effort for usage logs.
const ReasoningEffortMetadataKey = "reasoning_effort"
// ServiceTierMetadataKey stores the client-requested service tier for usage logs.
const ServiceTierMetadataKey = "service_tier"
// GenerateMetadataKey stores whether the client requested actual generation for usage logs.
// Missing or true means generation is enabled; only an explicit false disables generation.
const GenerateMetadataKey = "generate"
const (
// PinnedAuthMetadataKey locks execution to a specific auth ID.
PinnedAuthMetadataKey = "pinned_auth_id"
// SelectedAuthMetadataKey stores the auth ID selected by the scheduler.
SelectedAuthMetadataKey = "selected_auth_id"
// SelectedAuthCallbackMetadataKey carries an optional callback invoked with the selected auth ID.
SelectedAuthCallbackMetadataKey = "selected_auth_callback"
// SelectedAuthIndexMetadataKey stores the stable index of the auth selected by the scheduler.
SelectedAuthIndexMetadataKey = "selected_auth_index"
// SelectedAuthIndexCallbackMetadataKey carries an optional callback invoked with the selected auth index.
SelectedAuthIndexCallbackMetadataKey = "selected_auth_index_callback"
// ExecutionSessionMetadataKey identifies a long-lived downstream execution session.
ExecutionSessionMetadataKey = "execution_session_id"
// DerivedSessionIDMetadataKey stores a stable session identity inferred from request context.
DerivedSessionIDMetadataKey = "derived_session_id"
// CallerScopeMetadataKey isolates inferred session identities between downstream callers.
CallerScopeMetadataKey = "caller_scope"
// SessionAffinityProviderMetadataKey carries the affinity selection namespace
// (provider string, e.g. the literal "mixed" pool key) used by SessionAffinitySelector.Pick,
// so OnResult keys the session cache identically to how selection read it.
SessionAffinityProviderMetadataKey = "session_affinity_provider"
// SessionAffinityModelMetadataKey carries the model used during session affinity selection.
SessionAffinityModelMetadataKey = "session_affinity_model"
)
// Request encapsulates the translated payload that will be sent to a provider executor.
type Request struct {
// Model is the upstream model identifier after translation.
Model string
// Payload is the provider specific JSON payload.
Payload []byte
// Format represents the provider payload schema.
Format sdktranslator.Format
// Metadata carries optional provider specific execution hints.
Metadata map[string]any
}
// RequestAfterAuthInterceptor rewrites a request after credential selection and before executor translation.
type RequestAfterAuthInterceptor func(context.Context, RequestAfterAuthInterceptRequest) RequestAfterAuthInterceptResponse
// RequestAfterAuthInterceptRequest describes a selected-auth request before executor translation.
type RequestAfterAuthInterceptRequest struct {
// SourceFormat is the original client protocol format.
SourceFormat sdktranslator.Format
// ToFormat is the selected upstream protocol format.
ToFormat sdktranslator.Format
// Model is the selected upstream model for this attempt.
Model string
// RequestedModel is the client-requested model before alias/model-pool rewriting.
RequestedModel string
// Stream reports whether the request expects streaming output.
Stream bool
// Headers contains the current upstream request headers.
Headers http.Header
// Body contains the current request payload.
Body []byte
// Metadata is a best-effort cloned context snapshot. Treat it as read-only and JSON-like.
Metadata map[string]any
}
// RequestAfterAuthInterceptResponse returns selected-auth request modifications.
type RequestAfterAuthInterceptResponse struct {
// Headers replaces matching current request headers and preserves headers not mentioned here.
Headers http.Header
// Body replaces the current request body only when non-empty.
Body []byte
// ClearHeaders explicitly removes current request headers before Headers is applied.
ClearHeaders []string
// Terminate prevents the selected executor from receiving the request.
Terminate bool
// StatusCode is the downstream HTTP status used when Terminate is true.
StatusCode int
// ResponseHeaders contains downstream response headers used when Terminate is true.
ResponseHeaders http.Header
// ResponseBody contains the downstream response body used when Terminate is true.
ResponseBody []byte
}
// RequestTerminatedError carries a plugin-defined downstream response without executing upstream.
type RequestTerminatedError struct {
HTTPStatus int
Header http.Header
Body []byte
}
func (e *RequestTerminatedError) Error() string {
return "request terminated by plugin"
}
// StatusCode returns the plugin-defined downstream HTTP status.
func (e *RequestTerminatedError) StatusCode() int {
if e == nil {
return 0
}
return e.HTTPStatus
}
// ResponseHeaders returns a copy of the plugin-defined downstream headers.
func (e *RequestTerminatedError) ResponseHeaders() http.Header {
if e == nil {
return nil
}
return e.Header.Clone()
}
// ResponseBody returns a copy of the plugin-defined downstream body.
func (e *RequestTerminatedError) ResponseBody() []byte {
if e == nil {
return nil
}
return append([]byte(nil), e.Body...)
}
// Options controls execution behavior for both streaming and non-streaming calls.
type Options struct {
// Stream toggles streaming mode.
Stream bool
// Alt carries optional alternate format hint (e.g. SSE JSON key).
Alt string
// Headers are forwarded to the provider request builder.
Headers http.Header
// Query contains optional query string parameters.
Query url.Values
// OriginalRequest preserves the inbound request bytes prior to translation.
OriginalRequest []byte
// SourceFormat identifies the inbound schema.
SourceFormat sdktranslator.Format
// ResponseFormat identifies the downstream response schema.
// Empty means responses should use SourceFormat for backward compatibility.
ResponseFormat sdktranslator.Format
// Metadata carries extra execution hints shared across selection and executors.
Metadata map[string]any
// RequestAfterAuthInterceptor runs after credential selection and before executor translation.
RequestAfterAuthInterceptor RequestAfterAuthInterceptor
// ExecutionLifecycle owns Home-dispatched execution resources. Executors must not add it to request metadata.
ExecutionLifecycle ExecutionLifecycle
}
// EnsureMetadata initializes and returns Metadata, ensuring it is non-nil.
func (o *Options) EnsureMetadata() map[string]any {
if o.Metadata == nil {
o.Metadata = make(map[string]any)
}
return o.Metadata
}
// ResponseFormatOrSource returns the response target format for an execution.
func ResponseFormatOrSource(opts Options) sdktranslator.Format {
if opts.ResponseFormat != "" {
return opts.ResponseFormat
}
return opts.SourceFormat
}
// Response wraps either a full provider response or metadata for streaming flows.
type Response struct {
// Payload is the provider response in the executor format.
Payload []byte
// Metadata exposes optional structured data for translators.
Metadata map[string]any
// Headers carries upstream HTTP response headers for passthrough to clients.
Headers http.Header
}
// StreamChunk represents a single streaming payload unit emitted by provider executors.
type StreamChunk struct {
// Payload is the raw provider chunk payload.
Payload []byte
// Err reports any terminal error encountered while producing chunks.
Err error
}
// StreamResult wraps the streaming response, providing both the chunk channel
// and the upstream HTTP response headers captured before streaming begins.
type StreamResult struct {
// Headers carries upstream HTTP response headers from the initial connection.
Headers http.Header
// Chunks is the channel of streaming payload units.
Chunks <-chan StreamChunk
}
// StatusError represents an error that carries an HTTP-like status code.
// Provider executors should implement this when possible to enable
// better auth state updates on failures (e.g., 401/402/429).
type StatusError interface {
error
StatusCode() int
}
// RequestScopedError identifies a failure tied to the current request rather
// than the selected credential. Auth managers should not retry these errors
// across credentials or change credential availability because of them.
type RequestScopedError interface {
error
IsRequestScoped() bool
}

View file

@ -0,0 +1,26 @@
package executor
import (
"testing"
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
)
func TestResponseFormatOrSourceUsesExplicitResponseFormat(t *testing.T) {
opts := Options{
SourceFormat: sdktranslator.FormatOpenAI,
ResponseFormat: sdktranslator.FormatClaude,
}
if got := ResponseFormatOrSource(opts); got != sdktranslator.FormatClaude {
t.Fatalf("ResponseFormatOrSource() = %q, want %q", got, sdktranslator.FormatClaude)
}
}
func TestResponseFormatOrSourceFallsBackToSourceFormat(t *testing.T) {
opts := Options{SourceFormat: sdktranslator.FormatGemini}
if got := ResponseFormatOrSource(opts); got != sdktranslator.FormatGemini {
t.Fatalf("ResponseFormatOrSource() = %q, want %q", got, sdktranslator.FormatGemini)
}
}

View file

@ -0,0 +1,29 @@
package executor
import (
"errors"
"net/http"
)
// UpstreamWebsocketReplayRequiredError indicates that an incremental request
// cannot safely continue because its upstream websocket is no longer reusable.
type UpstreamWebsocketReplayRequiredError struct{}
func (*UpstreamWebsocketReplayRequiredError) Error() string {
return `{"error":{"message":"upstream transport requires full HTTP replay","type":"server_error","code":"upstream_http_replay_required","status":426}}`
}
func (*UpstreamWebsocketReplayRequiredError) StatusCode() int { return http.StatusUpgradeRequired }
func (*UpstreamWebsocketReplayRequiredError) IsRequestScoped() bool { return true }
// NewUpstreamWebsocketReplayRequiredError creates a request-scoped replay signal.
func NewUpstreamWebsocketReplayRequiredError() error {
return &UpstreamWebsocketReplayRequiredError{}
}
// IsUpstreamWebsocketReplayRequired reports whether err is the internal replay signal.
func IsUpstreamWebsocketReplayRequired(err error) bool {
var replayErr *UpstreamWebsocketReplayRequiredError
return errors.As(err, &replayErr)
}

View file

@ -0,0 +1,25 @@
package executor
import (
"fmt"
"net/http"
"testing"
)
func TestUpstreamWebsocketReplayRequiredError(t *testing.T) {
err := NewUpstreamWebsocketReplayRequiredError()
if !IsUpstreamWebsocketReplayRequired(err) {
t.Fatal("replay error was not recognized")
}
if !IsUpstreamWebsocketReplayRequired(fmt.Errorf("wrapped: %w", err)) {
t.Fatal("wrapped replay error was not recognized")
}
statusErr, ok := err.(interface{ StatusCode() int })
if !ok || statusErr.StatusCode() != http.StatusUpgradeRequired {
t.Fatalf("replay error = %T %v, want status 426", err, err)
}
requestErr, ok := err.(RequestScopedError)
if !ok || !requestErr.IsRequestScoped() {
t.Fatalf("replay error = %T, want request scoped", err)
}
}