Skip to content

refactor: decoupling and speeding up#163

Merged
SantiagoDePolonia merged 13 commits intomainfrom
refactor/decoupling-and-speeding-up
Mar 22, 2026
Merged

refactor: decoupling and speeding up#163
SantiagoDePolonia merged 13 commits intomainfrom
refactor/decoupling-and-speeding-up

Conversation

@SantiagoDePolonia
Copy link
Copy Markdown
Contributor

@SantiagoDePolonia SantiagoDePolonia commented Mar 22, 2026

Summary by CodeRabbit

  • New Features

    • Asynchronous cache-write worker pool for more reliable, scalable caching
    • More robust streaming-request detection
  • Bug Fixes

    • Improved preservation and cloning of unknown/extra JSON fields across requests
    • Safer request-body snapshot handling to avoid unintended reads
  • Performance

    • Reduced allocations for unknown-field extraction and marshalling
    • Added benchmarks to validate JSON and caching hot paths
  • Tests

    • Expanded coverage and updated tests for unknown-field handling and selector parsing

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 22, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Refactors unknown JSON-field handling by replacing map-based storage with a compact UnknownJSONFields type, adds allocation-light scanning/merging helpers, switches several JSON-peek code paths to gjson, and implements an async bounded cache-write worker pool with shutdown coordination.

Changes

Cohort / File(s) Summary
Core JSON fields & helpers
internal/core/json_fields.go, internal/core/json_fields_test.go, internal/core/json_fields_bench_test.go
Introduce UnknownJSONFields wrapper, add Clone/Lookup/IsEmpty APIs, implement scanning-based extraction and byte-level merging, and add tests/benchmarks for extraction/marshalling.
Type declarations updated
internal/core/types.go, internal/core/batch.go, internal/core/responses.go, internal/core/chat_content.go
Change many ExtraFields fields from map[string]json.RawMessage to UnknownJSONFields across request/response/content types.
Core usage & cloning updates
internal/core/batch_preparation.go, internal/guardrails/provider.go, internal/providers/responses_adapter.go, internal/providers/anthropic/anthropic.go
Replace CloneRawJSONMap/map-based construction with CloneUnknownJSONFields/UnknownJSONFieldsFromMap and update call sites that copy or assign extra fields.
Tests & fixtures updated
internal/core/*_test.go, internal/providers/*_test.go, internal/guardrails/*_test.go, internal/server/*_test.go, internal/providers/groq/groq_test.go, internal/providers/openai/openai_test.go
Updated many tests to build ExtraFields via UnknownJSONFieldsFromMap(...) and to access unknown fields via ExtraFields.Lookup(...); added helper lookupUnknownField in some tests.
JSON marshal/unmarshal codepaths & benches
internal/core/json_codepaths_bench_test.go, internal/core/json_fields_bench_test.go
Add benchmarks for Chat/Responses JSON marshal/unmarshal and for unknown-field extraction/marshalling comparing old map baseline and new scanning approach.
Selector parsing (gjson) changes
internal/core/semantic.go, internal/core/semantic_test.go, internal/server/model_validation.go, internal/server/model_validation_test.go
Replace stdlib Unmarshal-based selector extraction with gjson-based peeks (deriveSnapshotSelectorHintsGJSON / selectorHintsFromJSONGJSON) and add tests/benchmarks validating semantics.
Request snapshot ownership API
internal/core/request_snapshot.go, internal/core/request_snapshot_test.go, internal/server/request_snapshot.go
Add NewRequestSnapshotWithOwnedBody(...), refactor snapshot construction to optionally avoid cloning captured body, and switch server snapshot creation to owned-body variant; add tests/benchmarks.
Cache middleware rewrite
internal/responsecache/simple.go, internal/responsecache/middleware_test.go
Replace per-request goroutine writes with bounded async worker pool (enqueue, workers, shutdown), add queue-overflow behavior, change request-body acquisition to requestBodyForCache(req) with snapshot fallback, and switch streaming detection to gjson-based check.
Performance test tweaks
tests/perf/hotpath_test.go
Add formatted perf result helper, tighten allocation/byte ceilings for hot-path tests, and log formatted results.
Misc / tooling
.gitignore, go.mod
Add /.worktrees/ to .gitignore; add github.com/tidwall/gjson v1.18.0 (and indirect tidwall deps) to go.mod.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ServerMiddleware as Middleware
    participant Enqueue as EnqueueLogic
    participant WorkerPool as Workers
    participant Store

    Client->>Middleware: HTTP request
    Middleware->>Enqueue: requestBodyForCache(req) -> (body, cacheable)
    alt cacheable
        Enqueue->>WorkerPool: enqueue write job
        WorkerPool->>Store: Set(job)
        Store-->>WorkerPool: ack
        WorkerPool-->>Middleware: in-flight decremented
    else not cacheable
        Enqueue-->>Middleware: continue without enqueue
    end
    Middleware-->>Client: respond
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Poem

🐰 I nibble bytes and hop through lines,
New fields tucked where raw JSON shines.
Gjson scouts, workers hum in queue,
Unknowns preserved in compact view —
A rabbit's cheer for code made new! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and generic, using non-descriptive terms like 'decoupling and speeding up' that don't convey specific information about the changeset's primary focus. Revise the title to be more specific about the main refactoring work, such as 'refactor: replace map-based ExtraFields with UnknownJSONFields wrapper' or 'refactor: optimize JSON field handling and add async cache writes'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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/decoupling-and-speeding-up

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/plans/2026-03-22-chat-unknown-fields-refactor.md`:
- Line 13: Heading levels jump from H1 to H3 in this document; change the task
headings like "### Task 1: Add Raw Unknown-Field Container" (and the other task
headings at the noted spots) to H2 (use "##") or insert an intermediate H2
section above them so the structure goes H1 → H2 → H3 and satisfies markdownlint
MD001.

In `@internal/core/batch_preparation_test.go`:
- Around line 40-43: The test mutates slices returned by
cloned.Requests[0].ExtraFields.Lookup("x_item") and
cloned.ExtraFields.Lookup("x_top") without checking the returned slice is
non-nil and long enough, which can panic; before assigning to itemExtra[9] and
topExtra[9] guard the Lookup results by verifying they are not nil and have
length > 9 (or handle shorter/nil cases appropriately—e.g. skip mutation or
expand/copy into a new slice) so the test won't panic when Lookup returns nil or
a shorter byte slice.

In `@internal/core/json_fields.go`:
- Around line 44-56: The code building JSON in the loop over keys writes raw
bytes verbatim (using CloneRawJSON(fields[key])) which leaves `{"key":}` when
fields[key] is nil/empty; update the loop in the function that constructs buf
(the section iterating over keys and using fields[key] and CloneRawJSON) to
check the raw value first and, if nil or length 0, write the literal "null"
after the colon, otherwise write CloneRawJSON(fields[key]) as before so the
output is valid JSON for empty values.
- Around line 271-347: scanJSONValue, scanJSONObject, and scanJSONArray
currently accept malformed bare tokens and allow missing/Trailing commas; update
them so the scanner enforces JSON syntax: in scanJSONValue's default branch
actually parse/validate bare literals (true,false,null or a JSON number) instead
of accepting any run of non-delimiters — return an error for invalid tokens; in
scanJSONObject (function scanJSONObject) after parsing a value and
skipJSONWhitespace, require either a ',' to continue or a closing '}' (otherwise
return an error) and treat a trailing ',' followed immediately by '}' as an
error; likewise in scanJSONArray (function scanJSONArray) after each element
require either ',' or ']' (reject trailing commas) and return an error if
neither is present; ensure all branches advance the index to avoid infinite
loops and propagate errors from the literal validator back to callers.
- Around line 78-90: The Lookup implementation currently returns on the first
matching member, which loses later duplicates; instead, iterate through the
entire object, and when fieldName == key store CloneRawJSON(value) into a local
result variable (do not return immediately), continue decoding remaining members
(using readJSONObjectKey/dec.Decode as today) to preserve source order, then
after the loop return the last stored result (or nil if none found); update
references in Lookup to use a local "result" (or "found") and ensure existing
error handling paths remain unchanged.

In `@internal/responsecache/simple.go`:
- Around line 72-75: The raw error returned from
requestBodyForCache(c.Request()) must be wrapped as a core.GatewayError before
returning to enforce the repository's client-facing error contract; replace the
direct return of err with constructing and returning a core.GatewayError
(populate the OpenAI-compatible fields: type set to an appropriate client-facing
category, message set to err.Error(), param = nil, code = nil) so middleware
always emits the typed GatewayError at this boundary (locate the call to
requestBodyForCache in the same function in simple.go and wrap its error result
accordingly).
- Around line 173-178: The snapshot handling in the cache path uses
snapshot.CapturedBodyView() before checking snapshot.BodyNotCaptured, causing an
empty stored slice to be treated as an empty body; change the logic in the block
that uses core.GetRequestSnapshot(req.Context()) so it first checks
snapshot.BodyNotCaptured and returns nil,false,nil when true, and only then
calls snapshot.CapturedBodyView() to reuse bytes; reference the
GetRequestSnapshot call and RequestSnapshot.CapturedBodyView/BodyNotCaptured
fields to locate and reorder the checks.

In `@internal/server/model_validation.go`:
- Around line 149-163: The function selectorHintsFromJSONGJSON currently treats
any json.Unmarshal failure as parsed=false, causing syntactically valid bodies
with wrong selector field types to skip validation; change it so parsed reflects
successful top-level object traversal (what core.VisitTopLevelJSONObjectFields
returns) and do not short-circuit on unmarshal errors: in the
VisitTopLevelJSONObjectFields callback for "model" and "provider" attempt
json.Unmarshal into model/provider but ignore/uncheck errors (still return true
to continue), so type mismatches don't set parsed=false; also add a
middleware-level regression test that sends malformed selector fields (e.g.,
{"model":123} and {"provider":123}) and asserts the request goes through normal
invalid-request validation rather than being skipped.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a686a97a-fcb8-49ea-aecc-c432bf889c83

📥 Commits

Reviewing files that changed from the base of the PR and between 76d4993 and e918c6a.

📒 Files selected for processing (36)
  • .gitignore
  • docs/superpowers/plans/2026-03-22-chat-unknown-fields-refactor.md
  • internal/core/batch.go
  • internal/core/batch_json_test.go
  • internal/core/batch_preparation.go
  • internal/core/batch_preparation_test.go
  • internal/core/chat_content.go
  • internal/core/chat_json_test.go
  • internal/core/embeddings_json_test.go
  • internal/core/json_codepaths_bench_test.go
  • internal/core/json_fields.go
  • internal/core/json_fields_bench_test.go
  • internal/core/json_fields_test.go
  • internal/core/request_snapshot.go
  • internal/core/request_snapshot_test.go
  • internal/core/responses.go
  • internal/core/responses_json_test.go
  • internal/core/semantic.go
  • internal/core/semantic_test.go
  • internal/core/types.go
  • internal/core/types_test.go
  • internal/guardrails/provider.go
  • internal/guardrails/provider_test.go
  • internal/providers/anthropic/anthropic.go
  • internal/providers/groq/groq_test.go
  • internal/providers/openai/openai_test.go
  • internal/providers/responses_adapter.go
  • internal/providers/responses_adapter_test.go
  • internal/responsecache/middleware_test.go
  • internal/responsecache/simple.go
  • internal/server/handlers_test.go
  • internal/server/model_validation.go
  • internal/server/model_validation_test.go
  • internal/server/request_snapshot.go
  • internal/server/semantic_requests_test.go
  • tests/perf/hotpath_test.go

Comment on lines +78 to +90
for dec.More() {
fieldName, ok := readJSONObjectKey(dec)
if !ok {
return nil
}

var value json.RawMessage
if err := dec.Decode(&value); err != nil {
return nil
}
if fieldName == key {
return CloneRawJSON(value)
}
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 | 🟡 Minor

Lookup should not stop at the first duplicate member.

Line 88 returns immediately on the first match, but Lines 248-255 preserve duplicate unknown members in source order. For a payload like {"x":1,"x":2}, Lookup("x") can never see the later value.

💡 Minimal fix
 func (fields UnknownJSONFields) Lookup(key string) json.RawMessage {
 	if len(fields.raw) == 0 {
 		return nil
 	}
@@
+	var match json.RawMessage
 	for dec.More() {
 		fieldName, ok := readJSONObjectKey(dec)
 		if !ok {
 			return nil
 		}
@@
 		if err := dec.Decode(&value); err != nil {
 			return nil
 		}
 		if fieldName == key {
-			return CloneRawJSON(value)
+			match = CloneRawJSON(value)
 		}
 	}
 
-	return nil
+	return match
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/json_fields.go` around lines 78 - 90, The Lookup implementation
currently returns on the first matching member, which loses later duplicates;
instead, iterate through the entire object, and when fieldName == key store
CloneRawJSON(value) into a local result variable (do not return immediately),
continue decoding remaining members (using readJSONObjectKey/dec.Decode as
today) to preserve source order, then after the loop return the last stored
result (or nil if none found); update references in Lookup to use a local
"result" (or "found") and ensure existing error handling paths remain unchanged.

Comment on lines +149 to +163
func selectorHintsFromJSONGJSON(body []byte) (model, provider string, parsed bool) {
parsed = core.VisitTopLevelJSONObjectFields(body, func(key string, raw []byte) bool {
switch key {
case "model":
return json.Unmarshal(raw, &model) == nil
case "provider":
return json.Unmarshal(raw, &provider) == nil
default:
return true
}
})
if !parsed {
return "", "", false
}
return model, provider, true
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

Do not treat selector type mismatches as “unparsed.”

Returning parsed=false here short-circuits ensureRequestModelResolution; internal/server/request_model_resolution.go Lines 93-94 bail out on !parsed. That means syntactically valid bodies like {"model":123} or {"model":"gpt-4o-mini","provider":123} now skip model validation entirely instead of entering the normal invalid-request path.

🛠️ Suggested fix
 func selectorHintsFromJSONGJSON(body []byte) (model, provider string, parsed bool) {
 	parsed = core.VisitTopLevelJSONObjectFields(body, func(key string, raw []byte) bool {
 		switch key {
 		case "model":
-			return json.Unmarshal(raw, &model) == nil
+			_ = json.Unmarshal(raw, &model)
+			return true
 		case "provider":
-			return json.Unmarshal(raw, &provider) == nil
+			_ = json.Unmarshal(raw, &provider)
+			return true
 		default:
 			return true
 		}
 	})
 	if !parsed {
 		return "", "", false
 	}
 	return model, provider, true
 }

Please add a middleware-level regression test for malformed selector field types as well; the new stdlib-parity helper does not cover this control-flow contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/server/model_validation.go` around lines 149 - 163, The function
selectorHintsFromJSONGJSON currently treats any json.Unmarshal failure as
parsed=false, causing syntactically valid bodies with wrong selector field types
to skip validation; change it so parsed reflects successful top-level object
traversal (what core.VisitTopLevelJSONObjectFields returns) and do not
short-circuit on unmarshal errors: in the VisitTopLevelJSONObjectFields callback
for "model" and "provider" attempt json.Unmarshal into model/provider but
ignore/uncheck errors (still return true to continue), so type mismatches don't
set parsed=false; also add a middleware-level regression test that sends
malformed selector fields (e.g., {"model":123} and {"provider":123}) and asserts
the request goes through normal invalid-request validation rather than being
skipped.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
internal/server/model_validation.go (1)

149-179: 🧹 Nitpick | 🔵 Trivial

Type mismatches return parsed=false, skipping early model validation.

As noted in a previous review, bodies like {"model":123} return parsed=false, which causes deriveExecutionPlan (line 99) to skip model resolution and return the plan without validation. This means type-invalid selectors proceed to downstream handlers instead of getting an early 400 response.

This appears to be an intentional tradeoff for the gjson performance optimization. The handler will still reject the request when it attempts to use the invalid model. Consider adding a brief comment documenting this behavior choice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/server/model_validation.go` around lines 149 - 179, Update
selectorHintsFromJSONGJSON to include a brief comment explaining that non-string
or type-mismatched selector fields (e.g., "model": 123) cause the function to
return parsed=false and therefore bypass early model validation in
deriveExecutionPlan; state this is an intentional tradeoff for gjson performance
(first-match behavior and fast path) and note that downstream handlers will
still reject invalid selectors when they are used. Reference the
selectorHintsFromJSONGJSON function and the interaction with deriveExecutionPlan
in the comment so future readers understand why parsed can be false for
type-mismatches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/core/json_fields.go`:
- Around line 27-31: Update the stale doc comment to match the renamed function:
replace the incorrect `CloneRawJSONMap` description with a concise comment for
`CloneUnknownJSONFields` that explains it returns a detached copy of an
UnknownJSONFields value (and mention it uses `CloneRawJSON` of the underlying
`raw` field); ensure the comment sits immediately above the
`CloneUnknownJSONFields` function and references `UnknownJSONFields` and
`CloneRawJSON` to make intent clear.

---

Duplicate comments:
In `@internal/server/model_validation.go`:
- Around line 149-179: Update selectorHintsFromJSONGJSON to include a brief
comment explaining that non-string or type-mismatched selector fields (e.g.,
"model": 123) cause the function to return parsed=false and therefore bypass
early model validation in deriveExecutionPlan; state this is an intentional
tradeoff for gjson performance (first-match behavior and fast path) and note
that downstream handlers will still reject invalid selectors when they are used.
Reference the selectorHintsFromJSONGJSON function and the interaction with
deriveExecutionPlan in the comment so future readers understand why parsed can
be false for type-mismatches.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: aee2c738-1c42-4fc3-8bac-251584582ea4

📥 Commits

Reviewing files that changed from the base of the PR and between 472ba8a and a5d171f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • go.mod
  • internal/core/batch_preparation_test.go
  • internal/core/json_fields.go
  • internal/core/json_fields_test.go
  • internal/core/semantic.go
  • internal/core/semantic_test.go
  • internal/responsecache/middleware_test.go
  • internal/responsecache/simple.go
  • internal/server/model_validation.go
  • internal/server/model_validation_test.go

@SantiagoDePolonia SantiagoDePolonia self-assigned this Mar 22, 2026
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