@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.
- package/LICENSE +214 -0
- package/dist/consensus/index.d.ts +23 -0
- package/dist/consensus/index.d.ts.map +1 -0
- package/dist/consensus/index.js +45 -0
- package/dist/consensus/index.js.map +1 -0
- package/dist/consensus/moderator.d.ts +15 -0
- package/dist/consensus/moderator.d.ts.map +1 -0
- package/dist/consensus/moderator.js +201 -0
- package/dist/consensus/moderator.js.map +1 -0
- package/dist/consensus/synthesis.d.ts +15 -0
- package/dist/consensus/synthesis.d.ts.map +1 -0
- package/dist/consensus/synthesis.js +161 -0
- package/dist/consensus/synthesis.js.map +1 -0
- package/dist/consensus/voting.d.ts +17 -0
- package/dist/consensus/voting.d.ts.map +1 -0
- package/dist/consensus/voting.js +168 -0
- package/dist/consensus/voting.js.map +1 -0
- package/dist/executor.d.ts +73 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +223 -0
- package/dist/executor.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/patterns/critique.d.ts +20 -0
- package/dist/patterns/critique.d.ts.map +1 -0
- package/dist/patterns/critique.js +338 -0
- package/dist/patterns/critique.js.map +1 -0
- package/dist/patterns/debate.d.ts +20 -0
- package/dist/patterns/debate.d.ts.map +1 -0
- package/dist/patterns/debate.js +236 -0
- package/dist/patterns/debate.js.map +1 -0
- package/dist/patterns/index.d.ts +25 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +49 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/patterns/round-robin.d.ts +13 -0
- package/dist/patterns/round-robin.d.ts.map +1 -0
- package/dist/patterns/round-robin.js +160 -0
- package/dist/patterns/round-robin.js.map +1 -0
- package/dist/patterns/synthesis.d.ts +20 -0
- package/dist/patterns/synthesis.d.ts.map +1 -0
- package/dist/patterns/synthesis.js +250 -0
- package/dist/patterns/synthesis.js.map +1 -0
- package/dist/patterns/voting.d.ts +19 -0
- package/dist/patterns/voting.d.ts.map +1 -0
- package/dist/patterns/voting.js +186 -0
- package/dist/patterns/voting.js.map +1 -0
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +21 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/templates.d.ts +55 -0
- package/dist/prompts/templates.d.ts.map +1 -0
- package/dist/prompts/templates.js +346 -0
- package/dist/prompts/templates.js.map +1 -0
- package/dist/provider-bridge.d.ts +115 -0
- package/dist/provider-bridge.d.ts.map +1 -0
- package/dist/provider-bridge.js +215 -0
- package/dist/provider-bridge.js.map +1 -0
- package/dist/types.d.ts +201 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +102 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
- package/src/consensus/index.ts +52 -0
- package/src/consensus/moderator.ts +242 -0
- package/src/consensus/synthesis.ts +202 -0
- package/src/consensus/voting.ts +221 -0
- package/src/executor.ts +338 -0
- package/src/index.ts +69 -0
- package/src/patterns/critique.ts +465 -0
- package/src/patterns/debate.ts +340 -0
- package/src/patterns/index.ts +56 -0
- package/src/patterns/round-robin.ts +223 -0
- package/src/patterns/synthesis.ts +353 -0
- package/src/patterns/voting.ts +266 -0
- package/src/prompts/index.ts +41 -0
- package/src/prompts/templates.ts +381 -0
- package/src/provider-bridge.ts +346 -0
- 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
|
+
}
|