Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@thushan
Copy link
Owner

@thushan thushan commented Oct 21, 2025

adds back basic request logging for Anthropic for debugging, this isn't designed for production and was a quick port.

Summary by CodeRabbit

  • New Features

    • Optional inspector now logs session-organised request/response (including streaming) entries to disk for troubleshooting.
  • Configuration

    • New inspector settings to enable/disable logging, set output directory and customise session header; defaults and privacy/security cautions provided.
  • Documentation

    • Added user-facing notes and overview explaining inspector usage, output format, analysis examples and security guidance.
  • Tests

    • Expanded validation and file-behaviour tests covering sanitisation, permissions, path-traversal and logging scenarios.
  • Behaviour

    • Startup emits a cautionary warning when the inspector is enabled.

@thushan thushan self-assigned this Oct 21, 2025
@thushan thushan added enhancement New feature or request experimental Experimental labels Oct 21, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

Walkthrough

Adds a file-based Inspector and configuration for the Anthropic translator, validates inspector settings, wires inspector into request/response and streaming flows to optionally persist JSONL request/response entries organised by date and session, and adds docs and startup warnings.

Changes

Cohort / File(s) Summary
Configuration
config/config.yaml, internal/config/types.go, internal/config/config_test.go
Introduces InspectorConfig under Anthropic translator (fields: enabled, output_dir, session_header) and top-level Translators entry. Adds path/header validation and tests for defaults, dangerous paths and header validation.
Inspector Implementation
internal/adapter/inspector/simple.go, internal/adapter/inspector/simple_test.go
New file-based Simple inspector and Entry type with constructor, sanitisation, one-time security warning, LogRequest/LogResponse, GetSessionHeader, Enabled. Writes JSONL to {outputDir}/{YYYY-MM-DD}/{session}.jsonl with mutexed writes and permission handling; comprehensive unit tests added.
Anthropic Translator Integration
internal/adapter/translator/anthropic/translator.go, internal/adapter/translator/anthropic/request.go, internal/adapter/translator/anthropic/response.go, internal/adapter/translator/anthropic/streaming.go, internal/adapter/translator/anthropic/constants.go, internal/adapter/translator/anthropic/token_count.go
Adds inspector field to Translator and initializes it from config. Derives session ID from configured header / X-Request-ID with default fallback. Logs requests, transformed responses and completed streaming responses when enabled; preserves existing return/error flows. Refactors system prompt/content-block handling and token counting helpers.
Runtime warnings & docs
internal/app/services/http.go, docs/content/notes/anthropic-inspector.md, docs/content/notes/overview.md, docs/mkdocs.yml
Prints a startup caution if inspector enabled; adds Anthropic Inspector documentation, overview note and updates docs navigation.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Translator
    participant Inspector
    participant FS

    Client->>Translator: HTTP request
    Translator->>Translator: Extract/convert body, derive sessionID
    alt Inspector enabled
        Translator->>Inspector: LogRequest(sessionID, model, body)
        Inspector->>FS: Ensure {outputDir}/{YYYY-MM-DD}/ (0700)
        Inspector->>FS: Append JSONL to {session}.jsonl (0600)
        note right of Inspector #f9f0c1: emits one-time security warning
    end
    Translator->>Translator: Forward to backend / stream
    Translator->>Translator: Transform backend response
    alt Streaming finalised
        Translator->>Translator: Reconstruct streaming response
    end
    alt Inspector enabled
        Translator->>Inspector: LogResponse(sessionID, responseJSON)
        Inspector->>FS: Append JSONL entry
    end
    Translator->>Client: Return response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • feat: olla profile #32 — Modifies/extends inspector subsystem and integrates inspector components into translator flows; likely overlaps in inspector types and wiring.
  • tweaks & bugfixes #35 — Touches HTTP service startup flow where this PR adds startup warnings; may conflict or overlap with same function changes.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat: anthropic / message logger (development only)" accurately and specifically describes the main change in the changeset. The primary addition is indeed a message logging system for the Anthropic translator that logs HTTP-like request and response activity to disk, and the title correctly characterizes it as development-only. The changes span configuration, inspector implementation, integration into the Anthropic translator, testing, documentation, and service warnings—all supporting this core feature. The title is concise, clear, and avoids vague terminology, making it easy for reviewers scanning history to understand the primary objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/anthropic-enhanced

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.

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 3

♻️ Duplicate comments (1)
internal/adapter/translator/anthropic/request.go (1)

39-52: Extract duplicated session ID derivation logic.

This session ID derivation logic (lines 41-47) is identical to the code in response.go (lines 72-78). See the comment on response.go for the suggested refactor to extract this into a shared helper method.

🧹 Nitpick comments (4)
internal/adapter/inspector/simple_test.go (1)

14-140: Add test coverage for security and concurrency scenarios.

While the basic functionality is well-tested, consider adding tests for:

  1. Path traversal protection: Test that session IDs like ../../../malicious are sanitised
  2. Concurrent writes: Test that multiple goroutines writing to the same session file don't corrupt data or cause race conditions
  3. Invalid session IDs: Test session IDs with special characters, empty strings, very long strings

Example test structure:

t.Run("PathTraversalPrevention", func(t *testing.T) {
	sessionID := "../../etc/passwd"
	err := inspector.LogRequest(sessionID, "model", []byte(`{}`))
	// Verify file is created in safe location, not traversed path
})

t.Run("ConcurrentWrites", func(t *testing.T) {
	sessionID := "concurrent-test"
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			inspector.LogRequest(sessionID, "model", []byte(fmt.Sprintf(`{"id":%d}`, i)))
		}(i)
	}
	wg.Wait()
	// Verify all 10 entries exist and are valid JSON
})
internal/config/types.go (1)

182-187: Consider adding validation for InspectorConfig.

The InspectorConfig lacks validation. Consider adding a Validate() method to ensure:

  • OutputDir is not empty when Enabled is true
  • OutputDir doesn't contain path traversal sequences
  • SessionHeader is a valid HTTP header name when provided
// Validate validates the inspector configuration
func (c *InspectorConfig) Validate() error {
	if !c.Enabled {
		return nil
	}
	
	if c.OutputDir == "" {
		return fmt.Errorf("output_dir must be specified when inspector is enabled")
	}
	
	// Check for path traversal attempts
	if strings.Contains(c.OutputDir, "..") {
		return fmt.Errorf("output_dir must not contain '..' sequences")
	}
	
	return nil
}

Then call it from AnthropicTranslatorConfig.Validate():

func (c *AnthropicTranslatorConfig) Validate() error {
	// existing validation...
	
	if err := c.Inspector.Validate(); err != nil {
		return fmt.Errorf("inspector config invalid: %w", err)
	}
	
	return nil
}
internal/adapter/inspector/simple.go (2)

86-94: Tighten file permissions for sensitive data.

The inspector logs may contain sensitive user data (as noted in config.yaml warnings). Consider using more restrictive permissions:

  • Line 86: Directory permissions 0755 (world-readable) → 0700 (owner-only)
  • Line 94: File permissions 0644 (world-readable) → 0600 (owner-only)
-	if err := os.MkdirAll(dirPath, 0755); err != nil {
+	if err := os.MkdirAll(dirPath, 0700); err != nil {

-	f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)

28-28: Consider per-session mutexes for better concurrency.

The single global mutex (line 28) serialises all file writes, creating a bottleneck when multiple sessions are active concurrently. Consider using a sync.Map of per-session mutexes for better throughput.

This is optional since the PR is described as a quick debug port, but for production use:

type Simple struct {
	logger        logger.StyledLogger
	outputDir     string
	sessionHeader string
	sessionMutexes sync.Map // map[string]*sync.Mutex
	enabled       bool
}

func (s *Simple) getSessionMutex(sessionID string) *sync.Mutex {
	mu, _ := s.sessionMutexes.LoadOrStore(sessionID, &sync.Mutex{})
	return mu.(*sync.Mutex)
}

func (s *Simple) writeEntry(sessionID string, entry Entry) error {
	mu := s.getSessionMutex(sessionID)
	mu.Lock()
	defer mu.Unlock()
	// ... rest of implementation
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a93664d and 3603e34.

📒 Files selected for processing (7)
  • config/config.yaml (1 hunks)
  • internal/adapter/inspector/simple.go (1 hunks)
  • internal/adapter/inspector/simple_test.go (1 hunks)
  • internal/adapter/translator/anthropic/request.go (2 hunks)
  • internal/adapter/translator/anthropic/response.go (1 hunks)
  • internal/adapter/translator/anthropic/translator.go (3 hunks)
  • internal/config/types.go (2 hunks)
🔇 Additional comments (1)
internal/adapter/translator/anthropic/translator.go (1)

47-61: LGTM! Clean inspector integration.

The inspector is properly initialised from configuration and assigned to the translator struct. The integration is straightforward and doesn't alter existing control flow.

Comment on lines 70 to 84
// Log response to inspector if enabled
if t.inspector.Enabled() {
sessionID := original.Header.Get(t.inspector.GetSessionHeader())
if sessionID == "" {
sessionID = original.Header.Get("X-Request-ID")
if sessionID == "" {
sessionID = "default"
}
}
if respBytes, err := json.Marshal(anthropicResp); err == nil {
if err := t.inspector.LogResponse(sessionID, respBytes); err != nil {
t.logger.Warn("Failed to log response to inspector", "error", err)
}
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract duplicated session ID derivation logic.

The session ID derivation logic (lines 72-78) is duplicated in both response.go and request.go (lines 41-47). This violates the DRY principle and makes maintenance harder if the fallback logic needs to change.

Consider extracting this to a helper method on the Translator:

// getSessionID extracts session ID from headers with fallback chain
func (t *Translator) getSessionID(r *http.Request) string {
	sessionID := r.Header.Get(t.inspector.GetSessionHeader())
	if sessionID == "" {
		sessionID = r.Header.Get("X-Request-ID")
		if sessionID == "" {
			sessionID = "default"
		}
	}
	return sessionID
}

Then use it in both files:

-	sessionID := original.Header.Get(t.inspector.GetSessionHeader())
-	if sessionID == "" {
-		sessionID = original.Header.Get("X-Request-ID")
-		if sessionID == "" {
-			sessionID = "default"
-		}
-	}
+	sessionID := t.getSessionID(original)
🤖 Prompt for AI Agents
In internal/adapter/translator/anthropic/response.go around lines 70 to 84, the
session ID derivation (falling back from t.inspector.GetSessionHeader() to
"X-Request-ID" to "default") is duplicated; extract it to a helper method on
Translator, e.g. func (t *Translator) getSessionID(r *http.Request) string that
implements the same fallback chain, add that method to the translator file (or a
shared translator utility), and replace the inline logic here with a call to
t.getSessionID(original); also update
internal/adapter/translator/anthropic/request.go to call the same helper so both
locations use the single implementation.

Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/adapter/translator/anthropic/streaming.go (1)

306-336: Critical: tool call deltas can be emitted under the wrong content block (indexing bug).

content_block_delta for tool arguments uses state.currentIndex instead of the tool’s content block index. If arguments arrive before id/name or multiple tool calls interleave, deltas can be written under a text block or another tool block. Buffering-only until the block exists and using state.toolIndexToBlock is required.

Apply this fix (guard + correct indexing + flush buffered args after block start):

@@
-func (t *Translator) sendToolArgumentsDelta(args string, toolIndex int, state *StreamingState, w http.ResponseWriter, rc *http.ResponseController) error {
-  state.toolCallBuffers[toolIndex].WriteString(args)
+func (t *Translator) sendToolArgumentsDelta(args string, blockIndex int, toolIndex int, state *StreamingState, w http.ResponseWriter, rc *http.ResponseController) error {
+  // Always buffer for final Input, regardless of streaming emission
+  state.toolCallBuffers[toolIndex].WriteString(args)
@@
-  if err := t.writeEvent(w, "content_block_delta", map[string]interface{}{
+  if err := t.writeEvent(w, "content_block_delta", map[string]interface{}{
     "type":  "content_block_delta",
-    "index": state.currentIndex,
+    "index": blockIndex,
     "delta": map[string]interface{}{
       "type":         "input_json_delta",
       "partial_json": args,
     },
   }); err != nil {
     return err
   }
   return rc.Flush()
 }
@@
   for _, tc := range toolCalls {
     data, ok := extractToolCallData(tc)
     if !ok {
       continue
     }
@@
     // initialise buffer if first time seeing this tool index
     if _, exists := state.toolCallBuffers[data.toolIndex]; !exists {
       state.toolCallBuffers[data.toolIndex] = &strings.Builder{}
     }
@@
     // start block when we get id + name
     if data.id != "" && data.name != "" {
       if err := t.initializeToolBlock(data.id, data.name, data.toolIndex, state, w, rc); err != nil {
         return err
       }
+      // If arguments were buffered before the block started, emit them now
+      if buf := state.toolCallBuffers[data.toolIndex]; buf != nil && buf.Len() > 0 {
+        if blockIndex, found := state.toolIndexToBlock[data.toolIndex]; found {
+          if err := t.sendToolArgumentsDelta(buf.String(), blockIndex, data.toolIndex, state, w, rc); err != nil {
+            return err
+          }
+          buf.Reset()
+        }
+      }
     }
@@
     // buffer args chunks and send as partial_json
     if data.arguments != "" {
-      if err := t.sendToolArgumentsDelta(data.arguments, data.toolIndex, state, w, rc); err != nil {
-        return err
-      }
+      // Only emit streaming delta if the block exists; otherwise just buffer
+      if blockIndex, found := state.toolIndexToBlock[data.toolIndex]; found {
+        if err := t.sendToolArgumentsDelta(data.arguments, blockIndex, data.toolIndex, state, w, rc); err != nil {
+          return err
+        }
+      } else {
+        state.toolCallBuffers[data.toolIndex].WriteString(data.arguments)
+      }
     }
   }

This preserves correct streaming semantics and ensures final Input is still reconstructed from buffers.

Also applies to: 339-354, 362-386

♻️ Duplicate comments (1)
internal/adapter/translator/anthropic/response.go (1)

70-84: Session ID derivation logic is duplicated.

The session ID derivation logic (lines 72-78) is duplicated in both response.go and request.go (lines 41-47). This has been previously identified in review comments.

Consider extracting this to a helper method on the Translator as suggested in the previous review:

// getSessionID extracts session ID from headers with fallback chain
func (t *Translator) getSessionID(r *http.Request) string {
	sessionID := r.Header.Get(t.inspector.GetSessionHeader())
	if sessionID == "" {
		sessionID = r.Header.Get("X-Request-ID")
		if sessionID == "" {
			sessionID = defaultSessionID
		}
	}
	return sessionID
}

Then use it in both files:

-	sessionID := original.Header.Get(t.inspector.GetSessionHeader())
-	if sessionID == "" {
-		sessionID = original.Header.Get("X-Request-ID")
-		if sessionID == "" {
-			sessionID = defaultSessionID
-		}
-	}
+	sessionID := t.getSessionID(original)
🧹 Nitpick comments (5)
internal/adapter/translator/anthropic/streaming.go (1)

75-80: Consider increasing the scanner token limit for large tool arguments.

1 MiB per SSE line may still be tight for complex tool arguments. Suggest 8–16 MiB or switching to bufio.Reader with manual line splitting. This reduces risk of truncated chunks.

internal/adapter/inspector/simple_test.go (1)

103-123: Minor: avoid potential midnight rollover flake.

Tests derive “today” via time.Now() after writes. Extremely rare, but a date change at midnight could misalign directories. Optionally capture the date once before logging or inject a clock into the inspector for tests.

internal/adapter/inspector/simple.go (2)

94-101: Use filepath.Rel for robust containment check (avoid HasPrefix pitfalls).

HasPrefix on cleaned absolute paths can yield false positives for sibling directories (e.g. /var/log/abc vs /var/log/abcc). Use filepath.Rel to ensure the test path is inside the output directory.

-  // try and ensure the resolved path is within the output directory
-  // uses Clean to normalise paths and prevent ../ bypasses
-  if !strings.HasPrefix(filepath.Clean(absTestPath), filepath.Clean(absOutputDir)) {
-    return "", fmt.Errorf("session ID would escape output directory")
-  }
+  // Ensure absTestPath resides within absOutputDir
+  rel, err := filepath.Rel(absOutputDir, absTestPath)
+  if err != nil || strings.HasPrefix(rel, "..") || strings.HasPrefix(filepath.ToSlash(rel), "../") {
+    return "", fmt.Errorf("session ID would escape output directory")
+  }

168-173: Typo in comment (“owtner”).

s/owtner/owner/

-  // Use 0700 permissions - owtner only access (not world-readable)
+  // Use 0700 permissions - owner only access (not world-readable)
internal/config/types.go (1)

209-236: Confirm intent: deny-list blocks all of /var/, /usr/, etc.

validateOutputPath currently rejects any path under common system trees (including /var/log/...). That’s quite restrictive and may surprise operators who expect to point logs to /var/log/olla-inspector.

  • If the goal is “don’t overwrite system dirs”, consider allowing subdirectories under those roots (e.g. /var/log/olla-inspector) while still rejecting the root directories themselves.
  • Alternative: require a relative path (default ./inspector-logs) unless an explicit allow-list is configured.

Happy to propose a tuned rule set if you confirm desired behaviour.

Also applies to: 246-267

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3603e34 and 94e65e0.

📒 Files selected for processing (12)
  • docs/content/notes/anthropic-inspector.md (1 hunks)
  • docs/content/notes/overview.md (1 hunks)
  • docs/mkdocs.yml (2 hunks)
  • internal/adapter/inspector/simple.go (1 hunks)
  • internal/adapter/inspector/simple_test.go (1 hunks)
  • internal/adapter/translator/anthropic/constants.go (1 hunks)
  • internal/adapter/translator/anthropic/request.go (2 hunks)
  • internal/adapter/translator/anthropic/response.go (1 hunks)
  • internal/adapter/translator/anthropic/streaming.go (3 hunks)
  • internal/app/services/http.go (1 hunks)
  • internal/config/config_test.go (2 hunks)
  • internal/config/types.go (3 hunks)
🧰 Additional context used
🪛 LanguageTool
docs/content/notes/anthropic-inspector.md

[style] ~12-~12: Using many exclamation marks might seem excessive (in this case: 3 exclamation marks for a text that’s 1754 characters long)
Context: ...s and troubleshoot integration issues. !!! danger "DEVELOPMENT USE ONLY - DO NOT E...

(EN_EXCESSIVE_EXCLAMATION)

docs/content/notes/overview.md

[uncategorized] ~9-~9: Possible missing comma found.
Context: ...ot warrant a full dedicated area/section but are still useful to know about. * [Ant...

(AI_HYDRA_LEO_MISSING_COMMA)

🪛 markdownlint-cli2 (0.18.1)
docs/content/notes/anthropic-inspector.md

29-29: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


54-54: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


70-70: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


81-81: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


90-90: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


96-96: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


102-102: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


112-112: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)


119-119: Code block style
Expected: indented; Actual: fenced

(MD046, code-block-style)

🔇 Additional comments (7)
internal/app/services/http.go (1)

164-168: LGTM! Appropriate warning for experimental feature.

The warning mechanism correctly alerts developers when the inspector is enabled, with a clear reference to documentation. The non-exported method placement and timing (after server start) are appropriate.

docs/mkdocs.yml (1)

116-116: LGTM! Documentation navigation properly updated.

The new Notes sections are appropriately placed in both Home and Development sections, providing clear navigation to the inspector documentation.

Also applies to: 179-180

internal/adapter/translator/anthropic/request.go (1)

39-52: LGTM! Request logging correctly implemented.

The inspector logging properly captures requests with appropriate fallback logic for session identification. Logging failures are handled gracefully without disrupting the request flow.

Note: The session ID derivation logic (lines 41-47) is duplicated in response.go. Consider extracting it to a helper method as suggested in the previous review of response.go to maintain DRY principles.

internal/adapter/translator/anthropic/constants.go (1)

27-31: LGTM! Well-documented fallback constant.

The defaultSessionID constant is clearly documented and provides a sensible fallback value for session identification when headers are absent.

docs/content/notes/anthropic-inspector.md (1)

1-122: LGTM! Comprehensive and well-structured documentation.

The documentation thoroughly covers all aspects of the Anthropic Inspector, including:

  • Clear security warnings about production usage
  • Detailed configuration and usage examples
  • Practical analysis commands with jq and diff
  • Security considerations for log protection

The emphasis on the experimental nature and security implications is appropriate for a dev-only debugging tool.

internal/config/config_test.go (1)

5-5: LGTM! Comprehensive test coverage for inspector configuration.

The test suite thoroughly validates:

  • Valid and invalid configuration scenarios
  • Default value application
  • Dangerous path detection (both Unix and Windows)
  • HTTP header validation (spaces, colons, special characters)
  • Platform-specific tests with appropriate skipping on Windows

The test cases provide excellent coverage for security-critical validation logic.

Also applies to: 636-837

internal/adapter/translator/anthropic/streaming.go (1)

464-468: Inspector logging hook looks good.

Logging happens post-finalisation, uses header-derived session with safe fallback, and failures don’t affect the stream. LGTM.

Also applies to: 473-515


# Notes

This section covers quite notes and bytes of information about Olla's features (mostly experimental) that may not warrant a full dedicated area/section but are still useful to know about.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo: "quite notes" should be "quick notes".

Apply this diff:

-This section covers quite notes and bytes of information about Olla's features (mostly experimental) that may not warrant a full dedicated area/section but are still useful to know about.
+This section covers quick notes and bytes of information about Olla's features (mostly experimental) that may not warrant a full dedicated area/section, but are still useful to know about.
📝 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
This section covers quite notes and bytes of information about Olla's features (mostly experimental) that may not warrant a full dedicated area/section but are still useful to know about.
This section covers quick notes and bytes of information about Olla's features (mostly experimental) that may not warrant a full dedicated area/section, but are still useful to know about.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~9-~9: Possible missing comma found.
Context: ...ot warrant a full dedicated area/section but are still useful to know about. * [Ant...

(AI_HYDRA_LEO_MISSING_COMMA)

🤖 Prompt for AI Agents
In docs/content/notes/overview.md around line 9, the phrase "quite notes" is a
typo and should be "quick notes"; update the sentence to read "This section
covers quick notes and bytes of information about Olla's features (mostly
experimental) that may not warrant a full dedicated area/section but are still
useful to know about." preserving the rest of the sentence and punctuation.

@thushan thushan changed the title Feature/anthropic enhanced feat: anthropic / message logger (development only) Oct 22, 2025
update token_count and translator strategies
Repository owner deleted a comment from coderabbitai bot Oct 22, 2025
@thushan
Copy link
Owner Author

thushan commented Oct 22, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Oct 22, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/adapter/translator/anthropic/streaming.go (1)

340-354: Bug: wrong index used for tool argument deltas when multiple tool calls interleave.

content_block_delta currently uses state.currentIndex, which tracks the last-started block. If arguments for tool index i arrive after starting tool index j, deltas for i will reference j’s index, breaking client reconstruction.

Apply this minimal fix to select the correct block index per toolIndex:

 func (t *Translator) sendToolArgumentsDelta(args string, toolIndex int, state *StreamingState, w http.ResponseWriter, rc *http.ResponseController) error {
-  state.toolCallBuffers[toolIndex].WriteString(args)
+  state.toolCallBuffers[toolIndex].WriteString(args)
+
+  // Resolve the correct content block index for this tool index.
+  blockIndex := state.currentIndex
+  if mapped, ok := state.toolIndexToBlock[toolIndex]; ok {
+    blockIndex = mapped
+  }

   if err := t.writeEvent(w, "content_block_delta", map[string]interface{}{
     "type":  "content_block_delta",
-    "index": state.currentIndex,
+    "index": blockIndex,
     "delta": map[string]interface{}{
       "type":         "input_json_delta",
       "partial_json": args,
     },
   }); err != nil {
     return err
   }

Optional hardening: if the block for toolIndex isn’t started yet, buffer only and skip sending deltas until initialiseToolBlock runs, then flush buffered args.

🧹 Nitpick comments (10)
internal/adapter/translator/anthropic/translator.go (2)

47-54: Inspector initialisation is straightforward; consider lazy/no-op when disabled.

Current approach is fine. If you want to shave overhead in prod, you could construct a nil/no‑op inspector when cfg.Inspector.Enabled is false.


81-93: Header constant duplication.

You already import internal/core/constants elsewhere. Consider reusing a shared header constant (if one exists) instead of redefining "X-Request-ID" locally.

internal/adapter/translator/anthropic/request.go (2)

298-315: System prompt join semantics.

Joining multiple text blocks with no separator can smash words together. Consider joining with a single space (or preserving original separators) to avoid accidental concatenation.

- return strings.Join(textParts, "")
+ return strings.Join(textParts, " ")

39-46: Add redaction config to InspectorConfig for safer development logging.

Raw request bodies are logged directly without filtering in LogRequest(). Consider extending InspectorConfig with optional fields (e.g., Redact bool, UnsafeFullBody bool) to truncate/mask sensitive content by default, keeping full bodies behind an explicit flag. This prevents accidental exposure of tool arguments or message content if inspector is enabled outside development.

internal/adapter/translator/anthropic/token_count.go (6)

78-87: Remove duplicate text handling; delegate to the single counting source.

Use countContentBlockChars for all typed blocks to keep logic in one place and avoid drift.

-    if block.Type == contentTypeText {
-      totalChars += len(block.Text)
-    }
-    // for other block types, convert to typed block and count
-    if block.Type != "" && block.Type != contentTypeText {
-      totalChars += countContentBlockChars(&block)
-    }
+    if block.Type != "" {
+      totalChars += countContentBlockChars(&block)
+    }

130-139: Support string items within []interface{} for system blocks.

Robustly accept arrays that mix strings and blocks.

-  for _, block := range systemBlocks {
-    if normalised, nok := normaliseContentBlock(block); nok {
+  for _, block := range systemBlocks {
+    if s, isStr := block.(string); isStr {
+      if s != "" {
+        if err := fn(ContentBlock{Type: contentTypeText, Text: s}); err != nil {
+          return err
+        }
+      }
+      continue
+    }
+    if normalised, nok := normaliseContentBlock(block); nok {
       if err := fn(*normalised); err != nil {
         return err
       }
     }
   }

162-167: *Add []ContentBlock support for message content (parity with system/tool_result).

Covers pointer-shaped message content arrays.

   case []ContentBlock:
     // typed block arrays
     for i := range content {
       totalChars += countContentBlockChars(&content[i])
     }
+  case *[]ContentBlock:
+    if content != nil {
+      for i := range *content {
+        totalChars += countContentBlockChars(&(*content)[i])
+      }
+    }

172-213: normaliseContentBlock: accept plain strings as text blocks.

Improves resilience when untyped arrays contain strings.

 func normaliseContentBlock(block interface{}) (*ContentBlock, bool) {
-  // already a typed ContentBlock pointer, return as-is
+  // plain string -> text block
+  if s, ok := block.(string); ok {
+    return &ContentBlock{Type: contentTypeText, Text: s}, true
+  }
+  // already a typed ContentBlock pointer, return as-is
   if typedBlock, ok := block.(*ContentBlock); ok {
     return typedBlock, true
   }

Happy to add unit tests covering: string, []interface{} with mixed types, []ContentBlock, and pointer variants.


230-249: Count runes, not bytes, for text and tool_use name.

len(...) overcounts non‑ASCII text; utf8.RuneCountInString improves estimation with multilingual input.

 case contentTypeText:
-  // text blocks contribute their text length
-  return len(block.Text)
+  // text blocks contribute their rune length
+  return utf8.RuneCountInString(block.Text)

 case contentTypeToolUse:
-  // tool use blocks contribute name length plus serialised input
-  return len(block.Name) + countToolInput(block.Input)
+  // tool use blocks contribute name rune length plus serialised input
+  return utf8.RuneCountInString(block.Name) + countToolInput(block.Input)

Add import:

 import (
   "bytes"
   "context"
   "encoding/json"
   "fmt"
   "io"
   "net/http"
+  "unicode/utf8"

255-296: countToolResultContent: handle single map content and use rune counts for strings.

Covers map-shaped content and improves non‑ASCII accuracy.

 if contentStr, ok := content.(string); ok {
-  return len(contentStr)
+  return utf8.RuneCountInString(contentStr)
 }
 
+// single untyped block map
+if blockMap, ok := content.(map[string]interface{}); ok {
+  if normalised, ok := normaliseContentBlock(blockMap); ok {
+    return countContentBlockChars(normalised)
+  }
+}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94e65e0 and 46f1d3c.

📒 Files selected for processing (5)
  • internal/adapter/translator/anthropic/request.go (3 hunks)
  • internal/adapter/translator/anthropic/response.go (1 hunks)
  • internal/adapter/translator/anthropic/streaming.go (3 hunks)
  • internal/adapter/translator/anthropic/token_count.go (2 hunks)
  • internal/adapter/translator/anthropic/translator.go (4 hunks)
🔇 Additional comments (6)
internal/adapter/translator/anthropic/response.go (1)

70-78: LGTM: inspector logging with centralised session ID.

Use of t.getSessionID(original) removes the prior duplication and the logging is cleanly gated behind Enabled().

internal/adapter/translator/anthropic/translator.go (2)

95-101: LGTM: thin wrapper keeps system-block iteration cohesive.


75-93: Verification complete—centralised session ID logic confirmed.

Script output confirms no stray inlined header fallback logic remains elsewhere in production code. The getSessionID method in internal/adapter/translator/anthropic/translator.go (lines 75–93) is the sole source of truth for session ID derivation. All other occurrences of X-Request-ID in the codebase are appropriately scoped to constants, documentation, test headers, and comments. Code changes are sound and maintain readability.

internal/adapter/translator/anthropic/streaming.go (3)

65-67: LGTM: passing original request into finalisation enables session-aware logging.


464-469: LGTM: end-of-stream inspector logging is correctly gated.

Nice separation: reconstruction happens after final SSE events; failures don’t affect client output.


473-510: LGTM: reconstructed response mirrors non‑streaming shape.

Good reuse of mapFinishReasonToStopReason and session derivation via getSessionID(original).

@thushan thushan merged commit fcba73e into feature/anthropic Oct 22, 2025
1 check passed
@thushan thushan deleted the feature/anthropic-enhanced branch October 22, 2025 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request experimental Experimental

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant