Extract AnthropicClient from AnthropicAgent#233
Merged
Conversation
First step of the SDK refactor series tracked in #232. Splitting the refactor into small, independently reviewable PRs so each step can land (or be reverted) on its own. Before this change, AnthropicAgent's constructor did four things: it resolved auth, wired up the token-refreshing HTTP client, wrapped that client in an AnthropicMessageStreamer, and held the Conversation. Only the last one is actually an agent concern. Auth, transport, and stream protocol are a transport concern. This extracts the first three into a new private AnthropicClient that extends IMessageStreamer directly. The previous AnthropicMessageStreamer wrapper class was a one-method passthrough, so it is removed rather than kept around as a second layer. AnthropicAgent now holds an IMessageStreamer (the abstract type), not the concrete client. Composition over inheritance. The constructor builds an AnthropicClient internally and stores it as the streamer. This preserves the FakeMessageStreamer contract used by AgentRun.spec.ts without touching any test imports. IMessageStreamer stays in MessageStreamer.ts for this PR. Moving it into interfaces.ts is a separate filename churn I did not want mixed into this commit. No public API or runtime behaviour changes. The agent's external shape is identical; all construction paths go through the same code. Verification: - pnpm --filter @shellicar/claude-sdk type-check: clean - pnpm test: 981/981 passing (core 5, sdk 78, sdk-tools 233, sdk-cli 426, cli 239) - pnpm build: 5/5 packages - pnpm biome check --diagnostic-level=error: 319 files, no fixes needed - changelog entry validated Step 1 of 6 for #232.
bananabot9000
approved these changes
Apr 9, 2026
Collaborator
bananabot9000
left a comment
There was a problem hiding this comment.
Clean extraction. Composition over inheritance preserved -- agent depends on IMessageStreamer, not the concrete AnthropicClient. Test contract untouched. Scope discipline excellent (step 1 of 6, nothing beyond auth/transport/streaming moved). Session log is a solid resumable artifact for future steps.
LGTM 🍌
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First step of the SDK refactor series in #232. Splitting the refactor into small PRs so each one can land or be reverted on its own.
Why
AnthropicAgent's constructor was doing four things: resolving auth, wiring the token-refreshing HTTP client, wrapping that client in anAnthropicMessageStreamer, and holding theConversation. Only the last is an agent concern. Auth, transport, and the stream protocol are transport concerns, and they belong in a dedicated client.What
packages/claude-sdk/src/private/AnthropicClient.tsthat extendsIMessageStreamerdirectly and owns auth + token refresh + HTTP transport.AnthropicAgentnow composes anIMessageStreamer(abstract type, not the concrete client). The constructor builds anAnthropicClientinternally and stores it as the streamer.AnthropicMessageStreamerclass was a one-method passthrough, so it is removed rather than kept as a second layer.IMessageStreamer(the abstract class) remains unchanged.IMessageStreamerstays inMessageStreamer.tsfor this PR. Moving it intointerfaces.tsis a separate filename churn I did not want mixed in here.Decisions
IMessageStreamer, not anAnthropicClient. This keeps theFakeMessageStreamercontract used byAgentRun.spec.tsworking without touching any test imports.Verification
pnpm --filter @shellicar/claude-sdk type-check: cleanpnpm test: 981/981 passing (core 5, sdk 78, sdk-tools 233, sdk-cli 426, cli 239)pnpm build: 5/5 packagespnpm biome check --diagnostic-level=error: 319 files, no fixes neededStep 1 of 6 for #232.