Skip to content

Commit 8977c28

Browse files
committed
refactor(internal): extract doStream helpers (Commit 2.9)
1 parent ff82df1 commit 8977c28

File tree

5 files changed

+416
-276
lines changed

5 files changed

+416
-276
lines changed

REFACTORING_PLAN.md

Lines changed: 138 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ This document outlines a prioritized refactoring plan for the 51 issues identifi
3939
| 2.6 | Simplify use-activity-query.ts | ✅ Complete | Codebuff |
4040
| 2.7 | Consolidate XML parsing | ✅ Complete | Codebuff |
4141
| 2.8 | Consolidate analytics | ✅ Complete | Codebuff |
42-
| 2.9 | Refactor doStream | ⬜ Not Started | - |
43-
| 2.10 | DRY up OpenRouter stream handling | ⬜ Not Started | - |
44-
| 2.11 | Consolidate image handling | Not Started | - |
42+
| 2.9 | Refactor doStream | ✅ Complete | Codebuff |
43+
| 2.10 | DRY up OpenRouter stream handling | ⏭️ Skipped | - |
44+
| 2.11 | Consolidate image handling | Not Needed | - |
4545
| 2.12 | Refactor suggestion-engine | ⬜ Not Started | - |
4646
| 2.13 | Fix browser actions + string utils | ⬜ Not Started | - |
4747
| 2.14 | Refactor agent-builder.ts | ⬜ Not Started | - |
@@ -780,59 +780,160 @@ common/src/analytics/
780780

781781
---
782782

783-
### Commit 2.9: Refactor `doStream` in OpenAI Compatible Model
784-
**Files:** `packages/internal/src/ai-sdk/openai-compatible-chat-language-model.ts`
783+
### Commit 2.9: Refactor `doStream` in OpenAI Compatible Model ✅ COMPLETE
784+
**Files:** `packages/internal/src/openai-compatible/chat/openai-compatible-chat-language-model.ts`
785785
**Est. Time:** 3-4 hours
786-
**Est. LOC Changed:** ~350-400
786+
**Actual Time:** ~2 hours
787+
**Est. LOC Changed:** ~350-400
788+
**Actual LOC Changed:** ~290 lines (3 new files) + ~180 lines reduced from main file
787789

788-
| Task | Description |
789-
|------|-------------|
790-
| Extract `StreamParser` class | Parsing logic |
791-
| Extract `ChunkProcessor` | Chunk handling |
792-
| Extract `StreamErrorHandler` | Error handling |
793-
| Simplify `doStream` | Orchestration only |
790+
| Task | Description | Status |
791+
|------|-------------|--------|
792+
| Create `stream-usage-tracker.ts` | Usage accumulation with factory pattern ||
793+
| Create `stream-content-tracker.ts` | Text/reasoning delta handling ||
794+
| Create `stream-tool-call-handler.ts` | Tool call state management ||
795+
| Simplify `doStream` | Orchestration with extracted helpers ||
796+
| Multi-agent review | Codex CLI + Codebuff reviewed, fixes applied ||
797+
798+
**New Files Created:**
799+
- `packages/internal/src/openai-compatible/chat/stream-usage-tracker.ts` (~60 lines):
800+
- `createStreamUsageTracker()` - factory for usage accumulation
801+
- `update()` - process chunk usage data
802+
- `getUsage()` - get LanguageModelV2Usage
803+
- `getCompletionTokensDetails()` - get detailed token breakdown
804+
- `packages/internal/src/openai-compatible/chat/stream-content-tracker.ts` (~45 lines):
805+
- `createStreamContentTracker()` - factory for content state
806+
- `processReasoningDelta()` - emit reasoning-start/delta events
807+
- `processTextDelta()` - emit text-start/delta events
808+
- `flush()` - emit reasoning-end/text-end events
809+
- Constants: `REASONING_ID`, `TEXT_ID`
810+
- `packages/internal/src/openai-compatible/chat/stream-tool-call-handler.ts` (~120 lines):
811+
- `createStreamToolCallHandler()` - factory for tool call state
812+
- `processToolCallDelta()` - handle streaming tool call chunks
813+
- `flushUnfinishedToolCalls()` - emit incomplete tool calls at end
814+
- `emitToolCallCompletion()` - extracted helper for DRY completion logic
815+
816+
**doStream Reduction:**
817+
- `openai-compatible-chat-language-model.ts`: ~300 → ~120 lines in doStream (-60%)
818+
- TransformStream now delegates to helpers instead of inline logic
819+
820+
**Multi-Agent Review (Codex CLI, Codebuff):**
821+
822+
| Finding | Agent | Severity | Resolution |
823+
|---------|-------|----------|------------|
824+
| **Magic string IDs** | Codebuff | Info | Added `REASONING_ID`, `TEXT_ID` constants |
825+
| **Unused getters** | Codebuff | Info | Removed `isReasoningActive()`, `isTextActive()`, `getToolCalls()` |
826+
| **Duplicated completion logic** | Codebuff | Warning | Extracted `emitToolCallCompletion()` helper |
827+
| **Non-null assertion** | Codex | Info | Removed unnecessary `!` assertion |
828+
| **Redundant nullish coalescing** | Both | Suggestion | Simplified `?? undefined` to just return value |
829+
| **Unused type exports** | Codebuff | Info | Made `ToolCallState` internal (not exported) |
830+
831+
**Multi-Agent Review (All 4 CLI Agents: Codex, Codebuff, Claude Code, Gemini):**
832+
833+
| Finding | Agents | Severity | Resolution |
834+
|---------|--------|----------|------------|
835+
| **Empty delta emission** | Codebuff, Claude | Warning | Fixed: Only emit delta if arguments truthy |
836+
| **Invalid JSON in flush** | Codex, Codebuff | Warning | Fixed: Use `isParsableJson` with `'{}'` fallback |
837+
| **Dead generateId() fallback** | Codebuff, Claude | Info | Fixed: Removed dead `?? generateId()` |
838+
| **Magic string IDs** | Codex, Claude, Gemini | Suggestion | Fixed: Added `REASONING_ID`, `TEXT_ID` constants |
839+
| **Side-effect mutation** | Codebuff, Claude, Gemini | Suggestion | Accepted: Keep for simplicity within limited scope |
840+
| **Hardcoded IDs** | Codex, Claude, Gemini | Suggestion | Documented: Single block assumption |
841+
| **No unit tests** | Codex | Warning | Deferred: Integration tests sufficient for now |
842+
| **Premature tool finalization** | Gemini | Critical | Rejected: Matches original behavior, intentional for providers sending complete tool calls |
843+
844+
**Architecture Decisions Validated (All 4 agents agree):**
845+
- ✅ Factory pattern is correct (vs classes or standalone functions)
846+
- ✅ Event arrays are cleaner than passing controller (testability)
847+
- ✅ Helpers are ready for OpenRouter reuse in Commit 2.10
848+
849+
**Review Fixes Applied:**
850+
| Fix | Description |
851+
|-----|-------------|
852+
| Add constants | `REASONING_ID = 'reasoning-0'`, `TEXT_ID = 'txt-0'` |
853+
| Remove unused getters | Deleted `isReasoningActive()`, `isTextActive()`, `getToolCalls()` |
854+
| Extract completion helper | `emitToolCallCompletion()` reduces duplication by ~30 lines |
855+
| Simplify usage tracker | Flattened state to simple variables instead of nested object |
856+
| Remove redundant code | Cleaned up `?? undefined` patterns |
857+
| **Empty delta fix** | Moved delta emission inside `if (arguments != null)` block |
858+
| **Invalid JSON fix** | Added `isParsableJson` check with `'{}'` fallback in flush |
859+
| **Dead fallback fix** | Removed `?? generateId()` since id is validated earlier |
860+
861+
**Test Results:**
862+
- 191 internal package tests pass
863+
- All 13 package typechecks pass
864+
- Streaming behavior unchanged
794865

795866
**Dependencies:** None
796867
**Risk:** Medium - Core streaming
797-
**Feature Flag:** `REFACTOR_STREAM=true`
798-
**Rollback:** Revert and flag off
868+
**Rollback:** Revert single commit
869+
**Commit:** `559857bc2`
799870

800871
---
801872

802-
### Commit 2.10: DRY Up OpenRouter Stream Handling
803-
**Files:** `packages/internal/src/ai-sdk/openrouter-ai-sdk/chat/index.ts`
873+
### Commit 2.10: DRY Up OpenRouter Stream Handling ⏭️ SKIPPED
874+
**Files:** `packages/internal/src/openrouter-ai-sdk/chat/index.ts`
804875
**Est. Time:** 2-3 hours
805876
**Est. LOC Changed:** ~300-400
806877

807-
| Task | Description |
808-
|------|-------------|
809-
| Create shared `stream-utils.ts` | Common streaming utilities |
810-
| Extract shared chunk processing | Reuse across providers |
811-
| Update OpenRouter implementation | Use shared code |
812-
| Update OpenAI compatible | Use shared code |
878+
> **Decision:** Skipped after multi-agent review of Commit 2.9. All 4 CLI agents reviewed and Codebuff's recommendation was adopted.
879+
880+
**Reason for Skipping:**
881+
OpenRouter streaming has materially different requirements from OpenAI-compatible streaming:
882+
- `reasoning_details` array with types (Text, Summary, Encrypted) vs simple `reasoning_content`
883+
- `annotations` / web search citations support
884+
- `openrouterUsage` with `cost`, `cost_details`, `upstreamInferenceCost`
885+
- Different tool call tracking (`inputStarted` flag vs `hasFinished`)
886+
- Provider routing info
887+
888+
Premature abstraction would add complexity without clear benefit. The helpers are small (45-120 lines each) and the "duplication" cost is low compared to the complexity cost of a forced abstraction.
889+
890+
**Revisit When:** We find ourselves fixing the same streaming bug in both implementations, or the APIs converge.
813891

814892
**Dependencies:** Commit 2.9
815-
**Risk:** Medium
816-
**Rollback:** Revert single commit
893+
**Risk:** N/A - Skipped
894+
**Rollback:** N/A
817895

818896
---
819897

820-
### Commit 2.11: Consolidate Image Handling
898+
### Commit 2.11: Consolidate Image Handling ✅ NOT NEEDED
821899
**Files:** Clipboard/image related files in CLI
822-
**Est. Time:** 2-3 hours
823-
**Est. LOC Changed:** ~300-400
900+
**Est. Time:** 0 hours (skipped)
901+
**Est. LOC Changed:** 0
902+
903+
> **Decision:** Skipped after codebase analysis. The image handling architecture is already well-organized.
904+
905+
**Reason for Skipping:**
906+
The refactoring plan's description was based on outdated analysis. The current architecture is clean:
907+
908+
| File | Purpose | Lines |
909+
|------|---------|-------|
910+
| `common/src/constants/images.ts` | Shared constants, MIME types, size limits | ~50 |
911+
| `cli/src/utils/image-handler.ts` | Core processing, compression, validation | ~290 |
912+
| `cli/src/utils/clipboard-image.ts` | Cross-platform clipboard operations | ~370 |
913+
| `cli/src/utils/image-processor.ts` | SDK message content integration | ~70 |
914+
| `cli/src/utils/pending-attachments.ts` | State management for pending images | ~190 |
915+
| `cli/src/utils/image-thumbnail.ts` | Pixel extraction for thumbnails | ~75 |
916+
| `cli/src/utils/terminal-images.ts` | iTerm2/Kitty protocol rendering | ~190 |
917+
| `cli/src/utils/image-display.ts` | Terminal dimension calculations | ~60 |
918+
919+
**Clean Dependency Chain:**
920+
```
921+
common/constants/images.ts (constants)
922+
923+
cli/utils/image-handler.ts (core processing)
924+
925+
├── cli/utils/clipboard-image.ts (clipboard operations)
926+
├── cli/utils/image-processor.ts (SDK integration)
927+
└── cli/utils/pending-attachments.ts (state management)
928+
```
824929

825-
| Task | Description |
826-
|------|-------------|
827-
| Create `utils/image-handler.ts` | Unified image handling |
828-
| Extract `processImageFromClipboard()` | Clipboard images |
829-
| Extract `processImageFromFile()` | File images |
830-
| Extract `validateImage()` | Image validation |
831-
| Update all usages | Replace duplicates |
930+
No duplication found. Architecture follows single responsibility principle.
832931

833-
**Dependencies:** None (can run in parallel with 2.10)
834-
**Risk:** Low
835-
**Rollback:** Revert single commit
932+
**Revisit When:** If new image handling code introduces duplication.
933+
934+
**Dependencies:** N/A
935+
**Risk:** N/A
936+
**Rollback:** N/A
836937

837938
---
838939

0 commit comments

Comments
 (0)