@goodandready/dsh-moa 0.2.6 → 0.2.7
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/lib/index.js +45 -46
- package/lib/moa-runner.js +62 -100
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import os from 'node:os'
|
|
|
16
16
|
import {
|
|
17
17
|
parseMoACommand,
|
|
18
18
|
runMoAPipeline,
|
|
19
|
-
|
|
19
|
+
streamMoATurn,
|
|
20
20
|
} from './moa-runner.js'
|
|
21
21
|
|
|
22
22
|
import {
|
|
@@ -450,44 +450,9 @@ export function apply(ctx, config) {
|
|
|
450
450
|
})
|
|
451
451
|
}, 'dsh-moa: run pipeline route')
|
|
452
452
|
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const adapter = new MoaRunnerAdapter((options) => {
|
|
457
|
-
const msgs = options.messages || []
|
|
458
|
-
const lastUser = [...msgs].reverse().find((m) => m && m.role === 'user')
|
|
459
|
-
const userText = typeof lastUser?.content === 'string'
|
|
460
|
-
? lastUser.content
|
|
461
|
-
: (Array.isArray(lastUser?.content)
|
|
462
|
-
? lastUser.content.filter((p) => p.type === 'text').map((p) => p.text).join('\n')
|
|
463
|
-
: '')
|
|
464
|
-
|
|
465
|
-
const cfg = live()
|
|
466
|
-
const parsed = parseMoACommand(userText, cfg.presets || []) || { prompt: userText, presetName: 'default' }
|
|
467
|
-
const targetPreset = (cfg.presets || []).find((p) => p.name === parsed.presetName) || cfg.presets?.[0]
|
|
468
|
-
|
|
469
|
-
const extraCtx = options.signal ? moaPendingContexts.get(options.signal) : null
|
|
470
|
-
const cwd = options.cwd || extraCtx?.cwd || process.cwd()
|
|
471
|
-
|
|
472
|
-
return {
|
|
473
|
-
targetPreset,
|
|
474
|
-
userPrompt: parsed.prompt || userText,
|
|
475
|
-
messages: msgs.slice(0, -1),
|
|
476
|
-
callLlm,
|
|
477
|
-
cwd,
|
|
478
|
-
}
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
const dispose = ctx.llm.registerAdapter(['moa-runner'], adapter)
|
|
482
|
-
return () => {
|
|
483
|
-
if (typeof dispose === 'function') dispose()
|
|
484
|
-
}
|
|
485
|
-
}, 'dsh-moa: llm adapter')
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Session turn interceptor: flag MoA turns and route to moa-runner adapter
|
|
489
|
-
const moaPendingSignals = new WeakSet()
|
|
490
|
-
const moaPendingContexts = new WeakMap()
|
|
453
|
+
// Session turn interceptor: route turn to real aggregator model & stream MoA synthesis
|
|
454
|
+
const moaPendingTurns = new WeakMap()
|
|
455
|
+
const moaSessionTurns = new Map()
|
|
491
456
|
|
|
492
457
|
ctx.on('agent/pre-step', async (event, next) => {
|
|
493
458
|
const messages = event.messages || []
|
|
@@ -501,10 +466,26 @@ export function apply(ctx, config) {
|
|
|
501
466
|
if (userText.startsWith('/moa')) {
|
|
502
467
|
const cfg = live()
|
|
503
468
|
if (cfg.enabled !== false) {
|
|
469
|
+
const parsed = parseMoACommand(userText, cfg.presets || []) || { prompt: userText, presetName: cfg.default_preset || 'default' }
|
|
470
|
+
const targetPreset = (cfg.presets || []).find((p) => p.name === parsed.presetName) || cfg.presets?.[0]
|
|
471
|
+
const aggProvider = targetPreset?.aggregator?.provider || 'codex'
|
|
472
|
+
const aggModel = targetPreset?.aggregator?.model || 'gpt-5.6-sol'
|
|
473
|
+
const sessCwd = event.cwd || event.session?.cwd || (event.session?.path ? path.dirname(event.session.path) : undefined) || process.cwd()
|
|
474
|
+
|
|
475
|
+
const turnContext = {
|
|
476
|
+
targetPreset,
|
|
477
|
+
userPrompt: parsed.prompt || userText,
|
|
478
|
+
messages: messages.slice(0, -1),
|
|
479
|
+
cwd: sessCwd,
|
|
480
|
+
aggregator: { provider: aggProvider, model: aggModel },
|
|
481
|
+
callLlm,
|
|
482
|
+
}
|
|
483
|
+
|
|
504
484
|
if (event.signal) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
485
|
+
moaPendingTurns.set(event.signal, turnContext)
|
|
486
|
+
}
|
|
487
|
+
if (event.session?.id) {
|
|
488
|
+
moaSessionTurns.set(event.session.id, turnContext)
|
|
508
489
|
}
|
|
509
490
|
}
|
|
510
491
|
}
|
|
@@ -514,14 +495,32 @@ export function apply(ctx, config) {
|
|
|
514
495
|
|
|
515
496
|
ctx.on('agent/request', async (payload, next) => {
|
|
516
497
|
const resolved = await next()
|
|
517
|
-
|
|
518
|
-
|
|
498
|
+
const turnContext = (payload.signal ? moaPendingTurns.get(payload.signal) : null)
|
|
499
|
+
|| (payload.session?.id ? moaSessionTurns.get(payload.session.id) : null)
|
|
500
|
+
|
|
501
|
+
if (turnContext) {
|
|
519
502
|
return {
|
|
520
503
|
...resolved,
|
|
521
|
-
provider:
|
|
522
|
-
model:
|
|
504
|
+
provider: turnContext.aggregator.provider,
|
|
505
|
+
model: turnContext.aggregator.model,
|
|
523
506
|
}
|
|
524
507
|
}
|
|
525
508
|
return resolved
|
|
526
509
|
})
|
|
510
|
+
|
|
511
|
+
ctx.effect(() => {
|
|
512
|
+
return ctx.on('llm/stream', (options, next) => {
|
|
513
|
+
const turnContext = (options.signal ? moaPendingTurns.get(options.signal) : null)
|
|
514
|
+
|| (options.session?.id ? moaSessionTurns.get(options.session.id) : null)
|
|
515
|
+
|
|
516
|
+
if (turnContext) {
|
|
517
|
+
if (options.signal) moaPendingTurns.delete(options.signal)
|
|
518
|
+
if (options.session?.id) moaSessionTurns.delete(options.session.id)
|
|
519
|
+
|
|
520
|
+
return streamMoATurn(turnContext, options)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return next(options)
|
|
524
|
+
})
|
|
525
|
+
}, 'dsh-moa: llm stream interceptor')
|
|
527
526
|
}
|
package/lib/moa-runner.js
CHANGED
|
@@ -231,7 +231,7 @@ export function parseMoACommand(text, presets = []) {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
const matchPreset = /^--preset
|
|
234
|
+
const matchPreset = /^--preset(?:=|\s+)([a-zA-Z0-9_-]+)\s*(.*)/s.exec(remainder)
|
|
235
235
|
if (matchPreset) {
|
|
236
236
|
return {
|
|
237
237
|
presetName: matchPreset[1],
|
|
@@ -684,118 +684,80 @@ export function formatMoAResponse({ moaResult, presetName }) {
|
|
|
684
684
|
return parts.join('\n')
|
|
685
685
|
}
|
|
686
686
|
|
|
687
|
-
export
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
providerInfo(provider) {
|
|
693
|
-
return { id: provider, name: 'Mixture of Agents Runner' }
|
|
694
|
-
}
|
|
687
|
+
export async function* streamMoATurn({ targetPreset, userPrompt, messages, callLlm, cwd }, options = {}) {
|
|
688
|
+
const signal = options?.signal
|
|
689
|
+
if (signal?.aborted) return
|
|
695
690
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
imageRequestPricing(_provider, _model) {
|
|
701
|
-
return undefined
|
|
702
|
-
}
|
|
691
|
+
yield { type: 'block-start', index: 0, blockType: 'text' }
|
|
692
|
+
yield { type: 'text-delta', index: 0, text: '🧠 *Mixture of Agents запущен...*\n\n' }
|
|
703
693
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
async resolveModel(provider, model) {
|
|
710
|
-
return { provider, id: model, name: 'MoA Ensemble' }
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
async prepareCall(providerOrConfig, modelOrSignal, _signal) {
|
|
714
|
-
const provider = typeof providerOrConfig === 'object' && providerOrConfig !== null ? providerOrConfig.provider : providerOrConfig
|
|
715
|
-
const model = typeof providerOrConfig === 'object' && providerOrConfig !== null ? providerOrConfig.model : modelOrSignal
|
|
716
|
-
return {
|
|
717
|
-
model: { provider, id: model, name: 'MoA Ensemble' },
|
|
718
|
-
stream: (options) => this.stream(options),
|
|
719
|
-
}
|
|
694
|
+
// Queue for streaming progress updates from within runMoAPipeline
|
|
695
|
+
const progressChunks = []
|
|
696
|
+
const onProgress = (text) => {
|
|
697
|
+
progressChunks.push(text)
|
|
720
698
|
}
|
|
721
699
|
|
|
722
|
-
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
// Queue for streaming progress updates from within runMoAPipeline
|
|
732
|
-
const progressChunks = []
|
|
733
|
-
const onProgress = (text) => {
|
|
734
|
-
progressChunks.push(text)
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
try {
|
|
738
|
-
const pipelinePromise = runMoAPipeline({
|
|
739
|
-
userPrompt,
|
|
740
|
-
messages,
|
|
741
|
-
preset: targetPreset,
|
|
742
|
-
callLlm,
|
|
743
|
-
cwd,
|
|
744
|
-
onProgress,
|
|
745
|
-
})
|
|
746
|
-
|
|
747
|
-
const startTime = Date.now()
|
|
748
|
-
let lastYieldTime = Date.now()
|
|
749
|
-
|
|
750
|
-
// Yield progressive updates while pipeline runs
|
|
751
|
-
while (true) {
|
|
752
|
-
if (signal?.aborted) return
|
|
700
|
+
try {
|
|
701
|
+
const pipelinePromise = runMoAPipeline({
|
|
702
|
+
userPrompt,
|
|
703
|
+
messages,
|
|
704
|
+
preset: targetPreset,
|
|
705
|
+
callLlm,
|
|
706
|
+
cwd,
|
|
707
|
+
onProgress,
|
|
708
|
+
})
|
|
753
709
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const chunk = progressChunks.shift()
|
|
757
|
-
yield { type: 'text-delta', index: 0, text: chunk }
|
|
758
|
-
emittedAny = true
|
|
759
|
-
lastYieldTime = Date.now()
|
|
760
|
-
}
|
|
710
|
+
const startTime = Date.now()
|
|
711
|
+
let lastYieldTime = Date.now()
|
|
761
712
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
])
|
|
766
|
-
if (done) break
|
|
767
|
-
|
|
768
|
-
const elapsedSec = Math.floor((Date.now() - startTime) / 1000)
|
|
769
|
-
// If 3 seconds passed without any progress message, send a live tick
|
|
770
|
-
if (!emittedAny && Date.now() - lastYieldTime >= 3000) {
|
|
771
|
-
yield { type: 'text-delta', index: 0, text: `⏳ *[${elapsedSec}с] Идёт обработка...*\n` }
|
|
772
|
-
lastYieldTime = Date.now()
|
|
773
|
-
}
|
|
774
|
-
}
|
|
713
|
+
// Yield progressive updates while pipeline runs
|
|
714
|
+
while (true) {
|
|
715
|
+
if (signal?.aborted) return
|
|
775
716
|
|
|
717
|
+
let emittedAny = false
|
|
776
718
|
while (progressChunks.length > 0) {
|
|
777
719
|
const chunk = progressChunks.shift()
|
|
778
720
|
yield { type: 'text-delta', index: 0, text: chunk }
|
|
721
|
+
emittedAny = true
|
|
722
|
+
lastYieldTime = Date.now()
|
|
779
723
|
}
|
|
780
724
|
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
725
|
+
const done = await Promise.race([
|
|
726
|
+
pipelinePromise.then(() => true),
|
|
727
|
+
new Promise((r) => { const t = setTimeout(() => r(false), 500); t.unref?.(); }),
|
|
728
|
+
])
|
|
729
|
+
if (done) break
|
|
730
|
+
|
|
731
|
+
const elapsedSec = Math.floor((Date.now() - startTime) / 1000)
|
|
732
|
+
// If 3 seconds passed without any progress message, send a live tick
|
|
733
|
+
if (!emittedAny && Date.now() - lastYieldTime >= 3000) {
|
|
734
|
+
yield { type: 'text-delta', index: 0, text: `⏳ *[${elapsedSec}с] Идёт обработка...*\n` }
|
|
735
|
+
lastYieldTime = Date.now()
|
|
736
|
+
}
|
|
737
|
+
}
|
|
788
738
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
yield { type: '
|
|
792
|
-
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
793
|
-
} catch (err) {
|
|
794
|
-
if (signal?.aborted) return
|
|
795
|
-
const errText = '\n\n⚠️ **Ошибка Mixture of Agents**: ' + (err?.message || String(err))
|
|
796
|
-
yield { type: 'text-delta', index: 0, text: errText }
|
|
797
|
-
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
798
|
-
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
739
|
+
while (progressChunks.length > 0) {
|
|
740
|
+
const chunk = progressChunks.shift()
|
|
741
|
+
yield { type: 'text-delta', index: 0, text: chunk }
|
|
799
742
|
}
|
|
743
|
+
|
|
744
|
+
const moaResult = await pipelinePromise
|
|
745
|
+
if (signal?.aborted) return
|
|
746
|
+
|
|
747
|
+
const formatted = formatMoAResponse({
|
|
748
|
+
moaResult,
|
|
749
|
+
presetName: targetPreset?.name || 'default',
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
yield { type: 'text-delta', index: 0, text: '\n---\n\n' + formatted }
|
|
753
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: formatted } }
|
|
754
|
+
yield { type: 'usage', usage: { inputTokens: moaResult?.usage?.totalTokens || 100, outputTokens: formatted.length } }
|
|
755
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
756
|
+
} catch (err) {
|
|
757
|
+
if (signal?.aborted) return
|
|
758
|
+
const errText = '\n\n⚠️ **Ошибка Mixture of Agents**: ' + (err?.message || String(err))
|
|
759
|
+
yield { type: 'text-delta', index: 0, text: errText }
|
|
760
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
761
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
800
762
|
}
|
|
801
763
|
}
|