feat(hermes): add streaming config form

This commit is contained in:
晴天
2026-05-24 08:40:53 +08:00
parent 1883e18f02
commit a1307716dd
9 changed files with 656 additions and 6 deletions

View File

@@ -38,12 +38,26 @@ test('Hermes 配置页会暴露记忆结构化配置字段', () => {
}
})
test('Hermes 配置页会暴露网关流式结构化配置字段', () => {
for (const id of [
'hm-streaming-save',
'hm-streaming-enabled',
'hm-streaming-transport',
'hm-streaming-edit-interval',
'hm-streaming-buffer-threshold',
'hm-streaming-cursor',
'hm-streaming-fresh-final-after-seconds',
]) {
assert.match(source, new RegExp(`id="${id}"`), `缺少 ${id}`)
}
})
test('Hermes 配置页数值输入会保留 0 值显示', () => {
assert.doesNotMatch(source, /String\(value \|\| ''\)/, 'esc(value) 不能把合法 0 渲染为空字符串')
})
test('Hermes 配置页新增结构化配置不会暴露翻译 key', () => {
const keys = new Set(extractEngineKeys().filter(key => key.includes('ToolGuardrails') || key.includes('MemoryConfig')))
const keys = new Set(extractEngineKeys().filter(key => key.includes('ToolGuardrails') || key.includes('MemoryConfig') || key.includes('StreamingConfig')))
assert.ok(keys.size > 0, '应能提取新增结构化配置用到的 engine 翻译 key')
for (const key of keys) {

View File

@@ -0,0 +1,122 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {
buildHermesStreamingConfigValues,
mergeHermesStreamingConfig,
} from '../scripts/dev-api.js'
test('Hermes 网关流式配置读取会提供上游默认值', () => {
const values = buildHermesStreamingConfigValues({})
assert.deepEqual(values, {
enabled: false,
transport: 'edit',
editInterval: 0.8,
bufferThreshold: 24,
cursor: ' ▉',
freshFinalAfterSeconds: 60,
})
})
test('Hermes 网关流式配置读取会优先使用顶层 streaming 并兼容 gateway.streaming', () => {
const fallbackValues = buildHermesStreamingConfigValues({
gateway: {
streaming: {
enabled: true,
transport: 'draft',
edit_interval: 0.25,
buffer_threshold: 11,
cursor: '...',
fresh_final_after_seconds: 0,
},
},
})
assert.equal(fallbackValues.enabled, true)
assert.equal(fallbackValues.transport, 'draft')
assert.equal(fallbackValues.editInterval, 0.25)
assert.equal(fallbackValues.bufferThreshold, 11)
assert.equal(fallbackValues.cursor, '...')
assert.equal(fallbackValues.freshFinalAfterSeconds, 0)
const topLevelValues = buildHermesStreamingConfigValues({
streaming: {
enabled: false,
transport: 'auto',
edit_interval: 0.5,
buffer_threshold: 40,
cursor: '>',
fresh_final_after_seconds: 120,
},
gateway: {
streaming: {
enabled: true,
transport: 'draft',
},
},
})
assert.equal(topLevelValues.enabled, false)
assert.equal(topLevelValues.transport, 'auto')
assert.equal(topLevelValues.editInterval, 0.5)
assert.equal(topLevelValues.bufferThreshold, 40)
assert.equal(topLevelValues.cursor, '>')
assert.equal(topLevelValues.freshFinalAfterSeconds, 120)
})
test('Hermes 网关流式配置保存会写入顶层 streaming 并保留无关 YAML', () => {
const next = mergeHermesStreamingConfig({
model: { provider: 'anthropic' },
streaming: {
enabled: false,
custom_flag: 'keep-me',
},
gateway: {
streaming: {
enabled: false,
legacy_flag: 'keep-nested',
},
},
display: {
streaming: true,
},
}, {
enabled: true,
transport: 'draft',
editInterval: '0.35',
bufferThreshold: '48',
cursor: '',
freshFinalAfterSeconds: '0',
})
assert.deepEqual(next.model, { provider: 'anthropic' })
assert.equal(next.display.streaming, true)
assert.equal(next.gateway.streaming.legacy_flag, 'keep-nested')
assert.equal(next.streaming.enabled, true)
assert.equal(next.streaming.transport, 'draft')
assert.equal(next.streaming.edit_interval, 0.35)
assert.equal(next.streaming.buffer_threshold, 48)
assert.equal(next.streaming.cursor, '')
assert.equal(next.streaming.fresh_final_after_seconds, 0)
assert.equal(next.streaming.custom_flag, 'keep-me')
})
test('Hermes 网关流式配置保存会拒绝非法传输模式和越界节奏', () => {
assert.throws(
() => mergeHermesStreamingConfig({}, { transport: 'invalid' }),
/streaming\.transport/,
)
assert.throws(
() => mergeHermesStreamingConfig({}, { editInterval: '0.01' }),
/streaming\.edit_interval/,
)
assert.throws(
() => mergeHermesStreamingConfig({}, { bufferThreshold: '0' }),
/streaming\.buffer_threshold/,
)
assert.throws(
() => mergeHermesStreamingConfig({}, { freshFinalAfterSeconds: '-1' }),
/streaming\.fresh_final_after_seconds/,
)
})