UMANS-DASH-GO/
├── go.mod # Module: umans-dash-go, go 1.22, zero external deps
├── main.go # Entry point (startup sequence, signal handling)
├── src/ # Proxy package (package proxy)
│ ├── embed.go # go:embed wrapper for dashboard assets
│ ├── interface/
│ │ ├── dashboard.html # Dashboard UI (ported from JS proxy, minus excluded features)
│ │ └── dashboard.js # Dashboard JavaScript (extracted, template-rendered)
│ ├── config.go # Constants, config loading/saving, env overrides
│ ├── upstream.go # UpstreamClient, HTTP calls to upstream API
│ ├── keypool.go # API key pool with cooldown/health tracking
│ ├── cache.go # ImageHandoffCache LRU, hash helpers
│ ├── normalize.go # Reasoning, tool schema, payload normalization
│ ├── conversation.go # Conversation tracking, fingerprinting
│ ├── errors.go # Header/body redaction
│ ├── errors_log.go # Error log file, HTTP error records
│ ├── retry.go # Retry loop, backoff presets
│ ├── http.go # HTTP helpers, SSE, error writers, auth
│ ├── proxy_chat.go # OpenAI chat completions proxy handler
│ ├── proxy_anthropic.go # Anthropic messages proxy handler
│ ├── queue.go # Queue dispatch, concurrency limit mode
│ ├── usage.go # Usage/concurrency fetch, caching
│ ├── handlers.go # All HTTP handlers
│ ├── wallpaper.go # Wallpaper download/cache/serve
│ ├── vision.go # Vision handoff
│ ├── dashboard.go # Dashboard rendering
│ ├── proxy.go # NewProxy, ServeHTTP, Shutdown, lifecycle
│ └── types.go # Type definitions (Config, KeyPool, ImageHandoffCache, Proxy, etc.)
├── screenshots/ # Dashboard screenshots
├── .cache/ # Cached wallpaper images (auto-created) — `.jpg` extension but may contain PNG/WebP data
│ ├── wallpaper.jpg # Cached Bing wallpaper (daily TTL)
│ └── wallpaper-haven.jpg # Cached Wallhaven wallpaper (hourly TTL)
├── .logs/ # HTTP error logs (auto-created, per-session rotating files)
├── .config/ # config.json (auto-created, gitignored)
└── AGENTS.md # This file
A Go rewrite of the UMANS-Dash (~3,326 lines of Node.js). The Go version targets zero external dependencies (stdlib only). Excludes Sleev and FreeGen features from the original.
src/contains the full proxy implementation, split across ~20 files by concern (config, upstream client, key pool, queue, handlers, etc.). See the Project Structure above for the file map.src/interface/dashboard.htmlis the dashboard UI, ported from the JS proxy minus excluded features (see "Excluded from Original" below).
| Type | Purpose |
|---|---|
Config |
Runtime configuration (API keys, cache settings, concurrency override, ApiKeyMode / JSON API_KEY_MODE: "smart" (default), "managed", or "passthrough", ConcurrencyLimitMode / JSON CONCURRENCY_LIMIT_MODE: "soft" (default), "hard", or "manual" — controls which cap gates concurrency, ManualConcurrencyLimit / JSON MANUAL_CONCURRENCY_LIMIT: int — user-chosen gate when mode is "manual", SlotFreeDelay / JSON SLOT_FREE_DELAY: int seconds — delay before freeing a concurrency slot after request completion (0 = immediate, default), RetryAttempts / JSON RETRY_ATTEMPTS: *int (omitempty) — max retry attempts (0–10, default 2, nil = unset → default), BackoffStrategy / JSON BACKOFF_STRATEGY: string — preset name ("aggressive" (default) or "conservative"), deprecated BurstMode / JSON BURST_MODE kept for migration + rollback safety, etc.) |
KeyConfig |
A single API key entry (name, key, session) |
KeyPool |
Round-robin multi-key pool with cooldown/unhealthy marking |
ImageHandoffCache |
LRU cache for vision handoff image descriptions (SHA-256 keyed, 50 entries, 24h TTL) |
QueueItem |
A queued or in-flight request: Response, Req (with detached context via context.WithoutCancel so it survives ServeHTTP return), Payload, Model, Format ("openai"/"anthropic"), WriteError/WritePassthroughError, Fingerprint, PreferredKeyIndex, IsStream, and Done chan struct{} — closed by runDispatch (via defer) and all rejection paths when the request completes; handlers block on <-done to keep ServeHTTP alive until the request finishes |
Proxy |
Central state holder — all runtime state lives here. Includes lastClientKeyMu (RWMutex) / lastClientKey for last-known-good client API key tracking; concurrencyLimitMu (RWMutex) / concurrencyLimitMode / manualLimit for thread-safe concurrency limit mode state; retryConfigMu (RWMutex) / retryAttempts / backoffStrategy for thread-safe retry config state. Queue infrastructure: queueCond (*sync.Cond bound to queueMu), queuePending (bool — a ProcessQueue pass is scheduled), queueDone (chan struct{} — closed during shutdown to cancel pending slot-free delays), workerWG (sync.WaitGroup for the queue worker goroutine), queueShutdownOnce (sync.Once guarding close(queueDone)), visionHandoffSem (chan struct{} — proxy-wide vision-handoff concurrency limit, nil if disabled). Status cache: statusCache (json.RawMessage) / statusCacheFetchedAt (time.Time) for /api/umans/status (1-min TTL); statusHistoryCache (*statusHistoryCacheEntry) for /api/umans/status-history (5-min TTL, keyed by params) |
ModelInfo / Capabilities |
Model metadata from upstream catalog |
UsageData / UsageInfo / WindowInfo / PlanInfo |
Usage tracking types. UsageInfo now includes ServiceMode (*ServiceModeInfo, JSON service_mode) and PriorityBudget ([]PriorityBudgetEntry, JSON priority_budget) |
ServiceModeInfo |
Current service mode from upstream: Current (string, e.g. "interactive" or "low_interactivity"), ResetsAt (*string, null when not applicable) |
PriorityBudgetEntry |
A single category in the priority budget: Category, Label, Models ([]string), UsedPct (int), OverBudgetToday (bool), Mode (string), ResetsAt (*string) |
ConcurrencyData |
Concurrency limits and current state |
ImagePart |
An image found in a request payload (for vision handoff) |
statusHistoryCacheEntry |
Cached status history data: data (interface{}), fetchedAt (time.Time), key (string — MD5 of query params). Used by HandleStatusHistory for 5-min param-keyed caching |
ParseDuration(str string) Duration— Parse"15m","6h","30s"; bare numbers (pure digits) interpreted as millisecondsMaskToken(key string) string— first 10 +"..."+ last 4ParseListenPort(addr string) int— Extract port from"host:port"LoadConfig() (Config, error)— validatesRequestTimeout > 0(fatal if not)saveConfig(cfg Config) error— atomic write viaconfig.json.tmp+os.Rename
StatusCacheTTL = 1 * time.Minute— cache duration for/api/umans/status(relatively static)StatusHistoryCacheTTL = 5 * time.Minute— cache duration for/api/umans/status-history(matches usage history)
NewKeyPool(keys []KeyConfig) *KeyPool— each entry defaultsCooldownMs = 30000,Healthy = true(*KeyPool) Acquire(preferredIndex int) (*KeySlot, bool)(*KeyPool) MarkUnhealthy(index, status int)— cooldown: 503→60s, 502→30s, else 10s(*KeyPool) MarkHealthy(index int)(*KeyPool) HealthyCount() int/Total() int/State() []KeyState
NewImageHandoffCache(maxSize int, ttl time.Duration) *ImageHandoffCache(*ImageHandoffCache) Get(key string) (string, bool)— updates LRU on hit(*ImageHandoffCache) Set(key, desc string)— evicts oldest at capacity(*ImageHandoffCache) Stats() HandoffCacheStats/Resize(maxSize, ttl)sha256Hash(s string) string— SHA-256 hex digest for cache keys
(*Proxy) ApplyCatalogData(data map[string]interface{})— writes undercatalogMu.Lock()(*Proxy) GetModelInfo(id string) ModelInfo— reads undercatalogMu.RLock()(*Proxy) GetModelDisplayName(id string) string— reads undercatalogMu.RLock()(*Proxy) GetOrderedModelIds() []string— sorted by display name; reads undercatalogMu.RLock()(*Proxy) GetEffectiveModels() []string— filters disabled models; reads undercatalogMu.RLock()
(*Proxy) NeedsVisionHandoff(resolvedModel string) bool(*Proxy) ResolveModelId(requestedModel string) stringCollectImageParts(payload map[string]interface{}) []ImagePart(*Proxy) PerformVisionHandoff(payload, resolvedModel) int
NormalizeToolSchemas(tools []interface{})TryResolveRef(node, defs) map[string]interface{}SimplifyNullableCombinator(schema, key string)NormalizeTypeField(schema)/NormalizeEnumField(schema)
StripReasoningContent(payload)— removesreasoning_contentfrom assistant messagesNormalizeThinkingPayload(payload)— camelCasebudgetTokens→ snake_casebudget_tokensLimitImagesInMessages(payload, maxImages int)— replaces excess images with placeholder textFingerprintPayload(payload) string— MD5 hash of first user message, 12 charsMsgText(m) string/ExtractUserPrompt(payload) string
ParseLevels(raw interface{}) []stringInferReasoningModeFromCapabilities(reasoningCaps) *boolBuildReasoningVariants(reasoningCaps) map[string]interface{}
RedactHeaders(headers map[string][]string) map[string][]stringRedactBodyJson(body string) string
applyDefaultApiKeyMode(cfg *Config)— defaultsApiKeyModeto"smart"when unset (called inLoadConfigpaths)extractClientAPIKey(req *http.Request) string— readsX-Api-Keyheader, elseAuthorization: Bearertoken(*Proxy) setLastClientKey(key string)— thread-safe write underlastClientKeyMu.Lock(*Proxy) getLastClientKey() string— thread-safe read underlastClientKeyMu.RLock(*Proxy) upstreamAPIKeyForDashboard() string— selects the key for usage/history/concurrency/status/status-history calls per mode: passthrough→last client key; smart→last client key with pool fallback; managed→pool key(*UpstreamClient) GetStatus(apiKey string) (json.RawMessage, error)— fetches the public health snapshot from upstreamGET {baseURL}/status. 10-second context timeout. Returns raw JSON. On non-2xx, returns error.(*UpstreamClient) GetStatusHistory(apiKey, from, to, granularity, metric, model string) (json.RawMessage, error)— fetches bucketed status history from upstreamGET {baseURL}/status/history?from=&to=&granularity=&metric=&model=. 15-second context timeout. Returns raw JSON. On non-2xx, returns error.
(*Proxy) getConcurrencyLimitMode() string— thread-safe read underconcurrencyLimitMu.RLock(*Proxy) setConcurrencyLimitMode(mode string)— thread-safe write underconcurrencyLimitMu.Lock(*Proxy) getManualLimit() int— thread-safe read underconcurrencyLimitMu.RLock(*Proxy) setManualLimit(limit int)— thread-safe write underconcurrencyLimitMu.Lock; clamps to ≥ 1(*Proxy) getSlotFreeDelay() int— readsConfig.SlotFreeDelayunderconfigMu.RLock(*Proxy) gateLimit() int— returns the effective concurrency gate based on mode:soft→ soft cap (Limit),hard→ hard cap (HardCap) if available elseLimit,manual→ManualConcurrencyLimitclamped to[1, HardCap](falls back to soft cap ifManualConcurrencyLimitis 0/uninitialized); returns-1if no gate applies. SnapshotsOverrideConcurrencyunderconfigMu.RLock, then callsGetEffectiveConcurrency(lastConcurrency, override)(which takesoverrideas a parameter — no longer readsconfigMuinternally — to avoid recursive locking when called fromHandleConfigPostorgateLimit).Config.ConcurrencyLimitMode(JSONCONCURRENCY_LIMIT_MODE) andConfig.ManualConcurrencyLimit(JSONMANUAL_CONCURRENCY_LIMIT) persisted viasaveConfig(); restored inNewProxy()on startup. OldBURST_MODEbool migrated:true→"hard",false/missing →"soft".BurstModekept in sync (truewhen mode is"hard") for rollback safety.Config.SlotFreeDelay(JSONSLOT_FREE_DELAY) — delay in seconds beforeOnRequestComplete()decrementsActiveRequests. Default 2.HandleConfigPostcallssignalQueue()on any mode change (not justhard/manual) since switching tosoftfrom a more restrictive mode can also raise the gate. The call happens afterconfigMu.Unlock()to avoid lock-order inversion. When switching tomanualwithmanualLimit == 0, initializes it to the current soft cap (or hard cap if no soft cap).
(*Proxy) getRetryAttempts() int— thread-safe read underretryConfigMu.RLock; uses< 0guard (not<= 0) so explicit 0 (no retries) passes through. Clamps toMaxRetryAttemptsCap(10). ReturnsDefaultRetryAttempts(2) when negative.(*Proxy) getBackoffStrategy() string— thread-safe read underretryConfigMu.RLock; returns"aggressive"if empty.(*Proxy) setRetryConfig(attempts int, strategy string)— thread-safe write underretryConfigMu.Lock. Called fromNewProxy()andHandleConfigPost.(*Proxy) getRetryDelay(attempt int) time.Duration— looks upBackoffPresets[strategy][attempt-1], clamping to last entry if index exceeds preset length.(*Proxy) retryLoop(fn)— method (was free functionRetryLoop). Loops up togetRetryAttempts()times. When 0, runs callback once withisLast=true.BackoffPresets— package-levelvar(map[string][]int):"aggressive"→ {1,3,5,10,15,20,25,30,45,60},"conservative"→ {5,15,30,45,60,120,180,240,300}.Config.RetryAttempts(JSONRETRY_ATTEMPTS,*intomitempty) — nil = unset → default 2; 0 = no retries; range 0–10.Config.BackoffStrategy(JSONBACKOFF_STRATEGY, string) —"aggressive"(default) or"conservative".- Both persisted via
saveConfig(); restored inNewProxy(). Old config.json files without these fields migrate gracefully (nil → default 2, empty → "aggressive"). HandleConfigPostacceptsretryAttempts(float64, clamped [0,10]),backoffStrategy(string, validated againstBackoffPresets),requestTimeout(float64 seconds, clamped [30,1800]).HandleConfigGetexposesretryAttempts,backoffStrategy,requestTimeout(seconds).- Retry applies to both OpenAI (
proxyChatRequest) and Anthropic (proxyAnthropicRequest) paths. (*UpstreamClient) SetTimeout(timeout time.Duration)— live-updateshttpClient.Timeoutand transport'sResponseHeaderTimeoutwithout restart.(*UpstreamClient) CloseIdleConnections()— closes idle connections on the underlying transport. Called byHandleConfigPostafterSetTimeoutto force new connections with the updated timeout.NewUpstreamClientuses the configured timeout forResponseHeaderTimeout(was hardcoded 300s).
headersCommittedflag — both OpenAI and Anthropic retry loops track whether response headers have been written to the client. Once committed, the retry callback returns an error instead of attempting to write again. Prevents "superfluous response.WriteHeader" panics when a mid-stream pipe break triggers a retry.safeFlush(w)— central helper that wrapsflusher.Flush()withdefer func() { recover() }(). All flush calls in the codebase go through this helper (or throughresponseWriterTracker.Flushwhich has its own hijack guard). No bareflusher.Flush()calls remain.writeSSEErrorEvent— panic-safe: wrapped indefer func() { recover() }()so writing an error event to a dead/hijacked connection is silently swallowed.writeSSEHeaders— delegates toresponseWriterTracker.commitSSE()when the writer is a tracker, making header commitment atomic under the tracker lock and guarded againstwritten/hijacked/aborted.responseWriterTracker— wraps the realResponseWriterwithwritten/hijacked/abortedtracking and implementshttp.Hijacker.WriteHeader/Write/Flushreturn early if the response is hijacked, written, or aborted. Used by the timeout middleware (ServeHTTP) to safely take over the response when a request times out.Claim()— atomic method onresponseWriterTrackerthat sets bothwritten=trueandhijacked=trueunder the mutex in a single step. Returnstrueon success (caller then writes directly to the underlyingResponseWriter, bypassingWriteJSON/IsCommitted). Returnsfalseif alreadywritten/hijacked/aborted. Used by theServeHTTPtimeout path and panic recovery handler to claim the response before writing the error, closing the race window where a concurrent handler write could trigger a superfluousWriteHeader.IsCommitted()guards — added to the non-streaming JSON response path (WriteJSON), the Anthropic passthrough success path, and dashboard handlers (/,/dashboard.js). Each checksIsCommitted()and returns early instead of relying on the tracker's silent no-op, preventing "superfluous response.WriteHeader" log warnings.- Retry callback panic recovery — both OpenAI and Anthropic retry callbacks use named return values (
retry bool, err error) with adeferrecovery that logs the panic withattempt,isLast, andheadersCommittedstate, then returns the error to the retry loop.
ServeHTTP— wraps every request in aresponseWriterTracker, enforcesREQUEST_TIMEOUTfor the pre-streaming phase, and supports request abort on timeout. Streaming responses continue after timeout. Both the timeout path and the panic-recovery handler usetw.Claim()to atomically setwritten=true+hijacked=trueunder the mutex before writing the error directly to the underlyingResponseWriter(bypassingWriteJSON/IsCommitted). Oncehijackedis set, all tracker methods (WriteHeader,Write,Flush) become no-ops for the handler goroutine, preventing concurrent writes.queueWorker()— background goroutine started inNewProxy. Waits onqueueCond(sync.Condbound toqueueMu). When signaled viatrySignalQueueLocked()/signalQueue(), callsProcessQueue(). Exits whenshuttingDownis set.dispatchItem(item *QueueItem) bool— used only for the no-limit fast path inTryEnqueueOrDispatch. Reserves a slot underqueueMu(checks shutdown, context cancellation, committed response), incrementsActiveRequests, then spawnsrunDispatch. Does NOT callOnRequestComplete()— that is exclusively inrunDispatch's defer.runDispatch(item *QueueItem)— executes a request that already owns a slot. Runs the proxy handler (proxyChatRequestorproxyAnthropicRequest) with panic recovery. Always callsOnRequestComplete()via defer in every exit path.TryEnqueueOrDispatch(item *QueueItem) bool— whengate >= 0, all requests go through the queue (no inline dispatch under gate), eliminating gate overshoot. Whengate < 0(no limit), dispatches immediately viadispatchItem. Rejects new requests during shutdown.ProcessQueue()— the only dispatcher when a concurrency limit is in effect. Reserves slots underqueueMuby incrementingActiveRequestsfor each item, removes them from the queue, then launchesrunDispatchgoroutines outside the lock. Drains the entire queue whengate < 0. Re-checksshuttingDownandgateLimit()inside the dispatch loop.OnRequestComplete()— sole decrementer ofActiveRequests. IfSlotFreeDelay > 0, spawns aqueueDone-aware goroutine that waits for the delay (or exits early on shutdown) before decrementing. CallssignalQueue()after decrementing to wake the queue worker.signalQueue()/trySignalQueueLocked()— schedules aProcessQueuepass viaqueueCond.Signal().trySignalQueueLocked()is a no-op ifqueuePendingis already true orshuttingDownis set. Caller oftrySignalQueueLockedmust holdqueueMu.Shutdown()— setsshuttingDown, broadcastsqueueCondto wake the worker, waits forworkerWG, rejects queued items viarejectQueueLocked(), drains active requests (5s timeout), then shuts down the HTTP server.close(queueDone)is guarded bysync.Oncefor idempotency.ResetQueue()— clears queue accounting. Only safe when no in-flight requests exist (theirOnRequestCompletecalls would diverge).
detachedContext(parent) context.Context— wraps parent withcontext.WithoutCancel(), producing a context that is never cancelled and has no deadline, but carries parent values. Used byHandleChatCompletionsandHandleMessagesto build theQueueItem.Reqso queue items surviveServeHTTP's return (wheredefer cancel()would otherwise cancel the original request context and cause every queued request to be rejected as "client disconnected"). The client-disconnect deadline is still propagated via the tracker'sclientCtxforpipeBodyToResponse.UpstreamClient.ChatCompletions(ctx, ...),Messages(ctx, ...),doPost(ctx, ...)— accept a caller-supplied context so client disconnects/timeouts cancel in-flight upstream work.- Streaming requests use the original client context once headers are committed, so long SSE streams are not truncated by the request timeout.
PerformVisionHandoff— parallel image analysis with a proxy-wide concurrency cap (visionHandoffSemchannel onProxy, sized fromVisionHandoffConcurrency, default 4) to prevent goroutine explosion. This replaces the previous per-call local semaphore, so the cap now limits total concurrent handoff goroutines across all in-flight requests, not just within a single request.analyzeImageViaHandoff(ctx, ...)— accepts a context for cancellation propagation.
- Catalog singleflight — custom singleflight group prevents thundering-herd
/models/infofetches; stale cache is returned withnilerror when upstream fails. - Shared
catalogClient— reusedhttp.Clientfor catalog/models/user-info fetches with keep-alive. gateLimit()cache — 1-second TTL cache to reduce lock churn under load.- Effective-concurrency cache — keyed on
Limit/HardCap/OverrideConcurrencyto avoid recomputation per queue operation. - Status cache —
statusCache(json.RawMessage) onProxywithStatusCacheTTL(1 min). On upstream failure, cached data is returned as fallback. Protected byp.mu. - Status history cache —
statusHistoryCache(*statusHistoryCacheEntry) onProxywithStatusHistoryCacheTTL(5 min), keyed by MD5 of all query params. On upstream failure, cached data is returned as fallback. Protected byp.mu.
- No shared mutable
apiKeyfield onUpstreamClient— the API key is passed as an explicit parameter toChatCompletions,Messages,doPost,GetUsage,GetUserInfo,GetStatus, andGetStatusHistory. This eliminates the data race that occurred when multiple goroutines calledSetAPIKeyconcurrently on the sharedp.Upstreaminstance. SetAPIKey(key string)still exists for backward compatibility but is not used in the request hot path.
(*Proxy) Authorized(req *http.Request) bool— behavior now depends onApiKeyMode:passthroughrequires a client key present;smartalways accepts (client or own key used downstream);managedvalidates the client's key against configuredAPIKeys(or accepts ifAPIKeysis empty)ReadBody(req *http.Request) (string, error)— 5 MB cap; useserrors.Is(err, io.EOF)for clean stream endpipeBodyToResponse(body, w, r) (int, error)— copies upstream response body to client with flushing; returns bytes written so caller can detect empty streamsdrainAndClose(body io.ReadCloser)— reads and discards any remaining body data before closing. Ensures the underlying TCP connection can be reused (HTTP keep-alive) instead of being discarded on an unread body.- Empty SSE stream detection — before committing SSE headers to the client, the proxy peeks at the first chunk from the upstream body. If 0 bytes + EOF (empty stream), it treats this as a retryable 502 error (logs, retries with key rotation). Prevents the "empty stream with no finish_reason" client error.
WriteJSON(w, status, payload)WriteOpenAIError(w, status, message, errType, code)WriteAnthropicError(w, status, message, errType)WritePassthroughError(w, status, body)/WriteAnthropicPassthroughError(w, status, body)
upgradePeapixResolution(imageURL string) string— regex_NNNN.(jpg|jpeg|png)$→_3840for UHD peapix imagesdownloadImage(imageURL, userAgent string, timeout time.Duration) []byte— HTTP fetch with UA + timeoutisValidImage(data []byte) bool— validates JPEG/PNG/WebP magic bytesisJPEG(data []byte) bool— JPEG-only magic check (0xFF 0xD8)imageContentType(data []byte) string— returns"image/png","image/webp", or"image/jpeg"(fallback) based on magic bytesserveWallpaperImage(w, data, expires)— serves image asimage/jpeg(legacy; delegates toserveWallpaperImageTyped)serveWallpaperImageTyped(w, data, expires, contentType)— typed wallpaper serving with caller-specified Content-TypesaveCacheFile(cacheFile string, data []byte) bool— writes cache file, mkdir if needed
(*Proxy) HandleHealthz(w, r)/HandleModels(w, r)/HandleConfigGet(w, r)—HandleConfigGetexposesapikeyMode,concurrencyLimitMode,manualConcurrencyLimit, andslotFreeDelayfields(*Proxy) HandleConfigPost(w, r)— acceptsapikeyMode(managed/passthrough/smart),concurrencyLimitMode(soft/hard/manual),manualConcurrencyLimit(int, clamped ≥ 1),slotFreeDelay(int seconds, clamped ≥ 0); syncsBurstModefor rollback safety; auto-initializesmanualLimitto soft cap when switching to manual with limit 0; releasesconfigMubefore callingsignalQueue()to avoid lock-order inversion with queue paths (which acquirequeueMuthen callgateLimit()→configMu.RLock); onrequestTimeoutchange, callsUpstream.SetTimeout()thenUpstream.CloseIdleConnections(); triggers debounced save(*Proxy) HandleKeysGet(w, r)/HandleKeysPost(w, r)—HandleKeysGetacquiresconfigMu.RLock()(*Proxy) HandleUsage(w, r)/HandleConcurrency(w, r)—HandleConcurrencyresponse includesconcurrency_limit_modeandmanual_limitfields;FetchUsage/FetchUsageHistorycallupstreamAPIKeyForDashboard()before upstream calls(*Proxy) HandleUsageHistory(w, r)/HandleUser(w, r)—HandleUserreturnsuser_idfromLastConcurrency(*Proxy) HandleStatus(w, r)— proxies upstream/v1/status. Caches forStatusCacheTTL(1 min);?fresh=1bypasses cache. On upstream failure, returns cached data if available (cache fallback). CallsupstreamAPIKeyForDashboard()for the key.(*Proxy) HandleStatusHistory(w, r)— proxies upstream/v1/status/history. Caches forStatusHistoryCacheTTL(5 min), keyed by all query params (from,to,granularity,metric,model);?fresh=1bypasses cache. On upstream failure, returns cached data if available, otherwise an empty series response. CallsupstreamAPIKeyForDashboard()for the key.(*Proxy) HandleRequest(w, r)— main router. Routes include/api/umans/status→HandleStatus,/api/umans/status-history→HandleStatusHistory(alongside existing/api/umans/usage,/api/umans/concurrency,/api/umans/usage-history,/api/umans/user, etc.)(*Proxy) HandleBingWallpaper(w, r)— daily cache; callsupgradePeapixResolution()for UHD variant(*Proxy) HandleWallhavenWallpaper(w, r)— hourly cache;atleast=2560x1440filter; usesisValidImage+serveWallpaperImageTypedwithimageContentType
- Concurrency Limit selector — three-button group (Soft Cap / Hard Cap / Manual) replacing the old Burst Mode On/Off toggle. Soft Cap gates at the soft cap (Limit), Hard Cap gates at the hard cap (HardCap), Manual reveals a slider (1 to hard cap) for a custom concurrency limit. Frontend POSTs
concurrencyLimitModeandmanualConcurrencyLimitto/api/config;loadConfig()initializes the state from backend;fetchConcurrency()readsconcurrency_limit_modeandmanual_limitfrom the concurrency API response and dynamically updates the slider max/position - Slot Free Delay slider — range slider (0–60s, default 2) in Quick Settings. POSTs
slotFreeDelayto/api/configon change. Shows live value. - Request Timeout slider — range slider (30s–30m, step 30s, default 15m) in Quick Settings. POSTs
requestTimeout(seconds) to/api/configon change. Applied live viaUpstream.SetTimeout()without restart. Positioned above Retry Attempts. - Retry Attempts slider — range slider (0–10, default 2) in Quick Settings. POSTs
retryAttemptsto/api/configon change. Shows live value. - Backoff Strategy dropdown — select in Quick Settings (hidden when retries=0). Options: Aggressive (1s→60s) or Conservative (5s→300s). POSTs
backoffStrategyto/api/configon change. Shows delay sequence preview below. - Priority card — replaces the old Throttled card in the usage stat grid. Always visible. Shows "Low (reason)" in yellow when priority is low, "Normal" in white otherwise.
- Usage Limits card — consolidates concurrency usage progress bar and priority budget inline bars (matching visual style). Shows a service mode banner when upstream is in low-interactivity mode. Collapsible (expanded by default).
- Service Status card — overall health stats and per-model breakdown with 3 view variants: Table (default), Bars, Heatmap. All views are fully dynamic from the upstream
/api/umans/statusresponse. Color thresholds: uptime ≥99.5% green / ≥99% yellow / below red; TTFT ≤1.5s green / ≤3s yellow / above red; TPS ≥100 green / ≥50 yellow / below red. Collapsible. - 5-Hour Window card — uses 6 static stat cards instead of dynamic DOM injection. Collapsible.
- Removed standalone API Key card — Manage API Keys button moved into Quick Settings (visible when API key mode is Smart or Managed; hidden in Pass-Through).
- Collapsible cards — Usage Limits, Current Concurrency, 5-Hour Window, Service Status, and Usage History are all collapsible (matching pattern).
- Mobile auto-collapse — all sections except Usage Limits collapse by default on screens ≤575px.
- Collapsed-by-default sections — Quick Actions, Models, and Environment sections start collapsed (have
collapsedclass in HTML). Other sections start expanded. - Environment section — shows Version (from
proxyData.version), Runtime (Go version), Port, and Started At. The collapsed header preview text showsv{version} · {runtime} · :{port}. - Header title — "UMANS Dash Go" (was "UMANS Dash").
setWallpaper(src, skipConfigSave)—skipConfigSaveparameter avoids redundant POST during initloadConfig()— decoupled from/healthz(fetched in background, not awaited); hides loader beforefetchUsage()(fire-and-forget)toggleSection()— animates section collapse/expand via JS-driven height transition (scrollHeight→0and back); swapsbi-chevron-down↔bi-chevron-righticon classes. CSS.collapsedsetsheight:0;opacity:0;padding:0;display:noneis applied inline by JS only aftertransitionendfires, so the collapse animation is visibleclearWallpaper()— usessetProperty(..., 'important')to override server-injected!importantCSS- Unified refresh cycle — all content (status, usage, concurrency, history) on one timer, persisted to localStorage
fetchConcurrency()— called on init (was missing previously). Readsconcurrency_limit_modeandmanual_limitfrom the API response to determine the effective gate (barMax) for the progress bar. Updates the manual slider max to the current hard cap. The redundant "Queued" card in the detail grid below the bar was removed (the count is already shown in the stat card above the bar)
go build ./... # Compile check
go vet ./... # Static analysis
go run . # Start the proxyThe Proxy struct holds several dedicated mutexes. Code touching these fields must acquire the correct lock:
| Mutex | Protects | Notes |
|---|---|---|
configMu (RWMutex) |
All Config fields |
RLock() in read-only handlers (ResolveModelId, NeedsVisionHandoff, HandleKeysGet, HandleConfigGet). Lock() in HandleConfigPost / mutations. Note: proxyChatRequest and proxyAnthropicRequest now snapshot needed config fields (ApiKeyMode, MaxImages, UpstreamBaseURL) under a brief RLock then release before the upstream call — previously held RLock for the entire request lifecycle, blocking HandleConfigPost. |
queueMu (RWMutex) |
ActiveRequests, requestQueue ([]*QueueItem), ThrottledCount, queueCond, queuePending, QueueLen |
All access via accessor helpers (getActiveRequests, getQueueLen, getThrottledCount, bumpThrottled). sync.Cond (queueCond) is bound to queueMu — Wait() uses write lock/unlock. Readers (dashboard) use RLock()/RUnlock() to avoid blocking the cond. Only the queue worker and signalQueue/trySignalQueueLocked use Lock()/Unlock(). Lock ordering: acquire queueMu BEFORE configMu/concurrencyLimitMu/catalogMu. Config handlers that mutate config must release their lock before calling signalQueue()/ProcessQueue(). |
catalogMu (RWMutex) |
ModelInfoMap, DisplayNameMap |
Lock() in ApplyCatalogData; RLock() in all catalog readers. |
convMu (Mutex) |
conversationMap, globalSessionCounter |
Used by both OpenAI and Anthropic paths for session tracking. |
rw.mu (in responseWriterTracker) |
Hijack/flush state | Flush() acquires rw.mu, checks hijacked, releases the lock, then calls Flush() — preventing flush-after-hijack panics. |
lastClientKeyMu (RWMutex) |
lastClientKey |
Lock() in setLastClientKey; RLock() in getLastClientKey. Tracks last-known-good client API key for usage/history calls when ApiKeyMode is passthrough/smart. |
concurrencyLimitMu (RWMutex) |
concurrencyLimitMode, manualLimit |
Lock() in setConcurrencyLimitMode/setManualLimit; RLock() in getConcurrencyLimitMode/getManualLimit. gateLimit() takes one RLock and reads both fields atomically. Gates gateLimit(): soft → soft cap (Limit), hard → hard cap (HardCap), manual → manualLimit clamped to [1, HardCap]. Restored from Config.ConcurrencyLimitMode in NewProxy(). |
retryConfigMu (RWMutex) |
retryAttempts, backoffStrategy |
Lock() in setRetryConfig; RLock() in getRetryAttempts/getBackoffStrategy. getRetryAttempts() uses < 0 guard (not <= 0) so explicit 0 passes through. Restored from Config.RetryAttempts / Config.BackoffStrategy in NewProxy(). |
Graceful shutdown (Shutdown()): sets shuttingDown, broadcasts queueCond and closes queueDone (guarded by sync.Once) to wake the queue worker and cancel pending slot-free delays, then waits for workerWG (the queue worker goroutine exits on shutdown). After the worker exits, it rejects all queued items via rejectQueueLocked() (503 to each), then polls ActiveRequests under queueMu.RLock every 50 ms (5 s timeout) before calling httpServer.Shutdown(), ensuring in-flight requests drain before the listener closes.
Zero. Go standard library only.
Features present in the upstream Node.js original that were intentionally removed:
- Sleev context-compression gateway — local gateway daemon, binary resolution, OAuth sign-in (
SLEEV_ENABLED,SLEEV_GATEWAY_*) - FreeGen AI wallpaper generation —
/api/bg-freegenendpoint,FREEGEN_PROMPT, WebSocket integration with freegen.app - i18n translation system —
/api/i18nroute,I18N_STRINGScatalog,LOCALEconfig, one-click autotranslate viaumans-flash - Shell Guard —
isGitCommand(),sanitizeShellToolCall(), blocking git commands in shell tool-call responses (streaming and non-streaming) - Response Cache — LRU cache for non-streaming chat responses (
ResponseCache,cacheKey(),CACHE_TTL/CACHE_MAX_SIZE/CACHE_ENABLEDconfig,/api/cacheGET/DELETE endpoints) - UMANS app login —
EMAIL/PASSWORD/APP_SESSIONconfig,loginToApp(),/api/umans/loginand/api/umans/logoutendpoints, platform login modal - Rate Limit Map —
RATE_LIMIT_MAP,enforceRateLimit(), per-model rate limit delays - Test Chat panel — streaming/context chat panel with model selector in the dashboard
- SS Mode — screenshot-safe mode (blur on hover, jumbled user ID, masked email)
- Glass UI — procedural SVG filter-based glassmorphism (
feDisplacementMap,feColorMatrix); replaced with CSSbackdrop-filter - SQLite Usage Cache —
.cache/usage.dbpersistent cache for daily usage-history buckets (node:sqlite/bun:sqlite) Enqueue()andHandleRestartMock()— dead code removed