@defai.digital/discussion-domain 13.0.3 → 13.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.
- package/dist/budget-manager.d.ts +79 -0
- package/dist/budget-manager.d.ts.map +1 -0
- package/dist/budget-manager.js +155 -0
- package/dist/budget-manager.js.map +1 -0
- package/dist/confidence-extractor.d.ts +60 -0
- package/dist/confidence-extractor.d.ts.map +1 -0
- package/dist/confidence-extractor.js +251 -0
- package/dist/confidence-extractor.js.map +1 -0
- package/dist/consensus/synthesis.d.ts.map +1 -1
- package/dist/consensus/synthesis.js +2 -0
- package/dist/consensus/synthesis.js.map +1 -1
- package/dist/consensus/voting.d.ts +3 -0
- package/dist/consensus/voting.d.ts.map +1 -1
- package/dist/consensus/voting.js +15 -4
- package/dist/consensus/voting.js.map +1 -1
- package/dist/context-tracker.d.ts +77 -0
- package/dist/context-tracker.d.ts.map +1 -0
- package/dist/context-tracker.js +177 -0
- package/dist/context-tracker.js.map +1 -0
- package/dist/cost-tracker.d.ts +123 -0
- package/dist/cost-tracker.d.ts.map +1 -0
- package/dist/cost-tracker.js +195 -0
- package/dist/cost-tracker.js.map +1 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +9 -0
- package/dist/executor.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/participant-resolver.d.ts +111 -0
- package/dist/participant-resolver.d.ts.map +1 -0
- package/dist/participant-resolver.js +160 -0
- package/dist/participant-resolver.js.map +1 -0
- package/dist/patterns/round-robin.d.ts +3 -0
- package/dist/patterns/round-robin.d.ts.map +1 -1
- package/dist/patterns/round-robin.js +41 -2
- package/dist/patterns/round-robin.js.map +1 -1
- package/dist/patterns/synthesis.d.ts +3 -0
- package/dist/patterns/synthesis.d.ts.map +1 -1
- package/dist/patterns/synthesis.js +77 -3
- package/dist/patterns/synthesis.js.map +1 -1
- package/dist/prompts/templates.d.ts +1 -0
- package/dist/prompts/templates.d.ts.map +1 -1
- package/dist/prompts/templates.js +3 -1
- package/dist/prompts/templates.js.map +1 -1
- package/dist/provider-bridge.d.ts.map +1 -1
- package/dist/provider-bridge.js +31 -30
- package/dist/provider-bridge.js.map +1 -1
- package/dist/recursive-executor.d.ts +77 -0
- package/dist/recursive-executor.d.ts.map +1 -0
- package/dist/recursive-executor.js +344 -0
- package/dist/recursive-executor.js.map +1 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/budget-manager.ts +272 -0
- package/src/confidence-extractor.ts +321 -0
- package/src/consensus/synthesis.ts +2 -0
- package/src/consensus/voting.ts +22 -6
- package/src/context-tracker.ts +307 -0
- package/src/cost-tracker.ts +362 -0
- package/src/executor.ts +9 -0
- package/src/index.ts +72 -0
- package/src/participant-resolver.ts +297 -0
- package/src/patterns/round-robin.ts +48 -2
- package/src/patterns/synthesis.ts +89 -3
- package/src/prompts/templates.ts +4 -2
- package/src/provider-bridge.ts +31 -28
- package/src/recursive-executor.ts +500 -0
- package/src/types.ts +120 -0
package/src/provider-bridge.ts
CHANGED
|
@@ -238,43 +238,46 @@ export function createProviderBridge(
|
|
|
238
238
|
|
|
239
239
|
async getAvailableProviders(): Promise<string[]> {
|
|
240
240
|
const allProviders = registry.getAll();
|
|
241
|
-
const
|
|
241
|
+
const now = Date.now();
|
|
242
|
+
|
|
243
|
+
// Separate cached vs uncached providers
|
|
244
|
+
const cached: string[] = [];
|
|
245
|
+
const toCheck: LLMProviderLike[] = [];
|
|
242
246
|
|
|
243
|
-
// Check each provider's availability
|
|
244
247
|
for (const provider of allProviders) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
248
|
+
const entry = healthCache.get(provider.providerId);
|
|
249
|
+
if (entry && now - entry.timestamp < healthCheckCacheMs) {
|
|
250
|
+
if (entry.healthy) cached.push(provider.providerId);
|
|
251
|
+
} else if (performHealthChecks) {
|
|
252
|
+
toCheck.push(provider);
|
|
253
|
+
} else {
|
|
254
|
+
cached.push(provider.providerId); // No health check, assume available
|
|
252
255
|
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check uncached providers in parallel
|
|
259
|
+
if (toCheck.length === 0) return cached;
|
|
253
260
|
|
|
254
|
-
|
|
255
|
-
|
|
261
|
+
const results = await Promise.allSettled(
|
|
262
|
+
toCheck.map(async (provider) => {
|
|
256
263
|
try {
|
|
257
264
|
const health = await provider.checkHealth();
|
|
258
|
-
healthCache.set(provider.providerId, {
|
|
259
|
-
|
|
260
|
-
timestamp: Date.now(),
|
|
261
|
-
});
|
|
262
|
-
if (health.healthy) {
|
|
263
|
-
available.push(provider.providerId);
|
|
264
|
-
}
|
|
265
|
+
healthCache.set(provider.providerId, { healthy: health.healthy, timestamp: Date.now() });
|
|
266
|
+
return health.healthy ? provider.providerId : null;
|
|
265
267
|
} catch {
|
|
266
|
-
healthCache.set(provider.providerId, {
|
|
267
|
-
|
|
268
|
-
timestamp: Date.now(),
|
|
269
|
-
});
|
|
268
|
+
healthCache.set(provider.providerId, { healthy: false, timestamp: Date.now() });
|
|
269
|
+
return null;
|
|
270
270
|
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
271
|
+
})
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Collect successful health checks
|
|
275
|
+
const checked = results
|
|
276
|
+
.filter((r): r is PromiseFulfilledResult<string | null> => r.status === 'fulfilled')
|
|
277
|
+
.map((r) => r.value)
|
|
278
|
+
.filter((id): id is string => id !== null);
|
|
276
279
|
|
|
277
|
-
return
|
|
280
|
+
return [...cached, ...checked];
|
|
278
281
|
},
|
|
279
282
|
};
|
|
280
283
|
}
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursive Discussion Executor
|
|
3
|
+
*
|
|
4
|
+
* Extends the base discussion executor with support for recursive sub-discussions.
|
|
5
|
+
* Providers can spawn sub-discussions during their response.
|
|
6
|
+
*
|
|
7
|
+
* Invariants:
|
|
8
|
+
* - INV-DISC-600: Depth never exceeds maxDepth
|
|
9
|
+
* - INV-DISC-601: No circular discussions
|
|
10
|
+
* - INV-DISC-610: Child timeout ≤ parent remaining budget
|
|
11
|
+
* - INV-DISC-620: Total calls ≤ maxTotalCalls
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_PROVIDERS,
|
|
16
|
+
DiscussionErrorCodes,
|
|
17
|
+
createFailedDiscussionResult,
|
|
18
|
+
type DiscussStepConfig,
|
|
19
|
+
type DiscussionResult,
|
|
20
|
+
type DiscussionRequest,
|
|
21
|
+
type DiscussionContext,
|
|
22
|
+
type SubDiscussionResult,
|
|
23
|
+
type RecursiveConfig,
|
|
24
|
+
type TimeoutConfig,
|
|
25
|
+
type CostControlConfig,
|
|
26
|
+
DEFAULT_DISCUSSION_DEPTH,
|
|
27
|
+
DEFAULT_TOTAL_BUDGET_MS,
|
|
28
|
+
DEFAULT_MAX_TOTAL_CALLS,
|
|
29
|
+
MIN_SYNTHESIS_TIME_MS,
|
|
30
|
+
} from '@defai.digital/contracts';
|
|
31
|
+
|
|
32
|
+
import type {
|
|
33
|
+
DiscussionProviderExecutor,
|
|
34
|
+
RecursiveDiscussionExecutorOptions,
|
|
35
|
+
DiscussionProgressEvent,
|
|
36
|
+
RecursivePatternExecutionResult,
|
|
37
|
+
} from './types.js';
|
|
38
|
+
import { getPatternExecutor } from './patterns/index.js';
|
|
39
|
+
import { getConsensusExecutor } from './consensus/index.js';
|
|
40
|
+
import { createContextTracker } from './context-tracker.js';
|
|
41
|
+
import { createBudgetManager } from './budget-manager.js';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extended discussion result with recursive info
|
|
45
|
+
*/
|
|
46
|
+
export interface RecursiveDiscussionResult extends DiscussionResult {
|
|
47
|
+
/** Sub-discussions that were spawned */
|
|
48
|
+
subDiscussions?: SubDiscussionResult[];
|
|
49
|
+
|
|
50
|
+
/** Total provider calls across all levels */
|
|
51
|
+
totalProviderCalls?: number;
|
|
52
|
+
|
|
53
|
+
/** Maximum depth reached */
|
|
54
|
+
maxDepthReached?: number;
|
|
55
|
+
|
|
56
|
+
/** Discussion context */
|
|
57
|
+
context?: DiscussionContext;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Recursive discussion executor class.
|
|
62
|
+
*
|
|
63
|
+
* Orchestrates multi-model discussions with support for nested sub-discussions.
|
|
64
|
+
*/
|
|
65
|
+
export class RecursiveDiscussionExecutor {
|
|
66
|
+
private readonly providerExecutor: DiscussionProviderExecutor;
|
|
67
|
+
private readonly defaultTimeoutMs: number;
|
|
68
|
+
private readonly checkProviderHealth: boolean;
|
|
69
|
+
private readonly traceId: string | undefined;
|
|
70
|
+
private readonly recursiveConfig: RecursiveConfig;
|
|
71
|
+
private readonly timeoutConfig: TimeoutConfig;
|
|
72
|
+
private readonly costConfig: CostControlConfig;
|
|
73
|
+
private readonly parentContext: DiscussionContext | undefined;
|
|
74
|
+
private readonly onSubDiscussionSpawn: ((context: DiscussionContext, topic: string) => void) | undefined;
|
|
75
|
+
private readonly onSubDiscussionComplete: ((result: SubDiscussionResult) => void) | undefined;
|
|
76
|
+
|
|
77
|
+
constructor(options: RecursiveDiscussionExecutorOptions) {
|
|
78
|
+
this.providerExecutor = options.providerExecutor;
|
|
79
|
+
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 60000;
|
|
80
|
+
this.checkProviderHealth = options.checkProviderHealth ?? true;
|
|
81
|
+
this.traceId = options.traceId;
|
|
82
|
+
this.parentContext = options.parentContext;
|
|
83
|
+
this.onSubDiscussionSpawn = options.onSubDiscussionSpawn;
|
|
84
|
+
this.onSubDiscussionComplete = options.onSubDiscussionComplete;
|
|
85
|
+
|
|
86
|
+
// Initialize recursive config with defaults
|
|
87
|
+
this.recursiveConfig = {
|
|
88
|
+
enabled: options.recursive?.enabled ?? false,
|
|
89
|
+
maxDepth: options.recursive?.maxDepth ?? DEFAULT_DISCUSSION_DEPTH,
|
|
90
|
+
allowedProviders: options.recursive?.allowedProviders,
|
|
91
|
+
allowSubDiscussions: options.recursive?.allowSubDiscussions ?? true,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Initialize timeout config
|
|
95
|
+
this.timeoutConfig = {
|
|
96
|
+
strategy: options.timeout?.strategy ?? 'cascade',
|
|
97
|
+
totalBudgetMs: options.timeout?.totalBudgetMs ?? DEFAULT_TOTAL_BUDGET_MS,
|
|
98
|
+
minSynthesisMs: options.timeout?.minSynthesisMs ?? MIN_SYNTHESIS_TIME_MS,
|
|
99
|
+
levelTimeouts: options.timeout?.levelTimeouts,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Initialize cost config
|
|
103
|
+
this.costConfig = {
|
|
104
|
+
maxTotalCalls: options.cost?.maxTotalCalls ?? DEFAULT_MAX_TOTAL_CALLS,
|
|
105
|
+
budgetUsd: options.cost?.budgetUsd,
|
|
106
|
+
cascadingConfidence: {
|
|
107
|
+
enabled: options.cost?.cascadingConfidence?.enabled ?? true,
|
|
108
|
+
threshold: options.cost?.cascadingConfidence?.threshold ?? 0.9,
|
|
109
|
+
minProviders: options.cost?.cascadingConfidence?.minProviders ?? 2,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Execute a recursive discussion from a DiscussionRequest
|
|
116
|
+
*/
|
|
117
|
+
async executeRequest(
|
|
118
|
+
request: DiscussionRequest,
|
|
119
|
+
options?: {
|
|
120
|
+
abortSignal?: AbortSignal;
|
|
121
|
+
onProgress?: (event: DiscussionProgressEvent) => void;
|
|
122
|
+
}
|
|
123
|
+
): Promise<RecursiveDiscussionResult> {
|
|
124
|
+
// Convert request to step config
|
|
125
|
+
const config: DiscussStepConfig = {
|
|
126
|
+
pattern: request.pattern || 'synthesis',
|
|
127
|
+
rounds: request.rounds || 2,
|
|
128
|
+
providers: request.providers || [...DEFAULT_PROVIDERS],
|
|
129
|
+
prompt: request.topic,
|
|
130
|
+
consensus: {
|
|
131
|
+
method: request.consensusMethod || 'synthesis',
|
|
132
|
+
threshold: 0.5,
|
|
133
|
+
synthesizer: 'claude',
|
|
134
|
+
includeDissent: true,
|
|
135
|
+
},
|
|
136
|
+
context: request.context,
|
|
137
|
+
verbose: request.verbose ?? false,
|
|
138
|
+
providerTimeout: this.defaultTimeoutMs,
|
|
139
|
+
continueOnProviderFailure: true,
|
|
140
|
+
minProviders: 2,
|
|
141
|
+
temperature: 0.7,
|
|
142
|
+
agentWeightMultiplier: 1.5,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return this.execute(config, options);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Execute a recursive discussion with full configuration
|
|
150
|
+
*/
|
|
151
|
+
async execute(
|
|
152
|
+
config: DiscussStepConfig,
|
|
153
|
+
options?: {
|
|
154
|
+
abortSignal?: AbortSignal;
|
|
155
|
+
onProgress?: (event: DiscussionProgressEvent) => void;
|
|
156
|
+
}
|
|
157
|
+
): Promise<RecursiveDiscussionResult> {
|
|
158
|
+
const startedAt = new Date().toISOString();
|
|
159
|
+
const discussionId = crypto.randomUUID();
|
|
160
|
+
const { abortSignal, onProgress } = options || {};
|
|
161
|
+
|
|
162
|
+
// Check for early abort
|
|
163
|
+
if (abortSignal?.aborted) {
|
|
164
|
+
return createFailedDiscussionResult(
|
|
165
|
+
config.pattern,
|
|
166
|
+
config.prompt,
|
|
167
|
+
DiscussionErrorCodes.INVALID_CONFIG,
|
|
168
|
+
'Discussion aborted before starting',
|
|
169
|
+
startedAt
|
|
170
|
+
) as RecursiveDiscussionResult;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Create context tracker
|
|
174
|
+
const contextTracker = createContextTracker(
|
|
175
|
+
discussionId,
|
|
176
|
+
{
|
|
177
|
+
recursive: this.recursiveConfig,
|
|
178
|
+
timeout: this.timeoutConfig,
|
|
179
|
+
cost: this.costConfig,
|
|
180
|
+
},
|
|
181
|
+
this.parentContext
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Create budget manager
|
|
185
|
+
const budgetManager = createBudgetManager(
|
|
186
|
+
this.timeoutConfig,
|
|
187
|
+
this.recursiveConfig.maxDepth
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Check provider availability
|
|
191
|
+
let availableProviders: string[];
|
|
192
|
+
try {
|
|
193
|
+
availableProviders = await this.checkProviders(config.providers);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
196
|
+
return createFailedDiscussionResult(
|
|
197
|
+
config.pattern,
|
|
198
|
+
config.prompt,
|
|
199
|
+
DiscussionErrorCodes.ALL_PROVIDERS_FAILED,
|
|
200
|
+
`Provider health check failed: ${errorMessage}`,
|
|
201
|
+
startedAt
|
|
202
|
+
) as RecursiveDiscussionResult;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Filter by allowed providers if recursive
|
|
206
|
+
if (this.recursiveConfig.enabled && this.recursiveConfig.allowedProviders) {
|
|
207
|
+
availableProviders = availableProviders.filter(
|
|
208
|
+
p => this.recursiveConfig.allowedProviders!.includes(p)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check minimum providers
|
|
213
|
+
if (availableProviders.length < config.minProviders) {
|
|
214
|
+
return createFailedDiscussionResult(
|
|
215
|
+
config.pattern,
|
|
216
|
+
config.prompt,
|
|
217
|
+
DiscussionErrorCodes.INSUFFICIENT_PROVIDERS,
|
|
218
|
+
`Only ${availableProviders.length} providers available, need ${config.minProviders}`,
|
|
219
|
+
startedAt
|
|
220
|
+
) as RecursiveDiscussionResult;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Track sub-discussions
|
|
224
|
+
const subDiscussions: SubDiscussionResult[] = [];
|
|
225
|
+
let totalProviderCalls = 0;
|
|
226
|
+
let maxDepthReached = contextTracker.getContext().depth;
|
|
227
|
+
|
|
228
|
+
// Create sub-discussion spawner
|
|
229
|
+
const spawnSubDiscussion = async (
|
|
230
|
+
topic: string,
|
|
231
|
+
providers?: string[]
|
|
232
|
+
): Promise<SubDiscussionResult | null> => {
|
|
233
|
+
// Check if we can spawn
|
|
234
|
+
const check = contextTracker.canSpawnSubDiscussion();
|
|
235
|
+
if (!check.allowed) {
|
|
236
|
+
onProgress?.({
|
|
237
|
+
type: 'provider_complete',
|
|
238
|
+
message: `Sub-discussion blocked: ${check.reason}`,
|
|
239
|
+
timestamp: new Date().toISOString(),
|
|
240
|
+
});
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Create child context
|
|
245
|
+
const childId = crypto.randomUUID();
|
|
246
|
+
const childContext = contextTracker.createChildContext(childId);
|
|
247
|
+
|
|
248
|
+
// Notify spawn
|
|
249
|
+
this.onSubDiscussionSpawn?.(childContext, topic);
|
|
250
|
+
|
|
251
|
+
onProgress?.({
|
|
252
|
+
type: 'round_start',
|
|
253
|
+
message: `Spawning sub-discussion at depth ${childContext.depth}: ${topic.slice(0, 50)}...`,
|
|
254
|
+
timestamp: new Date().toISOString(),
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Create child executor
|
|
258
|
+
const childExecutor = new RecursiveDiscussionExecutor({
|
|
259
|
+
providerExecutor: this.providerExecutor,
|
|
260
|
+
defaultTimeoutMs: budgetManager.getProviderTimeout(childContext.depth),
|
|
261
|
+
checkProviderHealth: false, // Already checked parent providers
|
|
262
|
+
traceId: this.traceId,
|
|
263
|
+
recursive: {
|
|
264
|
+
...this.recursiveConfig,
|
|
265
|
+
maxDepth: this.recursiveConfig.maxDepth, // Keep same max depth
|
|
266
|
+
},
|
|
267
|
+
timeout: {
|
|
268
|
+
...this.timeoutConfig,
|
|
269
|
+
totalBudgetMs: childContext.remainingBudgetMs,
|
|
270
|
+
},
|
|
271
|
+
cost: this.costConfig,
|
|
272
|
+
parentContext: childContext,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Execute sub-discussion
|
|
276
|
+
const subStart = Date.now();
|
|
277
|
+
const subResult = await childExecutor.execute(
|
|
278
|
+
{
|
|
279
|
+
pattern: 'synthesis',
|
|
280
|
+
rounds: 1, // Sub-discussions are quick
|
|
281
|
+
providers: providers || availableProviders.slice(0, 3),
|
|
282
|
+
prompt: topic,
|
|
283
|
+
consensus: { method: 'synthesis', synthesizer: 'claude', threshold: 0.5, includeDissent: false },
|
|
284
|
+
providerTimeout: budgetManager.getProviderTimeout(childContext.depth),
|
|
285
|
+
continueOnProviderFailure: true,
|
|
286
|
+
minProviders: 2,
|
|
287
|
+
temperature: 0.7,
|
|
288
|
+
verbose: false,
|
|
289
|
+
agentWeightMultiplier: 1.5,
|
|
290
|
+
},
|
|
291
|
+
abortSignal ? { abortSignal } : {}
|
|
292
|
+
);
|
|
293
|
+
const subDuration = Date.now() - subStart;
|
|
294
|
+
|
|
295
|
+
// Record usage
|
|
296
|
+
contextTracker.recordCalls(subResult.participatingProviders.length);
|
|
297
|
+
contextTracker.recordElapsed(subDuration);
|
|
298
|
+
budgetManager.recordUsage(childContext.depth, subDuration);
|
|
299
|
+
|
|
300
|
+
// Update tracking
|
|
301
|
+
totalProviderCalls += subResult.totalProviderCalls ?? subResult.participatingProviders.length;
|
|
302
|
+
maxDepthReached = Math.max(maxDepthReached, childContext.depth);
|
|
303
|
+
|
|
304
|
+
// Create sub-discussion result
|
|
305
|
+
const subDiscussionResult: SubDiscussionResult = {
|
|
306
|
+
discussionId: childId,
|
|
307
|
+
topic,
|
|
308
|
+
participatingProviders: subResult.participatingProviders,
|
|
309
|
+
synthesis: subResult.synthesis,
|
|
310
|
+
durationMs: subDuration,
|
|
311
|
+
depth: childContext.depth,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
subDiscussions.push(subDiscussionResult);
|
|
315
|
+
this.onSubDiscussionComplete?.(subDiscussionResult);
|
|
316
|
+
|
|
317
|
+
onProgress?.({
|
|
318
|
+
type: 'round_complete',
|
|
319
|
+
message: `Sub-discussion completed at depth ${childContext.depth}`,
|
|
320
|
+
timestamp: new Date().toISOString(),
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return subDiscussionResult;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Get pattern executor
|
|
327
|
+
const patternExecutor = getPatternExecutor(config.pattern);
|
|
328
|
+
|
|
329
|
+
// Build execution context with recursion support
|
|
330
|
+
const patternContext = {
|
|
331
|
+
config: {
|
|
332
|
+
...config,
|
|
333
|
+
providerTimeout: budgetManager.getProviderTimeout(contextTracker.getContext().depth),
|
|
334
|
+
},
|
|
335
|
+
providerExecutor: this.providerExecutor,
|
|
336
|
+
availableProviders,
|
|
337
|
+
abortSignal,
|
|
338
|
+
traceId: this.traceId,
|
|
339
|
+
onProgress,
|
|
340
|
+
// Cascading confidence for early exit
|
|
341
|
+
cascadingConfidence: this.costConfig.cascadingConfidence ? {
|
|
342
|
+
enabled: this.costConfig.cascadingConfidence.enabled ?? true,
|
|
343
|
+
threshold: this.costConfig.cascadingConfidence.threshold ?? 0.9,
|
|
344
|
+
minProviders: this.costConfig.cascadingConfidence.minProviders ?? 2,
|
|
345
|
+
} : undefined,
|
|
346
|
+
// Recursive extensions
|
|
347
|
+
discussionContext: contextTracker.getContext(),
|
|
348
|
+
allowSubDiscussions: this.recursiveConfig.enabled && this.recursiveConfig.allowSubDiscussions,
|
|
349
|
+
spawnSubDiscussion: this.recursiveConfig.enabled ? spawnSubDiscussion : undefined,
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// Execute pattern
|
|
353
|
+
const patternResult = await patternExecutor.execute(patternContext) as RecursivePatternExecutionResult;
|
|
354
|
+
|
|
355
|
+
// Record calls from pattern execution
|
|
356
|
+
totalProviderCalls += patternResult.participatingProviders.length;
|
|
357
|
+
contextTracker.recordCalls(patternResult.participatingProviders.length);
|
|
358
|
+
|
|
359
|
+
if (!patternResult.success) {
|
|
360
|
+
return createFailedDiscussionResult(
|
|
361
|
+
config.pattern,
|
|
362
|
+
config.prompt,
|
|
363
|
+
DiscussionErrorCodes.ALL_PROVIDERS_FAILED,
|
|
364
|
+
patternResult.error || 'Pattern execution failed',
|
|
365
|
+
startedAt
|
|
366
|
+
) as RecursiveDiscussionResult;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Execute consensus mechanism
|
|
370
|
+
const consensusExecutor = getConsensusExecutor(config.consensus.method);
|
|
371
|
+
|
|
372
|
+
const consensusResult = await consensusExecutor.execute({
|
|
373
|
+
topic: config.prompt,
|
|
374
|
+
rounds: patternResult.rounds,
|
|
375
|
+
participatingProviders: patternResult.participatingProviders,
|
|
376
|
+
config: config.consensus,
|
|
377
|
+
providerExecutor: this.providerExecutor,
|
|
378
|
+
abortSignal,
|
|
379
|
+
onProgress,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Record synthesis call
|
|
383
|
+
totalProviderCalls += 1;
|
|
384
|
+
contextTracker.recordCalls(1);
|
|
385
|
+
|
|
386
|
+
if (!consensusResult.success) {
|
|
387
|
+
return createFailedDiscussionResult(
|
|
388
|
+
config.pattern,
|
|
389
|
+
config.prompt,
|
|
390
|
+
DiscussionErrorCodes.CONSENSUS_FAILED,
|
|
391
|
+
consensusResult.error || 'Consensus failed',
|
|
392
|
+
startedAt
|
|
393
|
+
) as RecursiveDiscussionResult;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Build final result
|
|
397
|
+
const result: RecursiveDiscussionResult = {
|
|
398
|
+
success: true,
|
|
399
|
+
pattern: config.pattern,
|
|
400
|
+
topic: config.prompt,
|
|
401
|
+
participatingProviders: patternResult.participatingProviders,
|
|
402
|
+
failedProviders: patternResult.failedProviders,
|
|
403
|
+
rounds: patternResult.rounds,
|
|
404
|
+
synthesis: consensusResult.synthesis,
|
|
405
|
+
consensus: consensusResult.consensus,
|
|
406
|
+
votingResults: consensusResult.votingResults,
|
|
407
|
+
totalDurationMs: patternResult.totalDurationMs + consensusResult.durationMs,
|
|
408
|
+
metadata: {
|
|
409
|
+
startedAt,
|
|
410
|
+
completedAt: new Date().toISOString(),
|
|
411
|
+
traceId: this.traceId,
|
|
412
|
+
// Include early exit info if triggered
|
|
413
|
+
...(patternResult.earlyExit?.triggered ? {
|
|
414
|
+
earlyExit: patternResult.earlyExit,
|
|
415
|
+
} : {}),
|
|
416
|
+
},
|
|
417
|
+
// Recursive extensions
|
|
418
|
+
...(subDiscussions.length > 0 ? { subDiscussions } : {}),
|
|
419
|
+
totalProviderCalls,
|
|
420
|
+
maxDepthReached,
|
|
421
|
+
context: contextTracker.getContext(),
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Check provider availability
|
|
429
|
+
*/
|
|
430
|
+
private async checkProviders(providers: string[]): Promise<string[]> {
|
|
431
|
+
if (!this.checkProviderHealth) {
|
|
432
|
+
return providers;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const available: string[] = [];
|
|
436
|
+
|
|
437
|
+
await Promise.all(
|
|
438
|
+
providers.map(async (providerId) => {
|
|
439
|
+
try {
|
|
440
|
+
const isAvailable = await this.providerExecutor.isAvailable(providerId);
|
|
441
|
+
if (isAvailable) {
|
|
442
|
+
available.push(providerId);
|
|
443
|
+
}
|
|
444
|
+
} catch {
|
|
445
|
+
// Provider check failed, don't include
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
return available;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Quick recursive synthesis discussion
|
|
455
|
+
*/
|
|
456
|
+
async quickRecursiveSynthesis(
|
|
457
|
+
topic: string,
|
|
458
|
+
options?: {
|
|
459
|
+
providers?: string[];
|
|
460
|
+
maxDepth?: number;
|
|
461
|
+
abortSignal?: AbortSignal;
|
|
462
|
+
onProgress?: (event: DiscussionProgressEvent) => void;
|
|
463
|
+
}
|
|
464
|
+
): Promise<RecursiveDiscussionResult> {
|
|
465
|
+
// Temporarily enable recursion for this call
|
|
466
|
+
const originalEnabled = this.recursiveConfig.enabled;
|
|
467
|
+
this.recursiveConfig.enabled = true;
|
|
468
|
+
|
|
469
|
+
if (options?.maxDepth) {
|
|
470
|
+
this.recursiveConfig.maxDepth = options.maxDepth;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
try {
|
|
474
|
+
return await this.executeRequest(
|
|
475
|
+
{
|
|
476
|
+
topic,
|
|
477
|
+
pattern: 'synthesis',
|
|
478
|
+
providers: options?.providers,
|
|
479
|
+
rounds: 2,
|
|
480
|
+
},
|
|
481
|
+
options
|
|
482
|
+
);
|
|
483
|
+
} finally {
|
|
484
|
+
this.recursiveConfig.enabled = originalEnabled;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Create a recursive discussion executor with default options
|
|
491
|
+
*/
|
|
492
|
+
export function createRecursiveDiscussionExecutor(
|
|
493
|
+
providerExecutor: DiscussionProviderExecutor,
|
|
494
|
+
options?: Partial<Omit<RecursiveDiscussionExecutorOptions, 'providerExecutor'>>
|
|
495
|
+
): RecursiveDiscussionExecutor {
|
|
496
|
+
return new RecursiveDiscussionExecutor({
|
|
497
|
+
providerExecutor,
|
|
498
|
+
...options,
|
|
499
|
+
});
|
|
500
|
+
}
|