@prmichaelsen/acp-visualizer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +68 -0
  2. package/agent/commands/acp.clarification-address.md +417 -0
  3. package/agent/commands/acp.clarification-capture.md +386 -0
  4. package/agent/commands/acp.clarification-create.md +437 -0
  5. package/agent/commands/acp.clarifications-research.md +326 -0
  6. package/agent/commands/acp.command-create.md +432 -0
  7. package/agent/commands/acp.design-create.md +286 -0
  8. package/agent/commands/acp.design-reference.md +355 -0
  9. package/agent/commands/acp.handoff.md +270 -0
  10. package/agent/commands/acp.index.md +423 -0
  11. package/agent/commands/acp.init.md +546 -0
  12. package/agent/commands/acp.package-create.md +895 -0
  13. package/agent/commands/acp.package-info.md +212 -0
  14. package/agent/commands/acp.package-install.md +539 -0
  15. package/agent/commands/acp.package-list.md +280 -0
  16. package/agent/commands/acp.package-publish.md +541 -0
  17. package/agent/commands/acp.package-remove.md +293 -0
  18. package/agent/commands/acp.package-search.md +307 -0
  19. package/agent/commands/acp.package-update.md +361 -0
  20. package/agent/commands/acp.package-validate.md +540 -0
  21. package/agent/commands/acp.pattern-create.md +386 -0
  22. package/agent/commands/acp.plan.md +587 -0
  23. package/agent/commands/acp.proceed.md +882 -0
  24. package/agent/commands/acp.project-create.md +675 -0
  25. package/agent/commands/acp.project-info.md +312 -0
  26. package/agent/commands/acp.project-list.md +226 -0
  27. package/agent/commands/acp.project-remove.md +379 -0
  28. package/agent/commands/acp.project-set.md +227 -0
  29. package/agent/commands/acp.project-update.md +307 -0
  30. package/agent/commands/acp.projects-restore.md +228 -0
  31. package/agent/commands/acp.projects-sync.md +347 -0
  32. package/agent/commands/acp.report.md +407 -0
  33. package/agent/commands/acp.resume.md +239 -0
  34. package/agent/commands/acp.sessions.md +301 -0
  35. package/agent/commands/acp.status.md +293 -0
  36. package/agent/commands/acp.sync.md +364 -0
  37. package/agent/commands/acp.task-create.md +500 -0
  38. package/agent/commands/acp.update.md +302 -0
  39. package/agent/commands/acp.validate.md +466 -0
  40. package/agent/commands/acp.version-check-for-updates.md +276 -0
  41. package/agent/commands/acp.version-check.md +191 -0
  42. package/agent/commands/acp.version-update.md +289 -0
  43. package/agent/commands/command.template.md +339 -0
  44. package/agent/commands/git.commit.md +526 -0
  45. package/agent/commands/git.init.md +514 -0
  46. package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
  47. package/agent/commands/tanstack-cloudflare.tail.md +275 -0
  48. package/agent/design/.gitkeep +0 -0
  49. package/agent/design/design.template.md +154 -0
  50. package/agent/design/local.dashboard-layout-routing.md +288 -0
  51. package/agent/design/local.data-model-yaml-parsing.md +310 -0
  52. package/agent/design/local.search-filtering.md +331 -0
  53. package/agent/design/local.server-api-auto-refresh.md +235 -0
  54. package/agent/design/local.table-tree-views.md +299 -0
  55. package/agent/design/local.visualizer-requirements.md +349 -0
  56. package/agent/design/requirements.template.md +387 -0
  57. package/agent/index/.gitkeep +0 -0
  58. package/agent/index/acp.core.yaml +137 -0
  59. package/agent/index/local.main.template.yaml +37 -0
  60. package/agent/manifest.template.yaml +13 -0
  61. package/agent/manifest.yaml +302 -0
  62. package/agent/milestones/.gitkeep +0 -0
  63. package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
  64. package/agent/milestones/milestone-1-{title}.template.md +206 -0
  65. package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
  66. package/agent/package.template.yaml +86 -0
  67. package/agent/patterns/.gitkeep +0 -0
  68. package/agent/patterns/bootstrap.template.md +1237 -0
  69. package/agent/patterns/pattern.template.md +382 -0
  70. package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
  71. package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
  72. package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
  73. package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
  74. package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
  75. package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
  76. package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
  77. package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
  78. package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
  79. package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
  80. package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
  81. package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
  82. package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
  83. package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
  84. package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
  85. package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
  86. package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
  87. package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
  88. package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
  89. package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
  90. package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
  91. package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
  92. package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
  93. package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
  94. package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
  95. package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
  96. package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
  97. package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
  98. package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
  99. package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
  100. package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
  101. package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
  102. package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
  103. package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
  104. package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
  105. package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
  106. package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
  107. package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
  108. package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
  109. package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
  110. package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
  111. package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
  112. package/agent/progress.template.yaml +161 -0
  113. package/agent/progress.yaml +145 -0
  114. package/agent/schemas/package.schema.yaml +276 -0
  115. package/agent/scripts/acp.common.sh +1781 -0
  116. package/agent/scripts/acp.install.sh +333 -0
  117. package/agent/scripts/acp.package-create.sh +924 -0
  118. package/agent/scripts/acp.package-info.sh +288 -0
  119. package/agent/scripts/acp.package-install.sh +893 -0
  120. package/agent/scripts/acp.package-list.sh +311 -0
  121. package/agent/scripts/acp.package-publish.sh +420 -0
  122. package/agent/scripts/acp.package-remove.sh +348 -0
  123. package/agent/scripts/acp.package-search.sh +156 -0
  124. package/agent/scripts/acp.package-update.sh +517 -0
  125. package/agent/scripts/acp.package-validate.sh +1018 -0
  126. package/agent/scripts/acp.uninstall.sh +85 -0
  127. package/agent/scripts/acp.version-check-for-updates.sh +98 -0
  128. package/agent/scripts/acp.version-check.sh +47 -0
  129. package/agent/scripts/acp.version-update.sh +176 -0
  130. package/agent/scripts/acp.yaml-parser.sh +985 -0
  131. package/agent/scripts/acp.yaml-validate.sh +205 -0
  132. package/agent/tasks/.gitkeep +0 -0
  133. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
  134. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
  135. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
  136. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
  137. package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
  138. package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
  139. package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
  140. package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
  141. package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
  142. package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
  143. package/agent/tasks/task-1-{title}.template.md +244 -0
  144. package/bin/visualize.mjs +84 -0
  145. package/package.json +48 -0
  146. package/src/components/ExtraFieldsBadge.tsx +15 -0
  147. package/src/components/FilterBar.tsx +33 -0
  148. package/src/components/Header.tsx +23 -0
  149. package/src/components/MilestoneTable.tsx +167 -0
  150. package/src/components/MilestoneTree.tsx +84 -0
  151. package/src/components/ProgressBar.tsx +20 -0
  152. package/src/components/SearchInput.tsx +22 -0
  153. package/src/components/Sidebar.tsx +54 -0
  154. package/src/components/StatusBadge.tsx +23 -0
  155. package/src/components/StatusDot.tsx +12 -0
  156. package/src/components/TaskList.tsx +36 -0
  157. package/src/components/ViewToggle.tsx +31 -0
  158. package/src/lib/config.ts +8 -0
  159. package/src/lib/file-watcher.ts +43 -0
  160. package/src/lib/search.ts +48 -0
  161. package/src/lib/types.ts +73 -0
  162. package/src/lib/useAutoRefresh.ts +31 -0
  163. package/src/lib/useCollapse.ts +31 -0
  164. package/src/lib/useFilteredData.ts +55 -0
  165. package/src/lib/yaml-loader-real.spec.ts +47 -0
  166. package/src/lib/yaml-loader.spec.ts +201 -0
  167. package/src/lib/yaml-loader.ts +265 -0
  168. package/src/routeTree.gen.ts +140 -0
  169. package/src/router.tsx +10 -0
  170. package/src/routes/__root.tsx +75 -0
  171. package/src/routes/api/watch.ts +29 -0
  172. package/src/routes/index.tsx +115 -0
  173. package/src/routes/milestones.tsx +50 -0
  174. package/src/routes/search.tsx +84 -0
  175. package/src/routes/tasks.tsx +63 -0
  176. package/src/services/progress-database.service.ts +46 -0
  177. package/src/styles.css +25 -0
  178. package/tsconfig.json +24 -0
  179. package/vite.config.ts +16 -0
  180. package/vitest.config.ts +27 -0
@@ -0,0 +1,401 @@
1
+ # Provider Adapter Pattern
2
+
3
+ **Category**: Architecture
4
+ **Applicable To**: TanStack Start + Cloudflare Workers applications with pluggable backends
5
+ **Status**: Stable
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ The Provider Adapter pattern defines TypeScript interfaces for external dependencies (AI providers, storage backends, messaging protocols, vision APIs) and injects concrete implementations at construction time. This enables the core business logic to remain portable, testable, and provider-agnostic while allowing implementations to be swapped without code changes.
12
+
13
+ By defining contracts as interfaces and injecting implementations, you can:
14
+ - Swap AI providers (Bedrock → OpenAI) without changing chat logic
15
+ - Swap storage (Firebase → Postgres) without changing services
16
+ - Mock all dependencies for unit testing
17
+ - Extract core logic into reusable packages
18
+
19
+ ---
20
+
21
+ ## When to Use This Pattern
22
+
23
+ ✅ **Use this pattern when:**
24
+ - Building features that depend on external services (AI, storage, messaging)
25
+ - Want to swap implementations without changing business logic
26
+ - Need testable code with mockable dependencies
27
+ - Building portable libraries that could be extracted into packages
28
+ - Working with multiple providers for the same capability
29
+
30
+ ❌ **Don't use this pattern when:**
31
+ - Only one implementation will ever exist (over-engineering)
32
+ - The dependency is trivial and unlikely to change
33
+ - Performance is critical and interface indirection adds measurable overhead
34
+
35
+ ---
36
+
37
+ ## Core Principles
38
+
39
+ 1. **Interface First**: Define the interface before writing any implementation
40
+ 2. **Single Responsibility**: Each interface covers one capability (AI, storage, MCP, vision)
41
+ 3. **Constructor Injection**: Implementations injected at construction time, not imported directly
42
+ 4. **Param Objects**: Methods accept typed parameter objects, not positional arguments
43
+ 5. **Callback-Based Streaming**: Use `onMessage` callbacks for streaming data rather than returning streams
44
+ 6. **Optional Methods**: Use `method?` syntax for capabilities not all providers support
45
+
46
+ ---
47
+
48
+ ## Implementation
49
+
50
+ ### Structure
51
+
52
+ ```
53
+ src/
54
+ ├── lib/
55
+ │ ├── chat/
56
+ │ │ ├── chat-engine.ts # Core logic — depends on interfaces only
57
+ │ │ ├── types.ts # Shared types
58
+ │ │ └── interfaces/
59
+ │ │ ├── ai-provider.ts # IAIProvider interface
60
+ │ │ ├── storage-provider.ts # IStorageProvider interface
61
+ │ │ ├── mcp-provider.ts # IMCPProvider interface
62
+ │ │ ├── vision-provider.ts # IVisionProvider interface
63
+ │ │ └── logger.ts # ILogger interface
64
+ │ └── chat-providers/
65
+ │ ├── bedrock-ai-provider.ts # IAIProvider → Bedrock
66
+ │ ├── firebase-storage-provider.ts # IStorageProvider → Firebase
67
+ │ ├── mcp-provider.ts # IMCPProvider → MCP SDK
68
+ │ └── google-vision-provider.ts # IVisionProvider → Google
69
+ └── durable-objects/
70
+ └── ChatRoom.ts # Assembles and injects providers
71
+ ```
72
+
73
+ ### Code Example
74
+
75
+ #### Step 1: Define Provider Interfaces
76
+
77
+ ```typescript
78
+ // src/lib/chat/interfaces/ai-provider.ts
79
+ import type { ChatEngineMessage } from '../types'
80
+
81
+ export interface IAIProvider {
82
+ streamChat(params: StreamChatParams): Promise<void>
83
+ }
84
+
85
+ export interface StreamChatParams {
86
+ messages: ChatMessage[]
87
+ systemPrompt: string
88
+ tools: Tool[]
89
+ onMessage: (message: ChatEngineMessage) => void
90
+ executeTool: (toolName: string, toolInput: any) => Promise<any>
91
+ }
92
+
93
+ export interface ChatMessage {
94
+ role: 'user' | 'assistant'
95
+ content: string | ContentBlock[]
96
+ }
97
+
98
+ export interface Tool {
99
+ name: string
100
+ description: string
101
+ input_schema: {
102
+ type: 'object'
103
+ properties: Record<string, any>
104
+ required?: string[]
105
+ }
106
+ }
107
+ ```
108
+
109
+ ```typescript
110
+ // src/lib/chat/interfaces/storage-provider.ts
111
+
112
+ export interface IStorageProvider {
113
+ saveMessage(params: SaveMessageParams): Promise<Message>
114
+ loadMessages(params: LoadMessagesParams): Promise<Message[]>
115
+ ensureConversation(params: EnsureConversationParams): Promise<string>
116
+ getConversation(params: GetConversationParams): Promise<Conversation | null>
117
+ updateConversation(params: UpdateConversationParams): Promise<void>
118
+ generateTitle?(params: GenerateTitleParams): Promise<string> // Optional
119
+ addToolCall(params: AddToolCallParams): Promise<string>
120
+ updateToolCall(params: UpdateToolCallParams): Promise<void>
121
+ }
122
+
123
+ export interface SaveMessageParams {
124
+ userId: string
125
+ conversationId: string
126
+ message: Message
127
+ }
128
+
129
+ export interface LoadMessagesParams {
130
+ userId: string
131
+ conversationId: string
132
+ limit?: number
133
+ startAfter?: string
134
+ }
135
+ ```
136
+
137
+ ```typescript
138
+ // src/lib/chat/interfaces/mcp-provider.ts
139
+
140
+ export interface IMCPProvider {
141
+ getAvailableServers(params: GetAvailableServersParams): Promise<MCPServer[]>
142
+ connectToServers(params: ConnectToServersParams): Promise<MCPConnection[]>
143
+ getTools(connections: MCPConnection[]): Promise<Tool[]>
144
+ executeTool(params: ExecuteToolParams): Promise<any>
145
+ disconnect(connections: MCPConnection[]): Promise<void>
146
+ }
147
+ ```
148
+
149
+ ```typescript
150
+ // src/lib/chat/interfaces/vision-provider.ts
151
+
152
+ export interface IVisionProvider {
153
+ analyzeImage(params: AnalyzeImageParams): Promise<VisionResult>
154
+ processImagesInMessage(params: ProcessImagesParams): Promise<MessageContent>
155
+ }
156
+ ```
157
+
158
+ #### Step 2: Create Engine with DI
159
+
160
+ ```typescript
161
+ // src/lib/chat/chat-engine.ts
162
+ import type { IAIProvider } from './interfaces/ai-provider'
163
+ import type { IStorageProvider } from './interfaces/storage-provider'
164
+ import type { IMCPProvider } from './interfaces/mcp-provider'
165
+ import type { IVisionProvider } from './interfaces/vision-provider'
166
+ import type { ILogger } from './interfaces/logger'
167
+
168
+ interface ChatEngineConfig {
169
+ aiProvider: IAIProvider
170
+ storageProvider: IStorageProvider
171
+ mcpProvider?: IMCPProvider // Optional — not all apps need MCP
172
+ visionProvider?: IVisionProvider // Optional — not all apps need vision
173
+ logger: ILogger
174
+ }
175
+
176
+ export class ChatEngine {
177
+ private ai: IAIProvider
178
+ private storage: IStorageProvider
179
+ private mcp?: IMCPProvider
180
+ private vision?: IVisionProvider
181
+ private logger: ILogger
182
+
183
+ constructor(config: ChatEngineConfig) {
184
+ this.ai = config.aiProvider
185
+ this.storage = config.storageProvider
186
+ this.mcp = config.mcpProvider
187
+ this.vision = config.visionProvider
188
+ this.logger = config.logger
189
+ }
190
+
191
+ async processMessage(params: ProcessMessageParams): Promise<void> {
192
+ const { userId, conversationId, message, onMessage } = params
193
+
194
+ // 1. Save user message
195
+ const savedMessage = await this.storage.saveMessage({
196
+ userId, conversationId, message: { ...message, role: 'user' }
197
+ })
198
+ onMessage({ type: 'user_message_saved', message: savedMessage })
199
+
200
+ // 2. Load history
201
+ const history = await this.storage.loadMessages({ userId, conversationId })
202
+
203
+ // 3. Get tools (if MCP available)
204
+ let tools: Tool[] = []
205
+ if (this.mcp) {
206
+ const servers = await this.mcp.getAvailableServers({ userId })
207
+ const connections = await this.mcp.connectToServers({ servers, userId })
208
+ tools = await this.mcp.getTools(connections)
209
+ }
210
+
211
+ // 4. Stream AI response
212
+ await this.ai.streamChat({
213
+ messages: history,
214
+ systemPrompt: this.buildSystemPrompt(userId),
215
+ tools,
216
+ onMessage,
217
+ executeTool: async (name, input) => {
218
+ if (!this.mcp) throw new Error('No MCP provider')
219
+ return this.mcp.executeTool({ toolName: name, toolInput: input, connections: [] })
220
+ }
221
+ })
222
+ }
223
+
224
+ async loadMessages(params: LoadMessagesParams) {
225
+ return this.storage.loadMessages(params)
226
+ }
227
+ }
228
+ ```
229
+
230
+ #### Step 3: Inject Implementations in Durable Object
231
+
232
+ ```typescript
233
+ // src/durable-objects/ChatRoom.ts
234
+ import { ChatEngine } from '@/lib/chat'
235
+ import {
236
+ BedrockAIProvider,
237
+ FirebaseStorageProvider,
238
+ MCPProvider,
239
+ GoogleVisionProvider
240
+ } from '@/lib/chat-providers'
241
+
242
+ export class ChatRoom extends DurableObject {
243
+ private chatEngine: ChatEngine
244
+
245
+ constructor(state: DurableObjectState, env: Env) {
246
+ super(state, env)
247
+
248
+ // Wire up all dependencies
249
+ this.chatEngine = new ChatEngine({
250
+ aiProvider: new BedrockAIProvider(),
251
+ storageProvider: new FirebaseStorageProvider(),
252
+ mcpProvider: new MCPProvider(),
253
+ visionProvider: new GoogleVisionProvider(),
254
+ logger: chatLogger
255
+ })
256
+ }
257
+ }
258
+ ```
259
+
260
+ #### Step 4: Mock for Testing
261
+
262
+ ```typescript
263
+ // src/__tests__/chat-engine.spec.ts
264
+ import { ChatEngine } from '@/lib/chat/chat-engine'
265
+
266
+ describe('ChatEngine', () => {
267
+ const mockAI: IAIProvider = {
268
+ streamChat: jest.fn(async ({ onMessage }) => {
269
+ onMessage({ type: 'chunk', content: 'Hello!' })
270
+ onMessage({ type: 'complete' })
271
+ })
272
+ }
273
+
274
+ const mockStorage: IStorageProvider = {
275
+ saveMessage: jest.fn(async (params) => ({ ...params.message, id: 'msg-1' })),
276
+ loadMessages: jest.fn(async () => []),
277
+ ensureConversation: jest.fn(async () => 'conv-1'),
278
+ getConversation: jest.fn(async () => null),
279
+ updateConversation: jest.fn(),
280
+ addToolCall: jest.fn(async () => 'tc-1'),
281
+ updateToolCall: jest.fn(),
282
+ }
283
+
284
+ it('should process message through providers', async () => {
285
+ const engine = new ChatEngine({
286
+ aiProvider: mockAI,
287
+ storageProvider: mockStorage,
288
+ logger: { info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn() }
289
+ })
290
+
291
+ const messages: any[] = []
292
+ await engine.processMessage({
293
+ userId: 'user1',
294
+ conversationId: 'conv1',
295
+ message: { content: 'Hi', role: 'user' },
296
+ onMessage: (msg) => messages.push(msg)
297
+ })
298
+
299
+ expect(mockStorage.saveMessage).toHaveBeenCalled()
300
+ expect(mockAI.streamChat).toHaveBeenCalled()
301
+ expect(messages).toContainEqual({ type: 'chunk', content: 'Hello!' })
302
+ })
303
+ })
304
+ ```
305
+
306
+ ---
307
+
308
+ ## Benefits
309
+
310
+ ### 1. Portable Business Logic
311
+ Core engine depends on interfaces, not implementations — extractable to npm package.
312
+
313
+ ### 2. Provider Swapping
314
+ Change `BedrockAIProvider` to `OpenAIProvider` in one place (the constructor).
315
+
316
+ ### 3. Full Testability
317
+ All external dependencies are mockable via interface substitution.
318
+
319
+ ### 4. Incremental Adoption
320
+ Optional providers (MCP, vision) can be added later without changing existing code.
321
+
322
+ ---
323
+
324
+ ## Trade-offs
325
+
326
+ ### 1. Interface Overhead
327
+ **Downside**: Additional files and indirection for each provider type.
328
+ **Mitigation**: Only create interfaces for dependencies you actually need to swap or mock.
329
+
330
+ ### 2. Param Object Verbosity
331
+ **Downside**: Parameter objects are more verbose than positional arguments.
332
+ **Mitigation**: Provides better readability and extensibility (add fields without breaking callers).
333
+
334
+ ---
335
+
336
+ ## Anti-Patterns
337
+
338
+ ### ❌ Anti-Pattern 1: Direct Imports in Engine
339
+
340
+ ```typescript
341
+ // ❌ BAD: Engine directly imports implementation
342
+ import { BedrockAIProvider } from '@/lib/chat-providers'
343
+
344
+ export class ChatEngine {
345
+ private ai = new BedrockAIProvider() // Tight coupling!
346
+ }
347
+
348
+ // ✅ GOOD: Engine accepts interface
349
+ export class ChatEngine {
350
+ constructor(config: { aiProvider: IAIProvider }) {
351
+ this.ai = config.aiProvider
352
+ }
353
+ }
354
+ ```
355
+
356
+ ### ❌ Anti-Pattern 2: God Interface
357
+
358
+ ```typescript
359
+ // ❌ BAD: One interface for everything
360
+ interface IChatProvider {
361
+ streamChat(): void
362
+ saveMessage(): void
363
+ loadMessages(): void
364
+ analyzeImage(): void
365
+ getTools(): void
366
+ }
367
+
368
+ // ✅ GOOD: Separate interfaces per concern
369
+ interface IAIProvider { streamChat(): void }
370
+ interface IStorageProvider { saveMessage(): void; loadMessages(): void }
371
+ interface IVisionProvider { analyzeImage(): void }
372
+ ```
373
+
374
+ ---
375
+
376
+ ## Related Patterns
377
+
378
+ - **[Durable Objects WebSocket](./tanstack-cloudflare.durable-objects-websocket.md)**: DOs inject providers into engines
379
+ - **[Library Services Pattern](./tanstack-cloudflare.library-services.md)**: Services can implement storage interfaces
380
+ - **[Zod Schema Validation](./tanstack-cloudflare.zod-schema-validation.md)**: Schemas define data shapes passed through interfaces
381
+
382
+ ---
383
+
384
+ ## Checklist for Implementation
385
+
386
+ - [ ] Interfaces defined in `interfaces/` directory
387
+ - [ ] Each interface covers one capability (single responsibility)
388
+ - [ ] Methods use typed parameter objects
389
+ - [ ] Optional providers marked with `?` in config
390
+ - [ ] Engine constructor accepts config object with all providers
391
+ - [ ] Implementations in separate `providers/` or `chat-providers/` directory
392
+ - [ ] Assembly (wiring) happens in Durable Object or server entry point
393
+ - [ ] Tests mock all providers via interface
394
+ - [ ] No direct implementation imports in engine code
395
+
396
+ ---
397
+
398
+ **Status**: Stable - Core architectural pattern for pluggable systems
399
+ **Recommendation**: Use when building features with external dependencies that may change
400
+ **Last Updated**: 2026-02-28
401
+ **Contributors**: Patrick Michaelsen