Skip to content

refactor: deduplicate shared helpers and consolidate app startup wiring#384

Merged
SantiagoDePolonia merged 16 commits into
mainfrom
refactor/cleaning
Jun 10, 2026
Merged

refactor: deduplicate shared helpers and consolidate app startup wiring#384
SantiagoDePolonia merged 16 commits into
mainfrom
refactor/cleaning

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Cleanup pass across the codebase: removes duplicated code and dead indirection in 14 focused commits, −327 net lines with no behavior changes. Full test suite (including make test-race and lint via pre-commit) passed on every commit.

Cross-package deduplication

  • ValidationError was defined identically in 5 packages (aliases, authkeys, guardrails, workflows, modelselectors) → single internal/validation.Error behind type aliases; every package's public API (ValidationError, IsValidationError) is unchanged
  • New internal/storage/sqlutil package collects helpers that had drifted into copies: LIKE-wildcard escaping, WHERE-clause building, pagination clamping (parameterized — usage keeps 50/200, auditlog keeps 25/100), unix-timestamp/nullable-string column converters, and SQLite timestamp parsing

App startup wiring (internal/app/app.go, −94 lines)

  • New() repeated hand-maintained errors.Join close chains at 11 failure points, each re-listing every prior component — and the close order had already drifted between blocks. Replaced with a LIFO closers slice unwound by a single fail() helper, so shutdown order on startup failure has one source of truth
  • The growing firstSharedStorage(a, b, c, ...) calls became a single running sharedStorage claimed after each component initializes

Provider layer

  • Passthrough semantic enrichers: four providers carried identical Enrich() mechanics → shared providers.SemanticEnricher; each provider now declares only its endpoint table
  • ListBatches pagination query building deduplicated across 6 providers (providers.PaginatedEndpoint, cursor param name covers Anthropic's before_id)
  • core.EnsureModel names the empty-model response fallback repeated at 12 sites; ollama's five unsupported-batch stubs share one error constructor

Core / JSON / readers

  • The 25-entry responses known-field list was inlined twice (drifting only by stream/stream_options) → defined once, with the full-request variant layered explicitly on top
  • core.IsJSONNull replaces 12 copies of the trimmed-null check; slices.Contains replaces a hand-rolled search
  • MongoDB usage reader: 41 replacements via mongoCostSum / mongoCostPresenceCount / costPtr helpers for the repeated cost-aggregation BSON and decode blocks
  • server/native_batch_support.go: removed five pass-through wrappers around gateway functions (most were test-only); callers use gateway directly

User-visible impact

None intended. Public APIs, error types, wire formats, and audit/usage semantics are unchanged. The only message-level change: a few startup failure error strings now use the uniform failed to <step>: <err> (also: close error: <err>) format.

Notes for reviewers

  • The app.go rewrite is the most behavior-sensitive commit (73c1dc4): cleanup-on-failure now always closes in exact reverse-init order, where the old hand-written chains had minor order inconsistencies between blocks (e.g. the response-cache failure path closed workflows before authKeys). Closing order among these independent stores is not semantically significant, which is why this is safe — but worth a look.
  • Latest main (Go 1.26.4 bump, TTS audio-duration costing) is merged in.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Consolidated validation error handling across components.
    • Centralized SQL helpers for consistent query building, wildcard escaping, and timestamp handling.
    • Unified provider passthrough semantics and standardized pagination endpoint construction.
    • Standardized JSON-null handling and response model defaulting.
  • Bug Fixes

    • More reliable startup/shutdown error handling and resource cleanup.
    • Safer, consistent filtering and pagination behavior in logs/usage queries.

SantiagoDePolonia and others added 15 commits June 9, 2026 17:15
Five packages (aliases, authkeys, guardrails, workflows, modelselectors)
each defined an identical ValidationError type. Replace them with type
aliases to a single internal/validation.Error, keeping every package's
public API (ValidationError, IsValidationError) unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
escapeLikeWildcards and buildWhereClause were byte-for-byte duplicates
in usage and auditlog; clampLimitOffset differed only in pagination
bounds. Move them to internal/storage/sqlutil, with each package
keeping a one-line clampLimitOffset wrapper expressing its policy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
authkeys and budget stores each carried their own copies of the
unix-timestamp and nullable-string conversion helpers (with pg-prefixed
duplicates in the PostgreSQL store). Move them to
internal/storage/sqlutil under provider-agnostic names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New() repeated hand-maintained errors.Join chains at every init failure
point (11 chains, each re-listing every prior component, with order
already drifting between blocks) and called firstSharedStorage with a
growing argument list at each step. Replace both with a LIFO closers
slice unwound by a single fail() helper, and a running sharedStorage
claimed after each component initializes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 25-entry known-field list for responses requests was inlined twice
in responses_json.go, differing only by the streaming controls. Define
responsesUtilityRequestFields once with responsesRequestFields layering
stream/stream_options on top, making that relationship explicit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Six providers repeated the same 3-line fallback that copies the
requested model into responses that omit it. Replace with
core.EnsureModel so the OpenAI-compatibility rule is stated once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pers

native_batch_support.go re-exported five gateway functions (and a
selection struct) without adding logic; most were only called from
tests. Call gateway directly and keep only the two cleanup methods
that carry real handler logic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four providers carried identical Enrich() mechanics differing only in
their endpoint-to-semantics mapping. Move the mechanics to a shared
providers.SemanticEnricher; each provider now declares just its
endpoint table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Six providers repeated the same url.Values limit/cursor block in
ListBatches. Replace with providers.PaginatedEndpoint, parameterized by
cursor name to cover Anthropic's before_id paging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every aggregation pipeline repeated the $ifNull cost sum and $cond
presence-counter BSON blocks (14 of each), and every decoder repeated
the has-count-to-pointer conversion (13 sites). Extract mongoCostSum,
mongoCostPresenceCount, and costPtr.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The len-or-null-literal check on trimmed JSON appeared 12 times across
core, guardrails, anthropicapi, and the anthropic provider.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 68a84707-431a-4ed4-8e48-0d74fc0e0360

📥 Commits

Reviewing files that changed from the base of the PR and between 5fe66d0 and 9ff0955.

📒 Files selected for processing (1)
  • internal/app/app.go

📝 Walkthrough

Walkthrough

This PR adds shared utilities (validation, sqlutil, JSON-null, provider enrichment), migrates many modules to use them (stores, readers, providers, server tests), and refactors app startup/shutdown to centralize cleanup and shared storage claiming.

Changes

Centralized Utilities and Cross-Module Consolidation

Layer / File(s) Summary
Shared validation error module and store adoption
internal/validation/error.go, internal/aliases/store.go, internal/authkeys/store.go, internal/guardrails/store.go, internal/modelselectors/selectors.go, internal/workflows/store.go
New validation.Error struct plus NewError/IsError; multiple stores replaced local ValidationError structs with aliases and delegate construction/detection to shared helpers.
SQL utility module foundation
internal/storage/sqlutil/sqlutil.go
Introduces EscapeLikeWildcards, BuildWhereClause, ClampLimitOffset, ParseSQLiteTimestamp, UnixOrNil, TimeFromUnix, TimeFromUnixPtr, NullableString, StringFromNullable, and DerefTrimmed.
Audit log migration to SQL utilities
internal/auditlog/reader_helpers.go, internal/auditlog/reader_postgresql.go, internal/auditlog/reader_sqlite.go, internal/auditlog/user_path_filter.go, internal/auditlog/user_path_filter_test.go
Audit log readers and tests now use sqlutil for pagination clamping, LIKE wildcard escaping, WHERE clause building, and SQLite timestamp parsing; local helpers removed.
Auth keys and budget store conversions
internal/authkeys/store_postgresql.go, internal/authkeys/store_sqlite.go, internal/authkeys/store.go, internal/budget/store_postgresql.go, internal/budget/store_sqlite.go
Store implementations replaced local nullable/time/string conversions with sqlutil helpers (NullableString, UnixOrNil, TimeFromUnix*, DerefTrimmed, etc.).
Core JSON null helper and adoption
internal/core/json_fields.go, internal/core/chat_content.go, internal/core/responses_json.go, internal/anthropicapi/request.go, internal/guardrails/store_mongodb.go, internal/providers/anthropic/request_translation.go
Adds IsJSONNull and replaces explicit empty/"null" byte checks with the centralized helper for unmarshalling and request-field handling.
Usage readers migration and MongoDB aggregation refactor
internal/usage/reader_helpers.go, internal/usage/reader_postgresql.go, internal/usage/reader_sqlite.go, internal/usage/recalculate_pricing_postgresql.go, internal/usage/recalculate_pricing_sqlite.go, internal/usage/user_path_filter.go, internal/usage/reader_mongodb.go
Usage readers and recalculation queries now use sqlutil for WHERE/wildcard/pagination/timestamp parsing; MongoDB pipelines consolidate nullable-cost $ifNull/$cond expressions into helpers and use costPtr to set nullable totals.
Core EnsureModel helper and provider adoption
internal/core/types.go, internal/providers/deepseek/deepseek.go, internal/providers/gemini/gemini.go, internal/providers/groq/groq.go, internal/providers/ollama/ollama.go, internal/providers/openai/compatible_provider.go, internal/providers/xai/xai.go
Adds core.EnsureModel and updates providers to call it for consistent response-model fallback.
Provider semantic enricher infrastructure and adoption
internal/providers/passthrough.go, internal/providers/anthropic/passthrough_semantics.go, internal/providers/deepseek/passthrough_semantics.go, internal/providers/openai/passthrough_semantics.go, internal/providers/vllm/passthrough_semantics.go
Adds providers.SemanticEnricher and NewSemanticEnricher; replaces bespoke endpoint-normalization enrichers with declarative mappings and updates tests/registrations.
Provider PaginatedEndpoint helper and adoption
internal/providers/endpoints.go, internal/providers/azure/azure.go, internal/providers/gemini/gemini.go, internal/providers/groq/groq.go, internal/providers/openai/compatible_provider.go, internal/providers/xai/xai.go
Introduces PaginatedEndpoint to compose optional limit/cursor query params; providers refactored to use it and drop manual url.Values/strconv usage.
App startup centralized error handling and shared storage
internal/app/app.go
Refactors app init to use a closers stack and fail(...) unwind helper, introduces shared-storage claiming, and updates shutdown to explicitly shutdown server before sequentially closing subsystems.
Server batch/response helpers extracted to gateway package
internal/server/handlers_test.go, internal/server/native_batch_support.go, internal/server/native_batch_support_test.go, internal/server/native_response_service.go, internal/server/workflow_policy_test.go
Removes local handler wrappers for batch/response helpers and updates tests/runtime callsites to use equivalents from gomodel/internal/gateway.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ENTERPILOT/GoModel#314: DeepSeek passthrough/enricher changes overlap with provider passthrough refactors here.
  • ENTERPILOT/GoModel#306: App startup/ pricing-overrides initialization touches similar startup wiring and shared-storage concerns.
  • ENTERPILOT/GoModel#210: Related guardrails/workflows startup and executor wiring refactors.

Suggested labels

release:internal

Poem

🐰 I hopped through files with ears held high,

Found nulls and wildcards hiding sly,
One validation home, one SQL rule, one map,
Shared helpers stitched the scattered gap—
A tidy burrow, now we say hi!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main changes: refactoring to deduplicate shared helpers and consolidate app startup wiring, which aligns with the core objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/cleaning

@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown

Greptile Summary

This PR deduplicates helper code and startup wiring across the gateway. The main changes are:

  • Shared validation error and SQL utility helpers.
  • LIFO startup cleanup and shared storage selection in app initialization.
  • Common provider pagination and passthrough semantic enrichment helpers.
  • Consolidated JSON null checks, response field lists, and Mongo usage aggregation helpers.
  • Removed server batch wrapper functions in favor of gateway helpers.

Confidence Score: 4/5

This is close, but the validation error behavior should be fixed before merging.

  • Package-specific validation predicates now match validation errors from unrelated packages.

  • Most other refactors preserve the previous helper logic and call patterns.

  • No PR-scoped security issue was found.

  • internal/validation/error.go

  • Package aliases that expose IsValidationError

Important Files Changed

Filename Overview
internal/validation/error.go Introduces the shared validation error type that changes package-specific error matching behavior.
internal/app/app.go Replaces repeated startup cleanup chains with one LIFO closer list and running shared storage selection.
internal/providers/passthrough.go Adds the shared passthrough semantic enricher used by OpenAI-like providers.
internal/storage/sqlutil/sqlutil.go Collects SQL escaping, pagination, timestamp, and nullable conversion helpers.

Reviews (1): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment on lines +34 to +36
func IsError(err error) bool {
_, ok := errors.AsType[*Error](err)
return ok

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Validation checks widened

IsError now matches the single shared *validation.Error type, so every package aliasing this type accepts validation errors from every other package. For example, aliases.IsValidationError(err) now returns true for an auth key, guardrail, workflow, or model selector validation error, whereas those were distinct public error types before. Callers that use a package-specific predicate to choose response handling can now classify an unrelated subsystem error as this package's validation failure.

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.

Acknowledged but intended: the widening is the point of the consolidation. In practice each admin handler applies its predicate only to errors returned by its own subsystem service, and the one place validation errors already crossed package lines before this PR (model/pricing overrides aliasing modelselectors.ValidationError) wanted exactly this classification. If a foreign validation error ever did reach another package's predicate, classifying it as a 400 invalid-input rather than a 500 is the more accurate outcome. No caller relies on package-specific discrimination (verified: the only external consumers are the per-feature validationWriter wrappers in internal/admin/errors.go).

@codecov-commenter

codecov-commenter commented Jun 10, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🤖 Prompt for all review comments with 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.

Inline comments:
In `@internal/app/app.go`:
- Around line 117-147: The providers.Init error path bypasses the unwinder so
app.live can leak; before calling providers.Init you must register a closer that
will shut down app.live into the closers slice (e.g., append a func() error that
calls app.live.Shutdown(...) or app.live.Close() as appropriate) and then
replace the direct return on providers.Init failure with return fail("failed to
initialize providers", err) so the unified fail(...) unwinds app.live and other
closers; ensure you reference the existing closers slice and fail function and
add the app.live closer prior to calling providers.Init.
- Around line 472-477: The ResponseCacheMiddleware (rcm) is never closed on
normal shutdown because App.Shutdown doesn't call Server.Shutdown; update the
shutdown flow so rcm.Close is always executed: either (preferred) modify
App.Shutdown to call a.server.Shutdown(ctx) (which will invoke
s.responseCacheMiddleware.Close()), or ensure the App-level closers pipeline
explicitly includes rcm.Close on successful start and is executed during normal
shutdown; reference rcm / responseCacheMiddleware, App.Shutdown and
internal/server.Server.Shutdown to locate where to add the call or to add
rcm.Close to the App's closers so the cache is drained on normal shutdown.

In `@internal/providers/passthrough.go`:
- Around line 90-95: The lookup misses endpoints with trailing slashes because
Enrich() only strips leading slashes; modify the normalization used before the
endpoints map lookup (the normalizedEndpoint computed from
PassthroughEndpointPath(&enriched) and used in the lookup against e.endpoints)
to also trim trailing slashes (e.g., strings.TrimRight(..., "/")) so that
equivalents like "responses/" and "/messages/" resolve to the same key and
populate enriched.SemanticOperation and enriched.AuditPath; keep the existing
fallback that sets enriched.AuditPath =
"/p/"+e.providerType+"/"+normalizedEndpoint when no semantic exists. Also add a
regression test in the provider table tests that calls Enrich()/Passthrough path
logic with a known endpoint including a trailing slash and asserts the same
SemanticOperation/AuditPath as the non-trailing-slash variant to lock this
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c31fbc7e-fa20-4d9b-8af5-3c23bc523183

📥 Commits

Reviewing files that changed from the base of the PR and between 8b79b38 and 5fe66d0.

📒 Files selected for processing (55)
  • internal/aliases/store.go
  • internal/anthropicapi/request.go
  • internal/app/app.go
  • internal/auditlog/reader_helpers.go
  • internal/auditlog/reader_postgresql.go
  • internal/auditlog/reader_sqlite.go
  • internal/auditlog/user_path_filter.go
  • internal/auditlog/user_path_filter_test.go
  • internal/authkeys/store.go
  • internal/authkeys/store_postgresql.go
  • internal/authkeys/store_sqlite.go
  • internal/budget/store_postgresql.go
  • internal/budget/store_sqlite.go
  • internal/core/chat_content.go
  • internal/core/json_fields.go
  • internal/core/responses_json.go
  • internal/core/types.go
  • internal/guardrails/store.go
  • internal/guardrails/store_mongodb.go
  • internal/modelselectors/selectors.go
  • internal/providers/anthropic/anthropic.go
  • internal/providers/anthropic/passthrough_semantics.go
  • internal/providers/anthropic/passthrough_semantics_test.go
  • internal/providers/anthropic/request_translation.go
  • internal/providers/azure/azure.go
  • internal/providers/deepseek/deepseek.go
  • internal/providers/deepseek/passthrough_semantics.go
  • internal/providers/deepseek/passthrough_semantics_test.go
  • internal/providers/endpoints.go
  • internal/providers/gemini/gemini.go
  • internal/providers/groq/groq.go
  • internal/providers/ollama/ollama.go
  • internal/providers/openai/compatible_provider.go
  • internal/providers/openai/openai.go
  • internal/providers/openai/passthrough_semantics.go
  • internal/providers/openai/passthrough_semantics_test.go
  • internal/providers/passthrough.go
  • internal/providers/vllm/passthrough_semantics.go
  • internal/providers/vllm/vllm.go
  • internal/providers/xai/xai.go
  • internal/server/handlers_test.go
  • internal/server/native_batch_support.go
  • internal/server/native_batch_support_test.go
  • internal/server/native_response_service.go
  • internal/server/workflow_policy_test.go
  • internal/storage/sqlutil/sqlutil.go
  • internal/usage/reader_helpers.go
  • internal/usage/reader_mongodb.go
  • internal/usage/reader_postgresql.go
  • internal/usage/reader_sqlite.go
  • internal/usage/recalculate_pricing_postgresql.go
  • internal/usage/recalculate_pricing_sqlite.go
  • internal/usage/user_path_filter.go
  • internal/validation/error.go
  • internal/workflows/store.go
💤 Files with no reviewable changes (1)
  • internal/server/native_batch_support.go

Comment thread internal/app/app.go Outdated
Comment thread internal/app/app.go
Comment on lines +90 to +95
normalizedEndpoint := strings.TrimLeft(strings.TrimSpace(PassthroughEndpointPath(&enriched)), "/")
if semantics, ok := e.endpoints["/"+normalizedEndpoint]; ok {
enriched.SemanticOperation = semantics.Operation
enriched.AuditPath = semantics.AuditPath
} else if strings.TrimSpace(enriched.AuditPath) == "" && normalizedEndpoint != "" {
enriched.AuditPath = "/p/" + e.providerType + "/" + normalizedEndpoint

@coderabbitai coderabbitai Bot Jun 10, 2026

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Trim trailing slashes before the endpoint-table lookup.

Enrich() only removes leading /, so equivalent inputs like "responses/" or "/messages/" miss the endpoints map and fall through to the generic /p/{provider}/... audit path. That silently changes SemanticOperation/AuditPath classification for a user-supplied endpoint variant this shared normalizer should accept.

Suggested fix
-	normalizedEndpoint := strings.TrimLeft(strings.TrimSpace(PassthroughEndpointPath(&enriched)), "/")
+	normalizedEndpoint := strings.Trim(strings.TrimSpace(PassthroughEndpointPath(&enriched)), "/")
 	if semantics, ok := e.endpoints["/"+normalizedEndpoint]; ok {

Please add one regression case in the existing provider table tests for a known endpoint with a trailing slash so this stays locked down.

As per coding guidelines, **/*.go should accept user requests generously and preserve the OpenAI-compatible public API; the PR objective also says audit/usage semantics should not change.

📝 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
normalizedEndpoint := strings.TrimLeft(strings.TrimSpace(PassthroughEndpointPath(&enriched)), "/")
if semantics, ok := e.endpoints["/"+normalizedEndpoint]; ok {
enriched.SemanticOperation = semantics.Operation
enriched.AuditPath = semantics.AuditPath
} else if strings.TrimSpace(enriched.AuditPath) == "" && normalizedEndpoint != "" {
enriched.AuditPath = "/p/" + e.providerType + "/" + normalizedEndpoint
normalizedEndpoint := strings.Trim(strings.TrimSpace(PassthroughEndpointPath(&enriched)), "/")
if semantics, ok := e.endpoints["/"+normalizedEndpoint]; ok {
enriched.SemanticOperation = semantics.Operation
enriched.AuditPath = semantics.AuditPath
} else if strings.TrimSpace(enriched.AuditPath) == "" && normalizedEndpoint != "" {
enriched.AuditPath = "/p/" + e.providerType + "/" + normalizedEndpoint
🤖 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 `@internal/providers/passthrough.go` around lines 90 - 95, The lookup misses
endpoints with trailing slashes because Enrich() only strips leading slashes;
modify the normalization used before the endpoints map lookup (the
normalizedEndpoint computed from PassthroughEndpointPath(&enriched) and used in
the lookup against e.endpoints) to also trim trailing slashes (e.g.,
strings.TrimRight(..., "/")) so that equivalents like "responses/" and
"/messages/" resolve to the same key and populate enriched.SemanticOperation and
enriched.AuditPath; keep the existing fallback that sets enriched.AuditPath =
"/p/"+e.providerType+"/"+normalizedEndpoint when no semantic exists. Also add a
regression test in the provider table tests that calls Enrich()/Passthrough path
logic with a known endpoint including a trailing slash and asserts the same
SemanticOperation/AuditPath as the non-trailing-slash variant to lock this
behavior.

Source: Coding guidelines

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.

Not fixing in this PR: the claim that this "silently changes" classification is incorrect — the original per-provider implementations used the identical strings.TrimLeft(strings.TrimSpace(...), "/") + switch, so a trailing-slash endpoint missed the match table before this refactor too (verified against the pre-refactor files). PassthroughEndpointPath doesn't strip trailing slashes either. Accepting trailing slashes would be a deliberate behavior change to audit/semantic classification, which this refactor explicitly avoids; happy to do it as a small follow-up PR with regression tests if desired.

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, thanks for the detailed correction — that's a fair point. The refactor faithfully preserves the pre-existing normalization behavior, and framing it as "silently changes" classification was wrong on my part. The strings.TrimLeft(..., "/") pattern being identical to the original per-provider switch-based implementations confirms this is a straight lift, not a behavioral delta.

If you'd like, I can open a follow-up GitHub issue to track the trailing-slash normalization improvement (with the regression test you mentioned). Just say the word.


✏️ Learnings added
Learnt from: SantiagoDePolonia
Repo: ENTERPILOT/GoModel PR: 384
File: internal/providers/passthrough.go:90-95
Timestamp: 2026-06-10T14:37:15.288Z
Learning: In `internal/providers/passthrough.go`, `SemanticEnricher.Enrich()` uses `strings.TrimLeft(strings.TrimSpace(PassthroughEndpointPath(&enriched)), "/")` to normalize the endpoint before the map lookup. This is intentionally identical to the pre-refactor per-provider implementations (e.g., anthropic, openai, deepseek, vllm passthrough semantics), which used the same pattern. `PassthroughEndpointPath` does not strip trailing slashes, so endpoints like "responses/" will miss the lookup table — but this is pre-existing behavior preserved deliberately. Any change to accept trailing slashes would be a deliberate behavior change to audit/semantic classification and should be done in a separate PR with explicit regression tests.

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.


🧠 Learnings used
Learnt from: SantiagoDePolonia
Repo: ENTERPILOT/GOModel PR: 33
File: internal/auditlog/auditlog_test.go:272-279
Timestamp: 2026-01-15T23:07:26.646Z
Learning: Go code guideline: Avoid using interface{} and map[string]interface{} for API request/response payload types. This should be restricted to public API surfaces and data payloads. It is acceptable to use interface{} in internal infrastructure packages (e.g., storage abstractions) only to break import cycles, and only when type assertions occur at initialization (not on every request) to minimize runtime type checks and improve readability. Apply this rule broadly to files under internal/ that deal with payload-like types, but not to internal components that are pure infrastructure or have runtime-per-request type assertions.

Learnt from: SantiagoDePolonia
Repo: ENTERPILOT/GOModel PR: 33
File: internal/auditlog/factory.go:112-143
Timestamp: 2026-01-15T23:07:37.652Z
Learning: Guideline: Do not use interface{} or map[string]interface{} for API request/response payload types. Prefer strongly-typed structs for API payload definitions to improve type safety, serialization, and documentation. Allow interface{} only in internal infrastructure code paths where pragmatic flexibility is necessary (e.g., to avoid import cycles or to handle highly dynamic internal contracts). In internal/auditlog/factory.go and similar non-API implementation files, applying this restriction is optional and should be evaluated on a case-by-case basis based on whether the type remains internal and does not define API boundary shapes.

Learnt from: SantiagoDePolonia
Repo: ENTERPILOT/GOModel PR: 99
File: internal/providers/responses_adapter.go:128-131
Timestamp: 2026-02-25T16:55:22.071Z
Learning: In internal/providers/responses_adapter.go (and related files), Do and DoStream should already normalize provider errors using core.ParseProviderError(). Call sites (e.g., ChatCompletion, StreamChatCompletion) receive normalized GatewayError types and must not call ParseProviderError again. When reviewing code, ensure no downstream calls duplicate Normalize error handling and that all provider-error surfaces rely on the GatewayError type as the canonical representation. If you find code that re-parses errors with ParseProviderError, suggest removing that duplication and relying on the existing normalization path.

Learnt from: SantiagoDePolonia
Repo: ENTERPILOT/GoModel PR: 294
File: internal/admin/handler_usage.go:243-244
Timestamp: 2026-04-30T07:12:57.910Z
Learning: In Go code targeting Go 1.26+ (e.g., this repo uses Go 1.26.x), do not flag `errors.AsType[T](err)` as a missing/non-existent API. `errors.AsType[E error](err error)` returns `(E, bool)` as a type-safe, generic alternative to `errors.As`. For example, code like `if val, ok := errors.AsType[*SomeErrorType](err); ok { ... }` is valid standard-library usage and should be treated as such during review.

Learnt from: SantiagoDePolonia
Repo: ENTERPILOT/GoModel PR: 294
File: internal/admin/handler_usage.go:243-244
Timestamp: 2026-04-30T07:12:57.910Z
Learning: In Go 1.26+ code, `errors.AsType[T](err)` is a standard-library, generic alternative to `errors.As`. Do not flag `errors.AsType[...]` as a non-existent function or compilation error when the repository’s Go version is 1.26 or newer. The expected usage pattern is `if val, ok := errors.AsType[*SomeErrorType](err); ok { ... }`.

Two lifecycle gaps surfaced by PR review:

- Startup failures returned before anything closed the live log broker;
  it is now the first entry on the closers stack, and the providers.Init
  failure routes through the same unwinder as every later step.
- Server.Shutdown (which drains in-flight response cache writes and
  closes the response/conversation stores) was never invoked: graceful
  shutdown only cancelled the HTTP server context. App.Shutdown now
  calls it once the server has stopped. Both gaps predate the refactor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants