internal/api
August 7, 2026 · View on GitHub
Mirror notice. Verbatim sync with AGENTS.md. Update both together — divergence = bug.
Presentation layer. Handlers adapt HTTP ↔ Service. Read root CLAUDE.md first.
Subpackages
admin/— operational endpoints:/health,/validate,/admin/v1/*anthropic/— Anthropic Messages surface (/v1/messages, passthrough,/v1/route)openai/— OpenAI Chat Completions (/v1/chat/completions)gemini/— Gemini native (/v1beta/models/:modelAction)analytics/— read-only routing-decision export (/v1/analytics/routing-decisions,/models,/schema). Authed byra_analytics keys viamiddleware.WithAnalyticsKeyonly — noWithAuth, no balance check, no spend cap, since nothing here can route or spend.feedback/— no-login feedback-link surface (/f/<token>, rating submit). The token itself (signed viainternal/feedback) is the sole credential, so this is the one subpackage that deliberately carries no auth middleware — do not addWithAuth/WithAdminOnlyhere; that would break the whole point of a shareable no-login link.
Import rules
- May import
internal/auth(Service handle + middleware-context types) andinternal/proxy(routing/dispatch service handle). - May import
internal/observabilityfor logging,internal/providersfor shared sentinel errors,internal/router/clusterforErrClusterUnavailablesentinel +DeployedModelsSourceinterface,internal/analyticsfor the export Service handle + row/schema types. - Must not import
internal/postgres, any concreteinternal/providers/*adapter, orinternal/translatedirectly. - Concrete instances reach presentation only via constructor params from composition root.
Adding an HTTP endpoint
- Decide timeout budget. Cheap auth-only ops use
validateTimeout/healthTimeout(1 s). Provider calls get own constant in../server/server.go— pick budget + justify in comment. - Decide auth. Routes needing valid
rk_bearer go throughmiddleware.WithAuth(authSvc). Admin endpoints useWithAdminOrAuth(admin cookie OR bearer) orWithAdminOnly(admin cookie only). Unauthed routes (e.g./health) attach no auth middleware. - Decide if self-hoster dashboard surface.
/ui/*static dashboard,/admin/v1/auth/*,/admin/v1mgmt group (metrics, keys, provider-keys, config, excluded-models) mount only whenmode == server.DeploymentModeSelfHosted. New endpoints whose only consumer is self-hosted dashboard go inside that block; product-surface endpoints (/v1/*,/v1beta/*,/health,/validate) stay outside so they're available inmanagedmode too. Do not add new/admin/v1/*route outside the selfhosted block — would re-expose redundant control plane on Weave-managed deploys. - Pick (or create) the right subpackage. Operational →
admin/; Anthropic Messages →anthropic/; OpenAI →openai/; Gemini →gemini/; no-login feedback-link surface →feedback/. New surfaces get their own subpackage. - Use
observability.FromGin(c)for request-scoped logger. For authed installation:middleware.InstallationFrom(c)(nil ifWithAuthnot applied — handler should be on authed group). For BYOK secrets: there's no gin-context accessor —WithAuthstashes them on the request context viacontext.WithValue(ctx, proxy.ExternalAPIKeysContextKey{}, externalKeys)(see../server/middleware/auth.go). Handlers don't read this directly; they forwardc.Request.Context()into*proxy.Servicecalls, which pull the keys back out internally viactx.Value(proxy.ExternalAPIKeysContextKey{}). - Pick the right service. Identity-only ops →
*auth.Service. Routing/dispatch/translate →*proxy.Service. Don't touch repositories, router, providers, planner/handover/cache packages from a handler. Handler adapts HTTP ↔ service; service does the work. - Test with in-memory fakes + gin testing harness (
httptest.NewRequest/ResponseRecorder). No real DB for handler tests — use fakes from../auth/service_test.goand../proxy/service_test.goas model.
History
internal/router/heuristic and internal/router/evalswitch previously lived in the API ring; both removed when heuristic fallback retired in favor of cluster.ErrClusterUnavailable → HTTP 503.