Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# PORT=8080
# Mount the whole gateway under a path prefix, e.g. https://example.com/g/
# BASE_PATH=/g
# Header used to read/write request user_path values (default: X-GoModel-User-Path)
# USER_PATH_HEADER=X-GoModel-User-Path
# Log output format: leave unset to auto-detect, or set to "json" / "text"
# LOG_FORMAT=text
# Log verbosity: "debug", "info" (default), "warn", or "error"
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Full reference: `.env.template` and `config/config.yaml`
- `PORT` (8080)
- `GOMODEL_MASTER_KEY` (empty = unsafe mode)
- `BODY_SIZE_LIMIT` ("10M")
- `USER_PATH_HEADER` (`X-GoModel-User-Path`: Header used to read/write request `user_path` values)
- `ENABLE_PASSTHROUGH_ROUTES` (true: Enable provider-native passthrough routes under /p/{provider}/...)
- `ALLOW_PASSTHROUGH_V1_ALIAS` (true: Allow /p/{provider}/v1/... aliases while keeping /p/{provider}/... canonical)
- `ENABLED_PASSTHROUGH_PROVIDERS` (openai,anthropic,openrouter,zai,vllm: Comma-separated list of enabled passthrough providers)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ Key settings:
| `PORT` | `8080` | Server port |
| `BASE_PATH` | `/` | Mount the gateway under a path prefix such as `/g` |
| `GOMODEL_MASTER_KEY` | (none) | API key for authentication |
| `USER_PATH_HEADER` | `X-GoModel-User-Path` | Header used to read/write request `user_path` values |
| `ENABLE_PASSTHROUGH_ROUTES` | `true` | Enable provider-native passthrough routes under `/p/{provider}/...` |
| `ALLOW_PASSTHROUGH_V1_ALIAS` | `true` | Allow `/p/{provider}/v1/...` aliases while keeping `/p/{provider}/...` canonical |
| `ENABLED_PASSTHROUGH_PROVIDERS` | `openai,anthropic,openrouter,zai,vllm,deepseek` | Comma-separated list of enabled passthrough providers |
Expand Down
1 change: 1 addition & 0 deletions config/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ server:
pprof_enabled: false # expose /debug/pprof/* for local profiling only
enable_passthrough_routes: true # expose /p/{provider}/{endpoint} passthrough routes
allow_passthrough_v1_alias: true # allow /p/{provider}/v1/... while keeping /p/{provider}/... canonical
user_path_header: "X-GoModel-User-Path" # env: USER_PATH_HEADER; inbound header used for user_path scoping
enabled_passthrough_providers: ["openai", "anthropic", "openrouter", "zai", "vllm", "deepseek"] # providers enabled on /p/{provider}/...

models:
Expand Down
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func buildDefaultConfig() *Config {
Server: ServerConfig{
Port: "8080",
BasePath: "/",
UserPathHeader: "X-GoModel-User-Path",
SwaggerEnabled: false,
PprofEnabled: false,
EnablePassthroughRoutes: true,
Expand Down Expand Up @@ -160,6 +161,10 @@ func Load() (*LoadResult, error) {
return nil, err
}
cfg.Server.BasePath = NormalizeBasePath(cfg.Server.BasePath)
cfg.Server.UserPathHeader, err = NormalizeHeaderName(cfg.Server.UserPathHeader, "X-GoModel-User-Path")
if err != nil {
return nil, fmt.Errorf("invalid server.user_path_header: %w", err)
}
cfg.Models.ConfiguredProviderModelsMode = ResolveConfiguredProviderModelsMode(cfg.Models.ConfiguredProviderModelsMode)
if !cfg.Models.ConfiguredProviderModelsMode.Valid() {
return nil, fmt.Errorf("models.configured_provider_models_mode must be one of: fallback, allowlist")
Expand Down
63 changes: 62 additions & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func clearProviderEnvVars(t *testing.T) {
func clearAllConfigEnvVars(t *testing.T) {
t.Helper()
for _, key := range []string{
"PORT", "BASE_PATH", "GOMODEL_MASTER_KEY", "BODY_SIZE_LIMIT", "SWAGGER_ENABLED", "PPROF_ENABLED", "ENABLE_PASSTHROUGH_ROUTES", "ALLOW_PASSTHROUGH_V1_ALIAS", "ENABLED_PASSTHROUGH_PROVIDERS",
"PORT", "BASE_PATH", "GOMODEL_MASTER_KEY", "BODY_SIZE_LIMIT", "SWAGGER_ENABLED", "PPROF_ENABLED", "ENABLE_PASSTHROUGH_ROUTES", "ALLOW_PASSTHROUGH_V1_ALIAS", "USER_PATH_HEADER", "ENABLED_PASSTHROUGH_PROVIDERS",
"GOMODEL_CACHE_DIR", "CACHE_REFRESH_INTERVAL",
"REDIS_URL", "REDIS_KEY_MODELS", "REDIS_KEY_RESPONSES", "REDIS_TTL_MODELS", "REDIS_TTL_RESPONSES",
"RESPONSE_CACHE_SIMPLE_ENABLED",
Expand Down Expand Up @@ -101,6 +101,9 @@ func TestBuildDefaultConfig(t *testing.T) {
if cfg.Server.BasePath != "/" {
t.Errorf("expected Server.BasePath=/, got %s", cfg.Server.BasePath)
}
if cfg.Server.UserPathHeader != "X-GoModel-User-Path" {
t.Errorf("expected Server.UserPathHeader=X-GoModel-User-Path, got %s", cfg.Server.UserPathHeader)
}
if cfg.Server.PprofEnabled {
t.Error("expected Server.PprofEnabled=false")
}
Expand Down Expand Up @@ -1138,6 +1141,64 @@ server:
})
}

func TestLoad_UserPathHeaderConfig(t *testing.T) {
clearAllConfigEnvVars(t)

withTempDir(t, func(dir string) {
yaml := `
server:
user_path_header: "x-tenant-path"
`
if err := os.WriteFile(filepath.Join(dir, "config.yaml"), []byte(yaml), 0644); err != nil {
t.Fatalf("Failed to write config.yaml: %v", err)
}

result, err := Load()
if err != nil {
t.Fatalf("Load() failed: %v", err)
}
if got := result.Config.Server.UserPathHeader; got != "X-Tenant-Path" {
t.Fatalf("Server.UserPathHeader = %q, want X-Tenant-Path", got)
}
})

withTempDir(t, func(dir string) {
yaml := `
server:
user_path_header: "X-Yaml-Path"
`
if err := os.WriteFile(filepath.Join(dir, "config.yaml"), []byte(yaml), 0644); err != nil {
t.Fatalf("Failed to write config.yaml: %v", err)
}

t.Setenv("USER_PATH_HEADER", "x-env-path")

result, err := Load()
if err != nil {
t.Fatalf("Load() failed: %v", err)
}
if got := result.Config.Server.UserPathHeader; got != "X-Env-Path" {
t.Fatalf("Server.UserPathHeader = %q, want X-Env-Path", got)
}
})
}

func TestLoad_UserPathHeaderRejectsInvalidName(t *testing.T) {
clearAllConfigEnvVars(t)

withTempDir(t, func(_ string) {
t.Setenv("USER_PATH_HEADER", "Bad Header")

_, err := Load()
if err == nil {
t.Fatal("expected Load() to reject invalid USER_PATH_HEADER")
}
if !strings.Contains(err.Error(), "invalid server.user_path_header") {
t.Fatalf("Load() error = %v, want invalid server.user_path_header", err)
}
})
}

func TestLoad_EnvOverridesYAML(t *testing.T) {
clearAllConfigEnvVars(t)

Expand Down
22 changes: 22 additions & 0 deletions config/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"fmt"
"net/textproto"
"path"
"regexp"
"strconv"
Expand Down Expand Up @@ -31,12 +32,33 @@ type ServerConfig struct {
// AllowPassthroughV1Alias allows /p/{provider}/v1/... style passthrough routes
// while keeping /p/{provider}/... as the canonical form. Default: true.
AllowPassthroughV1Alias bool `yaml:"allow_passthrough_v1_alias" env:"ALLOW_PASSTHROUGH_V1_ALIAS"`
// UserPathHeader is the inbound HTTP header used to read/write user paths.
// Default: X-GoModel-User-Path.
UserPathHeader string `yaml:"user_path_header" env:"USER_PATH_HEADER"`
// EnabledPassthroughProviders lists the provider types enabled on
// /p/{provider}/... passthrough routes. Default:
// ["openai", "anthropic", "openrouter", "zai", "vllm", "deepseek"].
EnabledPassthroughProviders []string `yaml:"enabled_passthrough_providers" env:"ENABLED_PASSTHROUGH_PROVIDERS"`
}

var headerNameRegex = regexp.MustCompile(`^[!#$%&'*+\-.^_` + "`" + `|~0-9A-Za-z]+$`)

// NormalizeHeaderName canonicalizes an HTTP header field name. Empty values
// fall back to fallback.
func NormalizeHeaderName(value, fallback string) (string, error) {
value = strings.TrimSpace(value)
if value == "" {
value = fallback
}
if !headerNameRegex.MatchString(value) {
return "", fmt.Errorf("invalid HTTP header name %q", value)
}
if strings.EqualFold(value, fallback) {
return fallback, nil
}
return textproto.CanonicalMIMEHeaderKey(value), nil
}

// NormalizeBasePath canonicalizes the public mount path for the HTTP server.
// Empty, whitespace-only, and "/" all resolve to root.
func NormalizeBasePath(value string) string {
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0002-ingress-frame-and-semantic-envelope.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ GoModel needs a model that preserves the original request faithfully while still

## Flow Diagram

![RequestSnapshot and WhiteBoxPrompt request flow](assets/0002-ingress-frame-flow.svg)
![RequestSnapshot and WhiteBoxPrompt request flow](/adr/assets/0002-ingress-frame-flow.svg)

## Decision

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0006-semantic-response-cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Unified `Embedder` interface with a single implementation: an **HTTP client** ca

### Vector Store

`VecStore` interface + `type`-switched factory in [`internal/responsecache/vecstore.go`](../../internal/responsecache/vecstore.go). When semantic caching is enabled, **`vector_store.type` is required** (no default). Supported values:
`VecStore` interface + `type`-switched factory in [`internal/responsecache/vecstore.go`](https://github.com/ENTERPILOT/GoModel/blob/main/internal/responsecache/vecstore.go). When semantic caching is enabled, **`vector_store.type` is required** (no default). Supported values:
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if the referenced file exists in the repository

# Test: Verify the file exists at the expected path
fd -t f '^vecstore\.go$' internal/responsecache/

Repository: ENTERPILOT/GoModel

Length of output: 97


Use a relative path for better documentation maintainability.

For markdown documentation, relative paths are more robust than absolute GitHub URLs to main. They work immediately and remain valid when viewing files offline or in local repositories.

📝 Suggested change
-`VecStore` interface + `type`-switched factory in [`internal/responsecache/vecstore.go`](https://github.com/ENTERPILOT/GoModel/blob/main/internal/responsecache/vecstore.go). When semantic caching is enabled, **`vector_store.type` is required** (no default). Supported values:
+`VecStore` interface + `type`-switched factory in [`internal/responsecache/vecstore.go`](../../internal/responsecache/vecstore.go). When semantic caching is enabled, **`vector_store.type` is required** (no default). Supported values:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`VecStore` interface + `type`-switched factory in [`internal/responsecache/vecstore.go`](https://github.com/ENTERPILOT/GoModel/blob/main/internal/responsecache/vecstore.go). When semantic caching is enabled, **`vector_store.type` is required** (no default). Supported values:
`VecStore` interface + `type`-switched factory in [`internal/responsecache/vecstore.go`](../../internal/responsecache/vecstore.go). When semantic caching is enabled, **`vector_store.type` is required** (no default). Supported values:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/adr/0006-semantic-response-cache.md` at line 54, Replace the absolute
GitHub URL in the ADR text with a relative repository link so the doc remains
valid offline; specifically update the link pointing to
internal/responsecache/vecstore.go in docs/adr/0006-semantic-response-cache.md
to use a relative path (e.g., ../../internal/responsecache/vecstore.go) while
keeping the surrounding text about `vector_store.type` unchanged.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am leaving this as an absolute GitHub URL because the relative repository path makes mint broken-links fail from the Mintlify docs root: ../../internal/responsecache/vecstore.go resolves outside the docs site and is reported as broken. The absolute URL keeps the ADR link valid in the published docs and keeps the docs verification green.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SantiagoDePolonia, that's a completely valid constraint — when Mintlify's broken-links checker resolves links relative to the docs site root, a path like ../../internal/responsecache/vecstore.go correctly falls outside the published site and is flagged as broken. Using the absolute GitHub URL is the right call here. I'll withdraw the suggestion.


✏️ Learnings added
Learnt from: SantiagoDePolonia
Repo: ENTERPILOT/GoModel PR: 330
File: docs/adr/0006-semantic-response-cache.md:54-54
Timestamp: 2026-05-12T16:27:27.727Z
Learning: In the GoModel repository, ADR and other markdown files under the `docs/` directory that link to source files outside the docs tree (e.g. `internal/`) must use absolute GitHub URLs (e.g. `https://github.com/ENTERPILOT/GoModel/blob/main/internal/...`) rather than relative paths. Relative paths like `../../internal/...` resolve outside the Mintlify docs site root and are reported as broken by `npx mintlatest broken-links`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


| Type | Notes |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down
3 changes: 3 additions & 0 deletions docs/advanced/config-yaml.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ For deployments mounted below a domain root, set `server.base_path` or
`BASE_PATH`. For example, `BASE_PATH=/g` serves the gateway at `/g/v1/...`,
`/g/admin/...`, and `/g/health`.

To change the inbound user path header, set `server.user_path_header` or
`USER_PATH_HEADER`. The default remains `X-GoModel-User-Path`.

## Docker

GoModel reads `config/config.yaml` first, then `config.yaml`.
Expand Down
2 changes: 2 additions & 0 deletions docs/advanced/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ The most common way to configure GoModel. Set any of the variables below to over
| `BASE_PATH` | Mount path prefix, for example `/g` | `/` |
| `GOMODEL_MASTER_KEY` | Authentication key for securing the gateway | _(empty, unsafe mode)_ |
| `BODY_SIZE_LIMIT` | Max request body size (e.g., `10M`, `1024K`, `500KB`) | _(no limit)_ |
| `USER_PATH_HEADER` | Header used to read/write request `user_path` values | `X-GoModel-User-Path` |

#### Logging

Expand Down Expand Up @@ -251,6 +252,7 @@ Then uncomment and edit the settings you want to change:
server:
port: "3000"
base_path: "/g"
user_path_header: "X-GoModel-User-Path"
master_key: "my-secret-key"

cache:
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced/workflows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,5 @@ curl -X POST http://localhost:8080/admin/workflows \

- `scope_model` requires `scope_provider_name`
- `scope_user_path` is normalized to canonical slash form
- managed API keys can override the request `X-GoModel-User-Path`; workflow matching uses the effective request user path
- managed API keys can override the request user path header; workflow matching uses the effective request user path
- budget enforcement runs only when the global budget feature and the matched workflow's `budget` feature are both enabled; see [Budgets](/features/budgets)
4 changes: 4 additions & 0 deletions docs/features/user-path.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ Clients can also send a user path directly:
X-GoModel-User-Path: /team/alpha
```

The header name is configurable with `USER_PATH_HEADER` or
`server.user_path_header` in `config.yaml`. If unset, GoModel uses
`X-GoModel-User-Path`.

If the API key has its own `user_path`, the key wins. GoModel overwrites the
header value with the key-bound path before workflow matching, audit logging,
usage tracking, and model access checks run.
Expand Down
1 change: 1 addition & 0 deletions helm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ helm install gomodel ./helm \
| `image.tag` | Image tag | `""` (uses appVersion) |
| `server.port` | Server port | `8080` |
| `server.basePath` | URL path prefix where GoModel is mounted | `"/"` |
| `server.userPathHeader` | Header used to read/write request user_path values | `"X-GoModel-User-Path"` |
| `server.bodySizeLimit` | Max request body size | `"10M"` |
| `auth.masterKey` | Master key for auth | `""` |
| `auth.existingSecret` | Existing secret for auth | `""` |
Expand Down
1 change: 1 addition & 0 deletions helm/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ metadata:
data:
PORT: {{ .Values.server.port | quote }}
BASE_PATH: {{ .Values.server.basePath | default "/" | quote }}
USER_PATH_HEADER: {{ .Values.server.userPathHeader | default "X-GoModel-User-Path" | quote }}
BODY_SIZE_LIMIT: {{ .Values.server.bodySizeLimit | quote }}
{{- if or .Values.redis.enabled .Values.cache.redis.url }}
REDIS_KEY_MODELS: {{ .Values.cache.redis.keyModels | default "gomodel:models" | quote }}
Expand Down
5 changes: 5 additions & 0 deletions helm/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ spec:
configMapKeyRef:
name: {{ include "gomodel.fullname" . }}
key: BASE_PATH
- name: USER_PATH_HEADER
valueFrom:
configMapKeyRef:
name: {{ include "gomodel.fullname" . }}
key: USER_PATH_HEADER
- name: BODY_SIZE_LIMIT
valueFrom:
configMapKeyRef:
Expand Down
1 change: 1 addition & 0 deletions helm/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@
"properties": {
"port": { "type": "integer" },
"basePath": { "type": "string" },
"userPathHeader": { "type": "string" },
"bodySizeLimit": { "type": "string" }
}
},
Expand Down
2 changes: 2 additions & 0 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ server:
port: 8080
# -- URL path prefix where GoModel is mounted (for example, "/g")
basePath: "/"
# -- Header used to read/write request user_path values
userPathHeader: "X-GoModel-User-Path"
# -- Maximum request body size (e.g., "10M", "1G", "500K")
bodySizeLimit: "10M"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ test("auth key expirations render as a UTC date with the full UTC timestamp in t
assert.match(authKeyForm, /copyId: 'auth-key-user-path-help-copy'/);
assert.match(
authKeyForm,
/When set, this key overrides X-GoModel-User-Path for audit logging and downstream request context\./,
/When set, this key overrides the configured user path request header for audit logging and downstream request context\./,
);
assert.doesNotMatch(authKeyForm, /id="auth-key-user-path"[^>]*aria-label=/);
assert.doesNotMatch(authKeyForm, /User Path Override/);
Expand All @@ -507,7 +507,7 @@ test("auth key expirations render as a UTC date with the full UTC timestamp in t
/<p class="form-hint">\s*When set, this key overrides <code>X-GoModel-User-Path<\/code> for audit logging and downstream request context\.\s*<\/p>/,
);
assert.match(indexTemplate, /x-text="key\.user_path \|\| '\\u2014'"/);
assert.match(indexTemplate, /X-GoModel-User-Path/);
assert.match(indexTemplate, /configured user path request header/);
assert.match(indexTemplate, /:disabled="authKeyFormSubmitting"/);
assert.match(
indexTemplate,
Expand Down Expand Up @@ -1071,7 +1071,7 @@ test("model category tables lazy mount only the active table body", () => {
);
assert.match(
modelsBlock,
/The selector uses <code>\/<\/code> for all providers and models, <code>\{provider_name\}\/<\/code> for one provider, or <code>\{provider_name\}\/\{model\}<\/code> for one model\.[\s\S]*managed API key <code>user_path<\/code> when present, otherwise the <code>X-GoModel-User-Path<\/code> request header\./,
/The selector uses <code>\/<\/code> for all providers and models, <code>\{provider_name\}\/<\/code> for one provider, or <code>\{provider_name\}\/\{model\}<\/code> for one model\.[\s\S]*managed API key <code>user_path<\/code> when present, otherwise the configured user path request header\./,
);

const loadingRule = readCSSRule(css, ".loading-state");
Expand Down
2 changes: 1 addition & 1 deletion internal/admin/dashboard/templates/page-auth-keys.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ <h3>Create API Key</h3>
</div>
</div>
<div class="form-field">
<div class="inline-help-section" x-data="{ open: false, copyId: 'auth-key-user-path-help-copy', showLabel: 'Show API key user path help', hideLabel: 'Hide API key user path help', text: 'When set, this key overrides X-GoModel-User-Path for audit logging and downstream request context.' }">
<div class="inline-help-section" x-data="{ open: false, copyId: 'auth-key-user-path-help-copy', showLabel: 'Show API key user path help', hideLabel: 'Hide API key user path help', text: 'When set, this key overrides the configured user path request header for audit logging and downstream request context.' }">
<div class="inline-help-title-row">
<label class="form-field-label" for="auth-key-user-path">User Path (optional)</label>
{{template "inline-help-toggle" .}}
Expand Down
2 changes: 1 addition & 1 deletion internal/admin/dashboard/templates/page-models.html
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ <h3 x-text="modelOverrideFormDisplayName || modelOverrideForm.selector || 'Acces
</div>

<p class="form-hint">
The selector uses <code>/</code> for all providers and models, <code>{provider_name}/</code> for one provider, or <code>{provider_name}/{model}</code> for one model. <code>user_paths</code> is matched against the effective request <code>user_path</code>: the managed API key <code>user_path</code> when present, otherwise the <code>X-GoModel-User-Path</code> request header.
The selector uses <code>/</code> for all providers and models, <code>{provider_name}/</code> for one provider, or <code>{provider_name}/{model}</code> for one model. <code>user_paths</code> is matched against the effective request <code>user_path</code>: the managed API key <code>user_path</code> when present, otherwise the configured user path request header.
</p>
<p class="form-hint" x-text="'Default enabled: ' + (modelOverrideFormDefaultEnabled ? 'yes' : 'no') + ' · Effective now: ' + (modelOverrideFormEffectiveEnabled ? 'yes' : 'no')"></p>

Expand Down
1 change: 1 addition & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ func New(ctx context.Context, cfg Config) (*App, error) {
DisablePassthroughRoutes: !appCfg.Server.EnablePassthroughRoutes,
EnabledPassthroughProviders: appCfg.Server.EnabledPassthroughProviders,
AllowPassthroughV1Alias: &allowPassthroughV1Alias,
UserPathHeader: appCfg.Server.UserPathHeader,
SwaggerEnabled: swaggerEnabled,
}

Expand Down
2 changes: 1 addition & 1 deletion internal/auditlog/entry_capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func internalJSONAuditHeaders(ctx context.Context, requestID string) http.Header
headers.Set("X-Request-ID", requestID)
}
if userPath := strings.TrimSpace(core.UserPathFromContext(ctx)); userPath != "" {
headers.Set(core.UserPathHeader, userPath)
headers.Set(core.UserPathHeaderNameFromContext(ctx), userPath)
}
if snapshot := core.GetRequestSnapshot(ctx); snapshot != nil {
snapshotHeaders := snapshot.GetHeaders()
Expand Down
Loading