@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,353 @@
1
+ /**
2
+ * Synthesis Pattern Executor
3
+ *
4
+ * Models give parallel perspectives, then cross-discuss, then one synthesizes.
5
+ * This is the default and most commonly used pattern.
6
+ *
7
+ * Flow:
8
+ * 1. Round 1: All providers respond in parallel with initial perspectives
9
+ * 2. Round 2: Each provider responds to others' perspectives
10
+ * 3. (Optional) Additional cross-discussion rounds
11
+ * 4. Final synthesis by designated synthesizer (default: claude)
12
+ */
13
+
14
+ import type { DiscussionRound, DebateRole } from '@defai.digital/contracts';
15
+ import type {
16
+ PatternExecutor,
17
+ PatternExecutionContext,
18
+ PatternExecutionResult,
19
+ } from '../types.js';
20
+ import {
21
+ SYNTHESIS_INITIAL,
22
+ SYNTHESIS_CROSS_DISCUSS,
23
+ interpolate,
24
+ formatPreviousResponses,
25
+ getProviderSystemPrompt,
26
+ } from '../prompts/templates.js';
27
+
28
+ // Local type for discussion DiscussionProviderResponse (avoids conflict with provider/v1 DiscussionProviderResponse)
29
+ interface DiscussionProviderResponse {
30
+ provider: string;
31
+ content: string;
32
+ round: number;
33
+ role?: DebateRole | undefined;
34
+ confidence?: number | undefined;
35
+ vote?: string | undefined;
36
+ timestamp: string;
37
+ durationMs: number;
38
+ tokenCount?: number | undefined;
39
+ truncated?: boolean | undefined;
40
+ error?: string | undefined;
41
+ }
42
+
43
+ export class SynthesisPattern implements PatternExecutor {
44
+ readonly pattern = 'synthesis' as const;
45
+
46
+ async execute(context: PatternExecutionContext): Promise<PatternExecutionResult> {
47
+ const startTime = Date.now();
48
+ const { config, providerExecutor, availableProviders, abortSignal, onProgress } = context;
49
+
50
+ const rounds: DiscussionRound[] = [];
51
+ const participatingProviders = new Set<string>();
52
+ const failedProviders = new Set<string>();
53
+
54
+ // Filter to available providers
55
+ const providers = config.providers.filter(p => availableProviders.includes(p));
56
+
57
+ if (providers.length < config.minProviders) {
58
+ return {
59
+ rounds: [],
60
+ participatingProviders: [],
61
+ failedProviders: config.providers.filter(p => !availableProviders.includes(p)),
62
+ totalDurationMs: Date.now() - startTime,
63
+ success: false,
64
+ error: `Only ${providers.length} providers available, need ${config.minProviders}`,
65
+ };
66
+ }
67
+
68
+ // Round 1: Gather initial perspectives in parallel
69
+ onProgress?.({
70
+ type: 'round_start',
71
+ round: 1,
72
+ message: 'Gathering initial perspectives in parallel',
73
+ timestamp: new Date().toISOString(),
74
+ });
75
+
76
+ const initialRound = await this.executeParallelRound(
77
+ 1,
78
+ providers,
79
+ config,
80
+ providerExecutor,
81
+ [],
82
+ abortSignal,
83
+ onProgress
84
+ );
85
+
86
+ rounds.push(initialRound.round);
87
+ initialRound.succeeded.forEach(p => participatingProviders.add(p));
88
+ initialRound.failed.forEach(p => failedProviders.add(p));
89
+
90
+ onProgress?.({
91
+ type: 'round_complete',
92
+ round: 1,
93
+ message: `Initial perspectives gathered from ${initialRound.succeeded.length} providers`,
94
+ timestamp: new Date().toISOString(),
95
+ });
96
+
97
+ // Check if enough providers participated
98
+ if (participatingProviders.size < config.minProviders) {
99
+ return {
100
+ rounds,
101
+ participatingProviders: Array.from(participatingProviders),
102
+ failedProviders: Array.from(failedProviders),
103
+ totalDurationMs: Date.now() - startTime,
104
+ success: false,
105
+ error: `Only ${participatingProviders.size} providers succeeded in round 1`,
106
+ };
107
+ }
108
+
109
+ // Additional rounds: Cross-discussion
110
+ for (let roundNum = 2; roundNum <= config.rounds; roundNum++) {
111
+ if (abortSignal?.aborted) {
112
+ break;
113
+ }
114
+
115
+ onProgress?.({
116
+ type: 'round_start',
117
+ round: roundNum,
118
+ message: `Starting cross-discussion round ${roundNum}`,
119
+ timestamp: new Date().toISOString(),
120
+ });
121
+
122
+ const crossRound = await this.executeCrossDiscussionRound(
123
+ roundNum,
124
+ providers.filter(p => participatingProviders.has(p)),
125
+ config,
126
+ providerExecutor,
127
+ rounds,
128
+ abortSignal,
129
+ onProgress
130
+ );
131
+
132
+ rounds.push(crossRound.round);
133
+ crossRound.failed.forEach(p => failedProviders.add(p));
134
+
135
+ onProgress?.({
136
+ type: 'round_complete',
137
+ round: roundNum,
138
+ message: `Cross-discussion round ${roundNum} complete`,
139
+ timestamp: new Date().toISOString(),
140
+ });
141
+ }
142
+
143
+ return {
144
+ rounds,
145
+ participatingProviders: Array.from(participatingProviders),
146
+ failedProviders: Array.from(failedProviders),
147
+ totalDurationMs: Date.now() - startTime,
148
+ success: participatingProviders.size >= config.minProviders,
149
+ };
150
+ }
151
+
152
+ private async executeParallelRound(
153
+ roundNum: number,
154
+ providers: string[],
155
+ config: PatternExecutionContext['config'],
156
+ providerExecutor: PatternExecutionContext['providerExecutor'],
157
+ _previousRounds: DiscussionRound[],
158
+ abortSignal?: AbortSignal,
159
+ onProgress?: PatternExecutionContext['onProgress']
160
+ ): Promise<{ round: DiscussionRound; succeeded: string[]; failed: string[] }> {
161
+ const roundStart = Date.now();
162
+ const succeeded: string[] = [];
163
+ const failed: string[] = [];
164
+
165
+ // Build prompts for all providers
166
+ const prompt = interpolate(SYNTHESIS_INITIAL, {
167
+ topic: config.prompt,
168
+ context: config.context ? `\n## Additional Context\n${config.context}` : '',
169
+ });
170
+
171
+ // Execute all providers in parallel
172
+ const promises = providers.map(async (providerId) => {
173
+ if (abortSignal?.aborted) {
174
+ return null;
175
+ }
176
+
177
+ onProgress?.({
178
+ type: 'provider_start',
179
+ round: roundNum,
180
+ provider: providerId,
181
+ timestamp: new Date().toISOString(),
182
+ });
183
+
184
+ const responseStart = Date.now();
185
+
186
+ try {
187
+ const result = await providerExecutor.execute({
188
+ providerId,
189
+ prompt: config.providerPrompts?.[providerId] || prompt,
190
+ systemPrompt: getProviderSystemPrompt(providerId),
191
+ temperature: config.temperature,
192
+ timeoutMs: config.providerTimeout,
193
+ abortSignal,
194
+ });
195
+
196
+ const response: DiscussionProviderResponse = {
197
+ provider: providerId,
198
+ content: result.success ? result.content || '' : '',
199
+ round: roundNum,
200
+ timestamp: new Date().toISOString(),
201
+ durationMs: result.durationMs,
202
+ tokenCount: result.tokenCount,
203
+ truncated: result.truncated,
204
+ error: result.success ? undefined : result.error,
205
+ };
206
+
207
+ if (result.success) {
208
+ succeeded.push(providerId);
209
+ } else {
210
+ failed.push(providerId);
211
+ }
212
+
213
+ onProgress?.({
214
+ type: 'provider_complete',
215
+ round: roundNum,
216
+ provider: providerId,
217
+ message: result.success ? 'completed' : `failed: ${result.error}`,
218
+ timestamp: new Date().toISOString(),
219
+ });
220
+
221
+ return response;
222
+
223
+ } catch (error) {
224
+ const errorMessage = error instanceof Error ? error.message : String(error);
225
+ failed.push(providerId);
226
+
227
+ return {
228
+ provider: providerId,
229
+ content: '',
230
+ round: roundNum,
231
+ timestamp: new Date().toISOString(),
232
+ durationMs: Date.now() - responseStart,
233
+ error: errorMessage,
234
+ } as DiscussionProviderResponse;
235
+ }
236
+ });
237
+
238
+ const responses = await Promise.all(promises);
239
+ const validResponses = responses.filter((r): r is DiscussionProviderResponse => r !== null);
240
+
241
+ return {
242
+ round: {
243
+ roundNumber: roundNum,
244
+ responses: validResponses,
245
+ durationMs: Date.now() - roundStart,
246
+ },
247
+ succeeded,
248
+ failed,
249
+ };
250
+ }
251
+
252
+ private async executeCrossDiscussionRound(
253
+ roundNum: number,
254
+ providers: string[],
255
+ config: PatternExecutionContext['config'],
256
+ providerExecutor: PatternExecutionContext['providerExecutor'],
257
+ previousRounds: DiscussionRound[],
258
+ abortSignal?: AbortSignal,
259
+ onProgress?: PatternExecutionContext['onProgress']
260
+ ): Promise<{ round: DiscussionRound; failed: string[] }> {
261
+ const roundStart = Date.now();
262
+ const failed: string[] = [];
263
+
264
+ // Execute all providers in parallel
265
+ const promises = providers.map(async (providerId) => {
266
+ if (abortSignal?.aborted) {
267
+ return null;
268
+ }
269
+
270
+ onProgress?.({
271
+ type: 'provider_start',
272
+ round: roundNum,
273
+ provider: providerId,
274
+ timestamp: new Date().toISOString(),
275
+ });
276
+
277
+ const responseStart = Date.now();
278
+
279
+ try {
280
+ // Get other providers' responses from previous round
281
+ const previousRound = previousRounds[previousRounds.length - 1];
282
+ const otherResponses = previousRound?.responses.filter(r => r.provider !== providerId) || [];
283
+
284
+ const prompt = interpolate(SYNTHESIS_CROSS_DISCUSS, {
285
+ topic: config.prompt,
286
+ otherPerspectives: formatPreviousResponses(
287
+ otherResponses.map(r => ({
288
+ provider: r.provider,
289
+ content: r.content,
290
+ }))
291
+ ),
292
+ });
293
+
294
+ const result = await providerExecutor.execute({
295
+ providerId,
296
+ prompt,
297
+ systemPrompt: getProviderSystemPrompt(providerId),
298
+ temperature: config.temperature,
299
+ timeoutMs: config.providerTimeout,
300
+ abortSignal,
301
+ });
302
+
303
+ const response: DiscussionProviderResponse = {
304
+ provider: providerId,
305
+ content: result.success ? result.content || '' : '',
306
+ round: roundNum,
307
+ timestamp: new Date().toISOString(),
308
+ durationMs: result.durationMs,
309
+ tokenCount: result.tokenCount,
310
+ error: result.success ? undefined : result.error,
311
+ };
312
+
313
+ if (!result.success) {
314
+ failed.push(providerId);
315
+ }
316
+
317
+ onProgress?.({
318
+ type: 'provider_complete',
319
+ round: roundNum,
320
+ provider: providerId,
321
+ timestamp: new Date().toISOString(),
322
+ });
323
+
324
+ return response;
325
+
326
+ } catch (error) {
327
+ const errorMessage = error instanceof Error ? error.message : String(error);
328
+ failed.push(providerId);
329
+
330
+ return {
331
+ provider: providerId,
332
+ content: '',
333
+ round: roundNum,
334
+ timestamp: new Date().toISOString(),
335
+ durationMs: Date.now() - responseStart,
336
+ error: errorMessage,
337
+ } as DiscussionProviderResponse;
338
+ }
339
+ });
340
+
341
+ const responses = await Promise.all(promises);
342
+ const validResponses = responses.filter((r): r is DiscussionProviderResponse => r !== null);
343
+
344
+ return {
345
+ round: {
346
+ roundNumber: roundNum,
347
+ responses: validResponses,
348
+ durationMs: Date.now() - roundStart,
349
+ },
350
+ failed,
351
+ };
352
+ }
353
+ }
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Voting Pattern Executor
3
+ *
4
+ * Each model evaluates options and votes with confidence scores.
5
+ *
6
+ * Flow:
7
+ * 1. Each provider evaluates all options in parallel
8
+ * 2. Votes are collected with confidence scores
9
+ * 3. Results are tallied (raw and confidence-weighted)
10
+ */
11
+
12
+ import type { DiscussionRound, DebateRole } from '@defai.digital/contracts';
13
+ import type {
14
+ PatternExecutor,
15
+ PatternExecutionContext,
16
+ PatternExecutionResult,
17
+ } from '../types.js';
18
+ import {
19
+ VOTING_EVALUATE,
20
+ interpolate,
21
+ formatVotingOptions,
22
+ getProviderSystemPrompt,
23
+ } from '../prompts/templates.js';
24
+
25
+ // Local type for discussion DiscussionProviderResponse (avoids conflict with provider/v1 DiscussionProviderResponse)
26
+ interface DiscussionProviderResponse {
27
+ provider: string;
28
+ content: string;
29
+ round: number;
30
+ role?: DebateRole | undefined;
31
+ confidence?: number | undefined;
32
+ vote?: string | undefined;
33
+ timestamp: string;
34
+ durationMs: number;
35
+ tokenCount?: number | undefined;
36
+ truncated?: boolean | undefined;
37
+ error?: string | undefined;
38
+ }
39
+
40
+ export class VotingPattern implements PatternExecutor {
41
+ readonly pattern = 'voting' as const;
42
+
43
+ async execute(context: PatternExecutionContext): Promise<PatternExecutionResult> {
44
+ const startTime = Date.now();
45
+ const { config, providerExecutor, availableProviders, abortSignal, onProgress } = context;
46
+
47
+ const rounds: DiscussionRound[] = [];
48
+ const participatingProviders = new Set<string>();
49
+ const failedProviders = new Set<string>();
50
+
51
+ // Filter to available providers
52
+ const providers = config.providers.filter(p => availableProviders.includes(p));
53
+
54
+ if (providers.length < config.minProviders) {
55
+ return {
56
+ rounds: [],
57
+ participatingProviders: [],
58
+ failedProviders: config.providers.filter(p => !availableProviders.includes(p)),
59
+ totalDurationMs: Date.now() - startTime,
60
+ success: false,
61
+ error: `Only ${providers.length} providers available, need ${config.minProviders}`,
62
+ };
63
+ }
64
+
65
+ // Extract options from prompt (look for numbered list or explicit options)
66
+ const options = this.extractOptions(config.prompt);
67
+
68
+ // Round 1: All providers vote in parallel
69
+ onProgress?.({
70
+ type: 'round_start',
71
+ round: 1,
72
+ message: 'Providers evaluating options and voting',
73
+ timestamp: new Date().toISOString(),
74
+ });
75
+
76
+ const votingRound = await this.executeVotingRound(
77
+ 1,
78
+ providers,
79
+ options,
80
+ config,
81
+ providerExecutor,
82
+ abortSignal,
83
+ onProgress
84
+ );
85
+
86
+ rounds.push(votingRound.round);
87
+ votingRound.succeeded.forEach(p => participatingProviders.add(p));
88
+ votingRound.failed.forEach(p => failedProviders.add(p));
89
+
90
+ onProgress?.({
91
+ type: 'round_complete',
92
+ round: 1,
93
+ message: `Collected ${votingRound.succeeded.length} votes`,
94
+ timestamp: new Date().toISOString(),
95
+ });
96
+
97
+ return {
98
+ rounds,
99
+ participatingProviders: Array.from(participatingProviders),
100
+ failedProviders: Array.from(failedProviders),
101
+ totalDurationMs: Date.now() - startTime,
102
+ success: participatingProviders.size >= config.minProviders,
103
+ };
104
+ }
105
+
106
+ private extractOptions(prompt: string): string[] {
107
+ // Try to extract numbered options (1. Option, 2. Option, etc.)
108
+ const numberedPattern = /^\d+\.\s*(.+)$/gm;
109
+ const matches = [...prompt.matchAll(numberedPattern)];
110
+
111
+ if (matches.length >= 2) {
112
+ return matches.map(m => m[1] ? m[1].trim() : '').filter(Boolean);
113
+ }
114
+
115
+ // Try to extract from "Options: A, B, C" format
116
+ const optionsMatch = /options?:\s*(.+?)(?:\n|$)/i.exec(prompt);
117
+ if (optionsMatch?.[1]) {
118
+ return optionsMatch[1].split(/[,;]/).map(o => o.trim()).filter(Boolean);
119
+ }
120
+
121
+ // Default: create Yes/No options
122
+ return ['Yes', 'No'];
123
+ }
124
+
125
+ private async executeVotingRound(
126
+ roundNum: number,
127
+ providers: string[],
128
+ options: string[],
129
+ config: PatternExecutionContext['config'],
130
+ providerExecutor: PatternExecutionContext['providerExecutor'],
131
+ abortSignal?: AbortSignal,
132
+ onProgress?: PatternExecutionContext['onProgress']
133
+ ): Promise<{ round: DiscussionRound; succeeded: string[]; failed: string[] }> {
134
+ const roundStart = Date.now();
135
+ const succeeded: string[] = [];
136
+ const failed: string[] = [];
137
+
138
+ const formattedOptions = formatVotingOptions(options);
139
+ const prompt = interpolate(VOTING_EVALUATE, {
140
+ topic: config.prompt,
141
+ options: formattedOptions,
142
+ context: config.context || '',
143
+ });
144
+
145
+ // Execute all votes in parallel
146
+ const promises = providers.map(async (providerId) => {
147
+ if (abortSignal?.aborted) return null;
148
+
149
+ onProgress?.({
150
+ type: 'provider_start',
151
+ round: roundNum,
152
+ provider: providerId,
153
+ timestamp: new Date().toISOString(),
154
+ });
155
+
156
+ const responseStart = Date.now();
157
+
158
+ try {
159
+ const result = await providerExecutor.execute({
160
+ providerId,
161
+ prompt,
162
+ systemPrompt: getProviderSystemPrompt(providerId),
163
+ temperature: config.temperature,
164
+ timeoutMs: config.providerTimeout,
165
+ abortSignal,
166
+ });
167
+
168
+ // Parse vote from response
169
+ const vote = result.success ? this.parseVote(result.content || '', options) : null;
170
+
171
+ const response: DiscussionProviderResponse = {
172
+ provider: providerId,
173
+ content: result.success ? result.content || '' : '',
174
+ round: roundNum,
175
+ timestamp: new Date().toISOString(),
176
+ durationMs: result.durationMs,
177
+ tokenCount: result.tokenCount,
178
+ confidence: vote?.confidence,
179
+ vote: vote?.choice,
180
+ error: result.success ? undefined : result.error,
181
+ };
182
+
183
+ if (result.success && vote) {
184
+ succeeded.push(providerId);
185
+ } else {
186
+ failed.push(providerId);
187
+ }
188
+
189
+ onProgress?.({
190
+ type: 'provider_complete',
191
+ round: roundNum,
192
+ provider: providerId,
193
+ message: vote ? `Voted: ${vote.choice} (${Math.round(vote.confidence * 100)}%)` : 'failed',
194
+ timestamp: new Date().toISOString(),
195
+ });
196
+
197
+ return response;
198
+
199
+ } catch (error) {
200
+ const errorMessage = error instanceof Error ? error.message : String(error);
201
+ failed.push(providerId);
202
+
203
+ return {
204
+ provider: providerId,
205
+ content: '',
206
+ round: roundNum,
207
+ timestamp: new Date().toISOString(),
208
+ durationMs: Date.now() - responseStart,
209
+ error: errorMessage,
210
+ } as DiscussionProviderResponse;
211
+ }
212
+ });
213
+
214
+ const responses = await Promise.all(promises);
215
+ const validResponses = responses.filter((r): r is DiscussionProviderResponse => r !== null);
216
+
217
+ return {
218
+ round: {
219
+ roundNumber: roundNum,
220
+ responses: validResponses,
221
+ durationMs: Date.now() - roundStart,
222
+ },
223
+ succeeded,
224
+ failed,
225
+ };
226
+ }
227
+
228
+ private parseVote(
229
+ content: string,
230
+ options: string[]
231
+ ): { choice: string; confidence: number; reasoning?: string | undefined } | null {
232
+ // Try to find "Your Vote: [option]" pattern
233
+ const voteMatch = /your vote:\s*\[?([^\]\n]+)\]?/i.exec(content);
234
+ const choice = voteMatch?.[1] ? voteMatch[1].trim() : null;
235
+
236
+ // Try to find confidence pattern
237
+ const confidenceMatch = /confidence:\s*\[?(\d+)%?\]?/i.exec(content);
238
+ const confidence = confidenceMatch?.[1] ? parseInt(confidenceMatch[1], 10) / 100 : 0.5;
239
+
240
+ // Try to find reasoning
241
+ const reasoningMatch = /reasoning:\s*(.+?)(?=\n\n|$)/is.exec(content);
242
+ const reasoning = reasoningMatch?.[1] ? reasoningMatch[1].trim() : undefined;
243
+
244
+ if (!choice) {
245
+ // Try to find which option is mentioned most prominently
246
+ for (const option of options) {
247
+ if (content.toLowerCase().includes(option.toLowerCase())) {
248
+ return { choice: option, confidence: Math.min(confidence, 0.7), reasoning };
249
+ }
250
+ }
251
+ return null;
252
+ }
253
+
254
+ // Normalize choice to match options
255
+ const normalizedChoice = options.find(
256
+ o => o.toLowerCase() === choice.toLowerCase() ||
257
+ choice.toLowerCase().includes(o.toLowerCase())
258
+ ) || choice;
259
+
260
+ return {
261
+ choice: normalizedChoice,
262
+ confidence: Math.max(0, Math.min(1, confidence)),
263
+ reasoning,
264
+ };
265
+ }
266
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Prompt Templates Module
3
+ *
4
+ * Re-exports all prompt templates and helper functions.
5
+ */
6
+
7
+ export {
8
+ // Provider prompts
9
+ getProviderSystemPrompt,
10
+
11
+ // Round-robin templates
12
+ ROUND_ROBIN_INITIAL,
13
+ ROUND_ROBIN_FOLLOWUP,
14
+
15
+ // Synthesis templates
16
+ SYNTHESIS_INITIAL,
17
+ SYNTHESIS_CROSS_DISCUSS,
18
+ SYNTHESIS_FINAL,
19
+
20
+ // Debate templates
21
+ DEBATE_PROPONENT,
22
+ DEBATE_OPPONENT,
23
+ DEBATE_REBUTTAL,
24
+ DEBATE_JUDGE,
25
+
26
+ // Critique templates
27
+ CRITIQUE_PROPOSAL,
28
+ CRITIQUE_REVIEW,
29
+ CRITIQUE_REVISION,
30
+
31
+ // Voting templates
32
+ VOTING_EVALUATE,
33
+ VOTING_TALLY,
34
+
35
+ // Helper functions
36
+ interpolate,
37
+ formatPreviousResponses,
38
+ formatVotingOptions,
39
+ formatVotes,
40
+ getRolePromptModifier,
41
+ } from './templates.js';