@defai.digital/discussion-domain 13.0.3

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 (82) hide show
  1. package/LICENSE +214 -0
  2. package/dist/consensus/index.d.ts +23 -0
  3. package/dist/consensus/index.d.ts.map +1 -0
  4. package/dist/consensus/index.js +45 -0
  5. package/dist/consensus/index.js.map +1 -0
  6. package/dist/consensus/moderator.d.ts +15 -0
  7. package/dist/consensus/moderator.d.ts.map +1 -0
  8. package/dist/consensus/moderator.js +201 -0
  9. package/dist/consensus/moderator.js.map +1 -0
  10. package/dist/consensus/synthesis.d.ts +15 -0
  11. package/dist/consensus/synthesis.d.ts.map +1 -0
  12. package/dist/consensus/synthesis.js +161 -0
  13. package/dist/consensus/synthesis.js.map +1 -0
  14. package/dist/consensus/voting.d.ts +17 -0
  15. package/dist/consensus/voting.d.ts.map +1 -0
  16. package/dist/consensus/voting.js +168 -0
  17. package/dist/consensus/voting.js.map +1 -0
  18. package/dist/executor.d.ts +73 -0
  19. package/dist/executor.d.ts.map +1 -0
  20. package/dist/executor.js +223 -0
  21. package/dist/executor.js.map +1 -0
  22. package/dist/index.d.ts +16 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +21 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/patterns/critique.d.ts +20 -0
  27. package/dist/patterns/critique.d.ts.map +1 -0
  28. package/dist/patterns/critique.js +338 -0
  29. package/dist/patterns/critique.js.map +1 -0
  30. package/dist/patterns/debate.d.ts +20 -0
  31. package/dist/patterns/debate.d.ts.map +1 -0
  32. package/dist/patterns/debate.js +236 -0
  33. package/dist/patterns/debate.js.map +1 -0
  34. package/dist/patterns/index.d.ts +25 -0
  35. package/dist/patterns/index.d.ts.map +1 -0
  36. package/dist/patterns/index.js +49 -0
  37. package/dist/patterns/index.js.map +1 -0
  38. package/dist/patterns/round-robin.d.ts +13 -0
  39. package/dist/patterns/round-robin.d.ts.map +1 -0
  40. package/dist/patterns/round-robin.js +160 -0
  41. package/dist/patterns/round-robin.js.map +1 -0
  42. package/dist/patterns/synthesis.d.ts +20 -0
  43. package/dist/patterns/synthesis.d.ts.map +1 -0
  44. package/dist/patterns/synthesis.js +250 -0
  45. package/dist/patterns/synthesis.js.map +1 -0
  46. package/dist/patterns/voting.d.ts +19 -0
  47. package/dist/patterns/voting.d.ts.map +1 -0
  48. package/dist/patterns/voting.js +186 -0
  49. package/dist/patterns/voting.js.map +1 -0
  50. package/dist/prompts/index.d.ts +7 -0
  51. package/dist/prompts/index.d.ts.map +1 -0
  52. package/dist/prompts/index.js +21 -0
  53. package/dist/prompts/index.js.map +1 -0
  54. package/dist/prompts/templates.d.ts +55 -0
  55. package/dist/prompts/templates.d.ts.map +1 -0
  56. package/dist/prompts/templates.js +346 -0
  57. package/dist/prompts/templates.js.map +1 -0
  58. package/dist/provider-bridge.d.ts +115 -0
  59. package/dist/provider-bridge.d.ts.map +1 -0
  60. package/dist/provider-bridge.js +215 -0
  61. package/dist/provider-bridge.js.map +1 -0
  62. package/dist/types.d.ts +201 -0
  63. package/dist/types.d.ts.map +1 -0
  64. package/dist/types.js +102 -0
  65. package/dist/types.js.map +1 -0
  66. package/package.json +48 -0
  67. package/src/consensus/index.ts +52 -0
  68. package/src/consensus/moderator.ts +242 -0
  69. package/src/consensus/synthesis.ts +202 -0
  70. package/src/consensus/voting.ts +221 -0
  71. package/src/executor.ts +338 -0
  72. package/src/index.ts +69 -0
  73. package/src/patterns/critique.ts +465 -0
  74. package/src/patterns/debate.ts +340 -0
  75. package/src/patterns/index.ts +56 -0
  76. package/src/patterns/round-robin.ts +223 -0
  77. package/src/patterns/synthesis.ts +353 -0
  78. package/src/patterns/voting.ts +266 -0
  79. package/src/prompts/index.ts +41 -0
  80. package/src/prompts/templates.ts +381 -0
  81. package/src/provider-bridge.ts +346 -0
  82. package/src/types.ts +375 -0
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Discussion Provider Bridge
3
+ *
4
+ * Bridges the discussion-domain's DiscussionProviderExecutor interface
5
+ * to the provider-adapters' ProviderRegistry.
6
+ *
7
+ * This adapter pattern allows the discussion domain to remain decoupled
8
+ * from the concrete provider implementation details.
9
+ */
10
+
11
+ import type {
12
+ DiscussionProviderExecutor,
13
+ ProviderExecuteRequest,
14
+ ProviderExecuteResult,
15
+ } from './types.js';
16
+
17
+ /**
18
+ * Minimal LLMProvider interface (mirrors provider-adapters without importing)
19
+ */
20
+ interface LLMProviderLike {
21
+ providerId: string;
22
+ complete(request: CompletionRequestLike): Promise<CompletionResponseLike>;
23
+ checkHealth(): Promise<HealthCheckResultLike>;
24
+ isAvailable(): Promise<boolean>;
25
+ }
26
+
27
+ /**
28
+ * Minimal CompletionRequest interface
29
+ */
30
+ interface CompletionRequestLike {
31
+ requestId: string;
32
+ model: string;
33
+ messages: { role: 'system' | 'user' | 'assistant'; content: string }[];
34
+ maxTokens?: number | undefined;
35
+ temperature?: number | undefined;
36
+ timeout?: number | undefined;
37
+ systemPrompt?: string | undefined;
38
+ }
39
+
40
+ /**
41
+ * Minimal CompletionResponse interface (discriminated union)
42
+ */
43
+ type CompletionResponseLike =
44
+ | {
45
+ success: true;
46
+ requestId: string;
47
+ content: string;
48
+ model: string;
49
+ usage: { inputTokens: number; outputTokens: number; totalTokens: number };
50
+ latencyMs: number;
51
+ }
52
+ | {
53
+ success: false;
54
+ requestId: string;
55
+ error: {
56
+ category: string;
57
+ message: string;
58
+ shouldRetry: boolean;
59
+ };
60
+ latencyMs: number;
61
+ };
62
+
63
+ /**
64
+ * Minimal HealthCheckResult interface
65
+ */
66
+ interface HealthCheckResultLike {
67
+ healthy: boolean;
68
+ message?: string | undefined;
69
+ latencyMs: number;
70
+ }
71
+
72
+ /**
73
+ * Minimal ProviderRegistry interface
74
+ */
75
+ export interface ProviderRegistryLike {
76
+ get(providerId: string): LLMProviderLike | undefined;
77
+ getAll(): LLMProviderLike[];
78
+ has(providerId: string): boolean;
79
+ }
80
+
81
+ /**
82
+ * Options for creating the provider bridge
83
+ */
84
+ export interface ProviderBridgeOptions {
85
+ /**
86
+ * Default timeout per provider call in milliseconds
87
+ */
88
+ defaultTimeoutMs?: number | undefined;
89
+
90
+ /**
91
+ * Default max tokens per response
92
+ */
93
+ defaultMaxTokens?: number | undefined;
94
+
95
+ /**
96
+ * Whether to perform health checks before considering a provider available
97
+ */
98
+ performHealthChecks?: boolean | undefined;
99
+
100
+ /**
101
+ * Cache health check results for this many milliseconds
102
+ */
103
+ healthCheckCacheMs?: number | undefined;
104
+ }
105
+
106
+ /**
107
+ * Creates a DiscussionProviderExecutor that bridges to a ProviderRegistry
108
+ *
109
+ * @param registry - The provider registry to bridge to
110
+ * @param options - Bridge configuration options
111
+ * @returns A DiscussionProviderExecutor implementation
112
+ */
113
+ export function createProviderBridge(
114
+ registry: ProviderRegistryLike,
115
+ options: ProviderBridgeOptions = {}
116
+ ): DiscussionProviderExecutor {
117
+ const {
118
+ defaultTimeoutMs = 120000,
119
+ defaultMaxTokens = 4000,
120
+ performHealthChecks = true,
121
+ healthCheckCacheMs = 60000,
122
+ } = options;
123
+
124
+ // Cache for health check results
125
+ const healthCache = new Map<string, { healthy: boolean; timestamp: number }>();
126
+
127
+ return {
128
+ async execute(request: ProviderExecuteRequest): Promise<ProviderExecuteResult> {
129
+ const startTime = Date.now();
130
+ const { providerId, prompt, systemPrompt, temperature, maxTokens, timeoutMs } = request;
131
+
132
+ // Get provider from registry
133
+ const provider = registry.get(providerId);
134
+
135
+ if (!provider) {
136
+ return {
137
+ success: false,
138
+ error: `Provider ${providerId} not found in registry`,
139
+ retryable: false,
140
+ durationMs: Date.now() - startTime,
141
+ };
142
+ }
143
+
144
+ try {
145
+ // Check abort signal
146
+ if (request.abortSignal?.aborted) {
147
+ return {
148
+ success: false,
149
+ error: 'Request aborted',
150
+ retryable: false,
151
+ durationMs: Date.now() - startTime,
152
+ };
153
+ }
154
+
155
+ // Build messages array
156
+ const messages: CompletionRequestLike['messages'] = [];
157
+
158
+ if (systemPrompt) {
159
+ messages.push({ role: 'system', content: systemPrompt });
160
+ }
161
+
162
+ messages.push({ role: 'user', content: prompt });
163
+
164
+ // Build completion request
165
+ const completionRequest: CompletionRequestLike = {
166
+ requestId: `discuss-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
167
+ model: 'default',
168
+ messages,
169
+ maxTokens: maxTokens ?? defaultMaxTokens,
170
+ temperature: temperature ?? 0.7,
171
+ timeout: timeoutMs ?? defaultTimeoutMs,
172
+ };
173
+
174
+ // Execute the completion
175
+ const response = await provider.complete(completionRequest);
176
+
177
+ if (response.success) {
178
+ return {
179
+ success: true,
180
+ content: response.content,
181
+ durationMs: response.latencyMs,
182
+ tokenCount: response.usage.totalTokens,
183
+ };
184
+ } else {
185
+ return {
186
+ success: false,
187
+ error: response.error.message,
188
+ retryable: response.error.shouldRetry,
189
+ durationMs: response.latencyMs,
190
+ };
191
+ }
192
+ } catch (error) {
193
+ const errorMessage = error instanceof Error ? error.message : String(error);
194
+
195
+ return {
196
+ success: false,
197
+ error: errorMessage,
198
+ retryable: true, // Assume retryable for unexpected errors
199
+ durationMs: Date.now() - startTime,
200
+ };
201
+ }
202
+ },
203
+
204
+ async isAvailable(providerId: string): Promise<boolean> {
205
+ const provider = registry.get(providerId);
206
+
207
+ if (!provider) {
208
+ return false;
209
+ }
210
+
211
+ // Check cache first
212
+ const cached = healthCache.get(providerId);
213
+ if (cached && Date.now() - cached.timestamp < healthCheckCacheMs) {
214
+ return cached.healthy;
215
+ }
216
+
217
+ // Perform health check if enabled
218
+ if (performHealthChecks) {
219
+ try {
220
+ const health = await provider.checkHealth();
221
+ healthCache.set(providerId, {
222
+ healthy: health.healthy,
223
+ timestamp: Date.now(),
224
+ });
225
+ return health.healthy;
226
+ } catch {
227
+ healthCache.set(providerId, {
228
+ healthy: false,
229
+ timestamp: Date.now(),
230
+ });
231
+ return false;
232
+ }
233
+ }
234
+
235
+ // If health checks disabled, just check if provider exists
236
+ return true;
237
+ },
238
+
239
+ async getAvailableProviders(): Promise<string[]> {
240
+ const allProviders = registry.getAll();
241
+ const available: string[] = [];
242
+
243
+ // Check each provider's availability
244
+ for (const provider of allProviders) {
245
+ // Check cache first
246
+ const cached = healthCache.get(provider.providerId);
247
+ if (cached && Date.now() - cached.timestamp < healthCheckCacheMs) {
248
+ if (cached.healthy) {
249
+ available.push(provider.providerId);
250
+ }
251
+ continue;
252
+ }
253
+
254
+ // Perform health check if enabled
255
+ if (performHealthChecks) {
256
+ try {
257
+ const health = await provider.checkHealth();
258
+ healthCache.set(provider.providerId, {
259
+ healthy: health.healthy,
260
+ timestamp: Date.now(),
261
+ });
262
+ if (health.healthy) {
263
+ available.push(provider.providerId);
264
+ }
265
+ } catch {
266
+ healthCache.set(provider.providerId, {
267
+ healthy: false,
268
+ timestamp: Date.now(),
269
+ });
270
+ }
271
+ } else {
272
+ // If health checks disabled, assume available
273
+ available.push(provider.providerId);
274
+ }
275
+ }
276
+
277
+ return available;
278
+ },
279
+ };
280
+ }
281
+
282
+ /**
283
+ * Creates a provider bridge with a custom provider map (for testing)
284
+ *
285
+ * @param providers - Map of provider ID to execute function
286
+ * @returns A DiscussionProviderExecutor implementation
287
+ */
288
+ export function createSimpleProviderBridge(
289
+ providers: Map<
290
+ string,
291
+ (prompt: string, systemPrompt?: string) => Promise<{ content: string; tokenCount?: number }>
292
+ >
293
+ ): DiscussionProviderExecutor {
294
+ return {
295
+ async execute(request: ProviderExecuteRequest): Promise<ProviderExecuteResult> {
296
+ const startTime = Date.now();
297
+ const { providerId, prompt, systemPrompt } = request;
298
+
299
+ const executeFunc = providers.get(providerId);
300
+
301
+ if (!executeFunc) {
302
+ return {
303
+ success: false,
304
+ error: `Provider ${providerId} not available`,
305
+ retryable: false,
306
+ durationMs: Date.now() - startTime,
307
+ };
308
+ }
309
+
310
+ try {
311
+ if (request.abortSignal?.aborted) {
312
+ return {
313
+ success: false,
314
+ error: 'Request aborted',
315
+ retryable: false,
316
+ durationMs: Date.now() - startTime,
317
+ };
318
+ }
319
+
320
+ const result = await executeFunc(prompt, systemPrompt);
321
+
322
+ return {
323
+ success: true,
324
+ content: result.content,
325
+ durationMs: Date.now() - startTime,
326
+ tokenCount: result.tokenCount,
327
+ };
328
+ } catch (error) {
329
+ return {
330
+ success: false,
331
+ error: error instanceof Error ? error.message : String(error),
332
+ retryable: true,
333
+ durationMs: Date.now() - startTime,
334
+ };
335
+ }
336
+ },
337
+
338
+ async isAvailable(providerId: string): Promise<boolean> {
339
+ return providers.has(providerId);
340
+ },
341
+
342
+ async getAvailableProviders(): Promise<string[]> {
343
+ return Array.from(providers.keys());
344
+ },
345
+ };
346
+ }
package/src/types.ts ADDED
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Discussion Domain Types
3
+ *
4
+ * Core types and interfaces for multi-model discussion orchestration.
5
+ */
6
+
7
+ import type {
8
+ DiscussionPattern,
9
+ DiscussStepConfig,
10
+ DiscussionRound,
11
+ ConsensusResult,
12
+ ConsensusMethod,
13
+ VotingResults,
14
+ } from '@defai.digital/contracts';
15
+
16
+ // ============================================================================
17
+ // Provider Executor Interface
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Interface for executing prompts against LLM providers.
22
+ *
23
+ * This is a port interface that will be implemented by the provider adapters.
24
+ * The discussion domain depends on this abstraction, not concrete providers.
25
+ *
26
+ * Invariants:
27
+ * - INV-DISC-100: Provider availability must be checked before discussions
28
+ * - INV-DISC-102: Provider timeout must be enforced
29
+ */
30
+ export interface DiscussionProviderExecutor {
31
+ /**
32
+ * Execute a prompt against a specific provider
33
+ */
34
+ execute(request: ProviderExecuteRequest): Promise<ProviderExecuteResult>;
35
+
36
+ /**
37
+ * Check if a provider is available/healthy
38
+ */
39
+ isAvailable(providerId: string): Promise<boolean>;
40
+
41
+ /**
42
+ * Get list of available providers
43
+ */
44
+ getAvailableProviders(): Promise<string[]>;
45
+ }
46
+
47
+ /**
48
+ * Request to execute a prompt against a provider
49
+ */
50
+ export interface ProviderExecuteRequest {
51
+ /** Provider to use (e.g., 'claude', 'glm', 'qwen', 'gemini') */
52
+ providerId: string;
53
+
54
+ /** The prompt to send */
55
+ prompt: string;
56
+
57
+ /** System prompt/context */
58
+ systemPrompt?: string | undefined;
59
+
60
+ /** Temperature (0-2) */
61
+ temperature?: number | undefined;
62
+
63
+ /** Max tokens for response */
64
+ maxTokens?: number | undefined;
65
+
66
+ /** Timeout in milliseconds */
67
+ timeoutMs?: number | undefined;
68
+
69
+ /** Abort signal for cancellation */
70
+ abortSignal?: AbortSignal | undefined;
71
+ }
72
+
73
+ /**
74
+ * Result from provider execution
75
+ */
76
+ export interface ProviderExecuteResult {
77
+ /** Whether execution succeeded */
78
+ success: boolean;
79
+
80
+ /** Response content */
81
+ content?: string | undefined;
82
+
83
+ /** Error message if failed */
84
+ error?: string | undefined;
85
+
86
+ /** Whether error is retryable */
87
+ retryable?: boolean | undefined;
88
+
89
+ /** Duration in milliseconds */
90
+ durationMs: number;
91
+
92
+ /** Token count if available */
93
+ tokenCount?: number | undefined;
94
+
95
+ /** Whether response was truncated */
96
+ truncated?: boolean | undefined;
97
+ }
98
+
99
+ // ============================================================================
100
+ // Pattern Executor Interface
101
+ // ============================================================================
102
+
103
+ /**
104
+ * Context passed to pattern executors
105
+ */
106
+ export interface PatternExecutionContext {
107
+ /** The discussion configuration */
108
+ config: DiscussStepConfig;
109
+
110
+ /** Provider executor for making calls */
111
+ providerExecutor: DiscussionProviderExecutor;
112
+
113
+ /** Providers that are available */
114
+ availableProviders: string[];
115
+
116
+ /** Abort signal for cancellation */
117
+ abortSignal?: AbortSignal | undefined;
118
+
119
+ /** Trace ID for debugging */
120
+ traceId?: string | undefined;
121
+
122
+ /** Callback for progress updates */
123
+ onProgress?: ((event: DiscussionProgressEvent) => void) | undefined;
124
+ }
125
+
126
+ /**
127
+ * Progress event during discussion execution
128
+ */
129
+ export interface DiscussionProgressEvent {
130
+ type: 'round_start' | 'provider_start' | 'provider_complete' | 'round_complete' | 'synthesis_start' | 'synthesis_complete';
131
+ round?: number | undefined;
132
+ provider?: string | undefined;
133
+ message?: string | undefined;
134
+ timestamp: string;
135
+ }
136
+
137
+ /**
138
+ * Result from pattern execution
139
+ */
140
+ export interface PatternExecutionResult {
141
+ /** All discussion rounds */
142
+ rounds: DiscussionRound[];
143
+
144
+ /** Providers that participated successfully */
145
+ participatingProviders: string[];
146
+
147
+ /** Providers that failed */
148
+ failedProviders: string[];
149
+
150
+ /** Total duration in milliseconds */
151
+ totalDurationMs: number;
152
+
153
+ /** Whether execution succeeded (enough providers participated) */
154
+ success: boolean;
155
+
156
+ /** Error if execution failed */
157
+ error?: string | undefined;
158
+ }
159
+
160
+ /**
161
+ * Interface for discussion pattern executors
162
+ */
163
+ export interface PatternExecutor {
164
+ /** Pattern type this executor handles */
165
+ readonly pattern: DiscussionPattern;
166
+
167
+ /**
168
+ * Execute the discussion pattern
169
+ */
170
+ execute(context: PatternExecutionContext): Promise<PatternExecutionResult>;
171
+ }
172
+
173
+ // ============================================================================
174
+ // Consensus Executor Interface
175
+ // ============================================================================
176
+
177
+ /**
178
+ * Context for consensus execution
179
+ */
180
+ export interface ConsensusExecutionContext {
181
+ /** Original topic/prompt */
182
+ topic: string;
183
+
184
+ /** All discussion rounds */
185
+ rounds: DiscussionRound[];
186
+
187
+ /** Providers that participated */
188
+ participatingProviders: string[];
189
+
190
+ /** Consensus configuration */
191
+ config: DiscussStepConfig['consensus'];
192
+
193
+ /** Provider executor for synthesis calls */
194
+ providerExecutor: DiscussionProviderExecutor;
195
+
196
+ /** Abort signal */
197
+ abortSignal?: AbortSignal | undefined;
198
+
199
+ /** Progress callback */
200
+ onProgress?: ((event: DiscussionProgressEvent) => void) | undefined;
201
+ }
202
+
203
+ /**
204
+ * Result from consensus execution
205
+ */
206
+ export interface ConsensusExecutionResult {
207
+ /** Synthesized final output */
208
+ synthesis: string;
209
+
210
+ /** Consensus details */
211
+ consensus: ConsensusResult;
212
+
213
+ /** Voting results if applicable */
214
+ votingResults?: VotingResults | undefined;
215
+
216
+ /** Duration in milliseconds */
217
+ durationMs: number;
218
+
219
+ /** Whether consensus was reached */
220
+ success: boolean;
221
+
222
+ /** Error if failed */
223
+ error?: string | undefined;
224
+ }
225
+
226
+ /**
227
+ * Interface for consensus mechanism executors
228
+ */
229
+ export interface ConsensusExecutor {
230
+ /** Consensus method this executor handles */
231
+ readonly method: ConsensusMethod;
232
+
233
+ /**
234
+ * Execute consensus mechanism
235
+ */
236
+ execute(context: ConsensusExecutionContext): Promise<ConsensusExecutionResult>;
237
+ }
238
+
239
+ // ============================================================================
240
+ // Discussion Executor Options
241
+ // ============================================================================
242
+
243
+ /**
244
+ * Options for creating a discussion executor
245
+ */
246
+ export interface DiscussionExecutorOptions {
247
+ /** Provider executor implementation */
248
+ providerExecutor: DiscussionProviderExecutor;
249
+
250
+ /** Default timeout per provider in milliseconds */
251
+ defaultTimeoutMs?: number | undefined;
252
+
253
+ /** Whether to check provider health before starting */
254
+ checkProviderHealth?: boolean | undefined;
255
+
256
+ /** Trace ID for debugging */
257
+ traceId?: string | undefined;
258
+ }
259
+
260
+ // ============================================================================
261
+ // Stub Provider Executor (for testing/development)
262
+ // ============================================================================
263
+
264
+ /**
265
+ * Stub provider executor that returns mock responses.
266
+ * Used for testing and development when real providers aren't available.
267
+ */
268
+ export class StubProviderExecutor implements DiscussionProviderExecutor {
269
+ private availableProviders: Set<string>;
270
+ private responseDelay: number;
271
+
272
+ constructor(
273
+ providers: string[] = ['claude', 'glm', 'qwen', 'gemini'],
274
+ responseDelayMs = 100
275
+ ) {
276
+ this.availableProviders = new Set(providers);
277
+ this.responseDelay = responseDelayMs;
278
+ }
279
+
280
+ async execute(request: ProviderExecuteRequest): Promise<ProviderExecuteResult> {
281
+ const start = Date.now();
282
+
283
+ // Simulate network delay
284
+ await new Promise(resolve => setTimeout(resolve, this.responseDelay));
285
+
286
+ if (!this.availableProviders.has(request.providerId)) {
287
+ return {
288
+ success: false,
289
+ error: `Provider ${request.providerId} not available`,
290
+ retryable: false,
291
+ durationMs: Date.now() - start,
292
+ };
293
+ }
294
+
295
+ // Generate mock response based on provider
296
+ const content = this.generateMockResponse(request.providerId, request.prompt);
297
+
298
+ return {
299
+ success: true,
300
+ content,
301
+ durationMs: Date.now() - start,
302
+ tokenCount: content.length / 4, // Rough estimate
303
+ };
304
+ }
305
+
306
+ async isAvailable(providerId: string): Promise<boolean> {
307
+ return this.availableProviders.has(providerId);
308
+ }
309
+
310
+ async getAvailableProviders(): Promise<string[]> {
311
+ return Array.from(this.availableProviders);
312
+ }
313
+
314
+ private generateMockResponse(providerId: string, prompt: string): string {
315
+ const providerStyles: Record<string, string> = {
316
+ claude: 'From a nuanced reasoning perspective',
317
+ glm: 'From a practical implementation standpoint',
318
+ qwen: 'Considering multilingual and mathematical aspects',
319
+ gemini: 'Based on extensive research and analysis',
320
+ codex: 'From a code-centric viewpoint',
321
+ grok: 'With real-time context in mind',
322
+ };
323
+
324
+ const style = providerStyles[providerId] || 'In my analysis';
325
+ const topicPreview = prompt.slice(0, 100).replace(/\n/g, ' ');
326
+
327
+ // Check if this is a voting prompt
328
+ if (prompt.includes('Your Vote:') || prompt.includes('evaluating options')) {
329
+ // Extract options from prompt - try numbered list format first (1. Option)
330
+ const numberedPattern = /^\d+\.\s*(.+)$/gm;
331
+ const matches = [...prompt.matchAll(numberedPattern)];
332
+ let options: string[];
333
+
334
+ if (matches.length >= 2) {
335
+ options = matches.map(m => m[1] ? m[1].trim() : '').filter(Boolean);
336
+ } else {
337
+ // Try comma-separated format
338
+ const optionsMatch = /Options:\s*([^\n]+)/i.exec(prompt);
339
+ options = optionsMatch?.[1]
340
+ ? optionsMatch[1].split(/[,;]/).map(o => o.trim()).filter(Boolean)
341
+ : ['Yes', 'No'];
342
+ }
343
+
344
+ const chosenOption = options[Math.floor(Math.random() * options.length)] || 'Yes';
345
+ const confidence = 70 + Math.floor(Math.random() * 25); // 70-94%
346
+
347
+ return `${style}, evaluating the options:\n\n` +
348
+ `After careful consideration of each option:\n\n` +
349
+ `Your Vote: [${chosenOption}]\n` +
350
+ `Confidence: [${confidence}%]\n\n` +
351
+ `Reasoning: This option best aligns with the requirements because ` +
352
+ `it offers the most balanced approach. ${providerId} recommends this ` +
353
+ `based on practical experience and technical merit.\n`;
354
+ }
355
+
356
+ return `${style}, regarding "${topicPreview}...":\n\n` +
357
+ `This is a mock response from ${providerId}. ` +
358
+ `In a real discussion, this would contain the provider's unique perspective ` +
359
+ `based on its strengths and training.\n\n` +
360
+ `Key points:\n` +
361
+ `1. First consideration from ${providerId}\n` +
362
+ `2. Second insight unique to this provider\n` +
363
+ `3. Practical recommendation\n`;
364
+ }
365
+
366
+ /** Add a provider to available set (for testing) */
367
+ addProvider(providerId: string): void {
368
+ this.availableProviders.add(providerId);
369
+ }
370
+
371
+ /** Remove a provider from available set (for testing) */
372
+ removeProvider(providerId: string): void {
373
+ this.availableProviders.delete(providerId);
374
+ }
375
+ }