Skip to content

Conversation

@ammar-agent
Copy link
Collaborator

@ammar-agent ammar-agent commented Feb 7, 2026

Summary

Extract three cohesive blocks (~363 lines) from streamMessage() into two new standalone modules, reducing aiService.ts from 1,708 to 1,367 lines (βˆ’341).

Background

Part of Phase 1b: decomposing AIService.streamMessage(). Prior extractions:

This PR targets the next two highest-impact blocks identified in the analysis β€” the agent resolution block (largest cohesive section, 164 lines) and the plan+system-prompt blocks (combined 160+ lines). Both were selected because they have minimal or zero this.* dependencies, allowing clean functional extraction.

Implementation

Commit 1: agentResolution.ts (290 lines)

New resolveAgentForStream(opts) handles:

  • Agent ID normalization & fallback to exec
  • Agent definition loading with error recovery
  • Disabled-agent enforcement (subagent workspaces error, top-level falls back to exec)
  • Inheritance chain resolution + plan-like detection
  • Task nesting depth enforcement
  • Tool policy composition (agent β†’ caller β†’ system workspace sandbox)
  • Sentinel tool name computation for agent transition detection

Service dependencies (emitError, initStateManager) injected via options object. Returns Result<AgentResolutionResult, SendMessageError> for clean error propagation.

Impact: aiService.ts 1,708 β†’ 1,574 (βˆ’134)

Commit 2: streamContextBuilder.ts (405 lines)

Two exported functions + one moved helper:

buildPlanInstructions(opts) β€” pure function, zero this.*:

  • Plan file reading with legacy migration
  • Plan-mode instruction injection
  • Plan-file hints (with Start Here deduplication)
  • Task nesting depth warnings
  • Planβ†’exec handoff transition content

buildStreamSystemContext(opts) β€” pure function, zero this.*:

  • Agent body resolution with inheritance + subagent prompt append
  • Subagent discovery for tool descriptions
  • Skill discovery for tool descriptions
  • System message construction + token counting

discoverAvailableSubagentsForToolContext() β€” moved from aiService.ts (re-exported for test compatibility).

Impact: aiService.ts 1,574 β†’ 1,367 (βˆ’207). 15 imports removed.

Cumulative Progress

Extraction Module aiService.ts LoC
Baseline (post-#2238) β€” 1,708
Agent resolution agentResolution.ts 1,574 (βˆ’134)
Plan + system prompt streamContextBuilder.ts 1,367 (βˆ’207)
Total βˆ’341

Validation

  • make typecheck: βœ… passes
  • make static-check: βœ… all checks pass
  • make test: βœ… 3,446 passed, 0 failed

Risks

Low risk β€” purely mechanical extractions with no behavioral changes. All code paths preserved 1:1. The Result return type in resolveAgentForStream correctly propagates the disabled-agent Err case. The plan/system-prompt functions are completely pure (no service dependencies).


Generated with mux β€’ Model: anthropic:claude-opus-4-6 β€’ Thinking: xhigh β€’ Cost: $234.49

Extract the agent resolution & tool policy computation block (~164 lines)
from streamMessage() into a standalone module: agentResolution.ts

The new resolveAgentForStream() function handles:
- Agent ID normalization & fallback to exec
- Agent definition loading with error recovery
- Disabled-agent enforcement (subagents error, top-level falls back)
- Inheritance chain resolution + plan-like detection
- Task nesting depth enforcement
- Tool policy composition (agent β†’ caller β†’ system workspace)
- Sentinel tool name computation for agent transition detection

Service dependencies (emitError, initStateManager) are injected via the
options object, keeping the function testable without class binding.

Impact: aiService.ts 1708 β†’ 1574 lines (βˆ’134)
Extract two more cohesive blocks from streamMessage() into a new module:
streamContextBuilder.ts

buildPlanInstructions() handles:
- Plan file reading with legacy migration
- Plan-mode instruction injection
- Plan-file hints in non-plan modes (with Start Here detection)
- Task nesting depth warnings
- Plan→exec handoff transition content determination

buildStreamSystemContext() handles:
- Agent body resolution with inheritance
- Subagent append_prompt resolution
- Available subagent discovery for tool descriptions
- Available skills discovery for tool descriptions
- System message construction
- System message token counting

Also moves discoverAvailableSubagentsForToolContext() helper to the new
module (re-exported from aiService.ts for test compatibility).

Both functions are purely functional β€” zero this.* dependencies.
15 imports removed from aiService.ts.

Impact: aiService.ts 1574 β†’ 1367 lines (βˆ’207)
@ammar-agent ammar-agent changed the title πŸ€– refactor: extract agent resolution from AIService.streamMessage() πŸ€– refactor: extract agent resolution + stream context from AIService.streamMessage() Feb 7, 2026
@ammario ammario merged commit b526dc2 into main Feb 7, 2026
23 checks passed
@ammario ammario deleted the refactor/extract-agent-resolution branch February 7, 2026 05:05
ammario pushed a commit that referenced this pull request Feb 7, 2026
)

## Summary

Continues the decomposition of the monolithic
`AIService.streamMessage()` method, extracting self-contained concerns
into focused modules and simplifying the orchestration code that
remains.

## Background

`AIService` was 1,367 lines with `streamMessage()` accounting for ~900
of those. Previous PRs (#2242) extracted agent resolution and stream
context building. This PR continues with simulation, tool assembly,
model resolution, and structural cleanup.

## Implementation

### Commit 1: Extract simulation + tool assembly
- **`streamSimulation.ts`** (185 lines): `simulateContextLimitError` and
`simulateToolPolicyNoop` β€” synthetic stream event sequences for OpenAI
SDK testing features (`forceContextLimitError`,
`simulateToolPolicyNoop`).
- **`toolAssembly.ts`** (235 lines): `applyToolPolicyAndExperiments`
(tool policy + PTC lazy-loading) and `captureMcpToolTelemetry` (MCP
stats + telemetry emission). Moves the PTC singleton (`getPTCModules`)
entirely out of `aiService.ts`.

### Commit 2: Model resolution + MCP telemetry + DRY cleanup
- **`providerModelFactory.ts`**: Added `resolveAndCreateModel()`
combining xAI Grok variant mapping, gateway resolution, and model
creation into a single call.
- **Event forwarding**: Replaced 9 individual one-liner event forwarding
calls with a single `for-of` loop.
- **Abort handling**: Consolidated two identical 7-line blocks into a
`deleteAbortedPlaceholder` helper.
- **JSDoc**: Trimmed 18-line `@param` block, removed backward-compat
re-export.

### Commit 3: Options bag + safeClone + inline cleanup
- **`StreamMessageOptions`**: Converted `streamMessage()` from **19
positional parameters** to a typed options bag. Call sites are now
self-documenting with named fields instead of `undefined` placeholders.
- **`safeClone<T>()`**: Extracted DRY utility for the duplicated
`structuredClone`-with-JSON-fallback pattern (used in 2 debug snapshot
capture sites).
- **Inline cleanups**: Removed `providerForMessages` alias, no-op OpenAI
debug block, unused `workspace` variable, stale comments. Moved
`assistantMessageId` creation before PTC callback for clearer closure
capture.

## Metrics

| Metric | Before | After | Change |
|--------|--------|-------|--------|
| `aiService.ts` lines | 1,367 | 1,111 | βˆ’256 (βˆ’19%) |
| New module lines | 0 | 420 | +420 |
| Net LoC change | β€” | β€” | +164 (complexity moved, not duplicated) |

## Validation

- All 3,446 tests pass (`make test`)
- All static checks pass (`make static-check`)
- Purely mechanical extraction β€” no behavioral changes

---

_Generated with `mux` β€’ Model: `anthropic:claude-opus-4-6` β€’ Thinking:
`xhigh` β€’ Cost: `$251.22`_

<!-- mux-attribution: model=anthropic:claude-opus-4-6 thinking=xhigh
costs=251.22 -->
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