fix(web): generate model-backed session titles

This commit is contained in:
Tianyi Cui
2026-07-23 18:20:17 +08:00
parent a60258b485
commit f666c39379
20 changed files with 142 additions and 29 deletions
@@ -9,6 +9,7 @@ import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
import { LlmAdapter } from '@deepseek-ai/dsh-llm'
import type { SessionId } from '@deepseek-ai/dsh-session'
import type { Config as SessionTitleConfig } from '@deepseek-ai/dsh-session-title'
import type { Config as SessionTitleLlmConfig } from '@deepseek-ai/dsh-session-title-first-message-llm'
import type { HostFrame, MuxFrame } from '@deepseek-ai/dsh-host-apiproxy/api'
import type { RpcRequest, RpcResponse } from '@deepseek-ai/dsh-host-apiproxy/api/rpc'
import { RpcId } from '@deepseek-ai/dsh-host-apiproxy/api/rpc'
@@ -21,6 +22,10 @@ class ScriptedAdapter extends LlmAdapter {
}
async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
if ((options.tools?.length ?? 0) === 0) {
yield * textResponse('Durable append-only session titles')
return
}
const entry = this.script.shift()
if (!entry) throw new Error('ScriptedAdapter: script exhausted')
if (entry === 'hang') {
@@ -96,6 +101,7 @@ afterEach(async () => {
async function boot(
script: (StreamChunk[] | 'hang')[] = [],
sessionTitle?: SessionTitleConfig,
sessionTitleLlm?: SessionTitleLlmConfig,
): Promise<RunningHost> {
host = await startHost({
boot: {
@@ -103,6 +109,7 @@ async function boot(
provider: 'scripted',
model: 'test-model',
...(sessionTitle === undefined ? {} : { sessionTitle }),
...(sessionTitleLlm === undefined ? {} : { sessionTitleLlm }),
},
})
host.ctx.llm.registerAdapter(['scripted'], new ScriptedAdapter(script))
@@ -158,6 +165,63 @@ describe('sessions.create / list', () => {
})
describe('sessions.prompt / cancel', () => {
it.each([
{ name: 'host default', config: undefined, target: '5 words', maxTokens: 64 },
{
name: 'configured policy',
config: {
targetWords: 3,
targetCjkCharacters: 8,
maxInputBytes: 2_048,
maxOutputTokens: 24,
timeoutMs: 2_000,
},
target: '3 words',
maxTokens: 24,
},
] satisfies {
name: string
config: SessionTitleLlmConfig | undefined
target: string
maxTokens: number
}[])('replaces the fallback with a model-backed first-message title using the $name', async ({ config, target, maxTokens }) => {
const modelTitle = 'Durable append-only session titles'
const running = await boot([textResponse('pong')], undefined, config)
const { api, ctx } = running
const { sessionId } = expectOk(await api.sessions.create(request({})))
const agent = ctx.agents.get(sessionId) as Agent
const idle = waitForIdle(ctx, agent)
expectOk(await api.sessions.prompt(request({
sessionId,
mode: 'queue' as const,
content: [{ type: 'text' as const, text: 'Explain why append-only logs make session titles durable.' }],
})))
await idle
await vi.waitFor(() => {
expect(agent.session.events.filter(event => event.type === 'session/title').map(event => event.data))
.toEqual([
{
title: 'Explain why append-only logs make',
messageSeqs: [1],
source: { kind: 'fallback' },
},
{
title: modelTitle,
messageSeqs: [1],
source: {
kind: 'provider',
provider: 'session-title-first-message-llm',
model: { provider: 'scripted', model: 'test-model' },
},
},
])
})
const titleRequest = agent.session.events.find(event => event.type === 'session/title-llm-request')
expect(titleRequest?.data.system).toContain(target)
expect(titleRequest?.data.maxTokens).toBe(maxTokens)
})
it.each([
{ name: 'host default', config: undefined, expected: 'Show the Web UI durable' },
{