fix(session-title): serialize provider request writes

This commit is contained in:
Tianyi Cui
2026-07-21 14:39:09 +08:00
parent b3ba4345f5
commit d9557fa5e0
11 changed files with 150 additions and 18 deletions
@@ -8,7 +8,7 @@ This package is a library, not a Cordis plugin. The provider plugins call `regis
`provider` and `model` overrides are optional but must be supplied together as non-empty strings. Without that pair, the helper uses the exact provider/model route captured from the current session's logged `request/header`; an explicit refresh before any route exists therefore needs overrides. Input exceeding `maxInputBytes` rejects instead of being truncated. Timeout, cancellation, malformed or empty output, tool calls, and non-stop finish reasons also reject; the session-title service decides whether that rejection is an automatic warning or an explicit caller failure.
After route and input validation, the helper appends a log-only `session/title-llm-request` event before model dispatch. It contains the title-provider id, exact source seqs, route, system prompt, message list, and output-token cap used by the call. The dispatched envelope is deep-frozen to keep interceptors aligned with that record but deliberately lacks dsh-agent-loop's process-local request identity, so loop-only reconstruction observers do not compare it with the conversation header. A later model failure leaves that request record intact; validation failures that never become dispatchable requests do not create one. The event stays outside derived model history.
After route and input validation, the helper appends a log-only `session/title-llm-request` event before model dispatch. It contains the title-provider id, exact source seqs, route, system prompt, message list, and output-token cap used by the call. The append shares the title capability's per-session settlement queue, so a superseding request cannot collide with an earlier fallback, request record, or accepted-title flush. The dispatched envelope is deep-frozen to keep interceptors aligned with that record but deliberately lacks dsh-agent-loop's process-local request identity, so loop-only reconstruction observers do not compare it with the conversation header. A later model failure leaves that request record intact; validation failures that never become dispatchable requests do not create one. The event stays outside derived model history.
## Configuration
@@ -9,7 +9,11 @@ import z from 'schemastery'
import { BlockAssembler, deepFreeze } from '@deepseek-ai/dsh-llm'
import type { FinishReason, GenerateOptions, Message } from '@deepseek-ai/dsh-llm'
import { deadline, MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
import { normalizeSessionTitle, SessionTitleProviderId } from '@deepseek-ai/dsh-session-title'
import {
appendSessionTitleOutOfBand,
normalizeSessionTitle,
SessionTitleProviderId,
} from '@deepseek-ai/dsh-session-title'
import type {
SessionTitleAutomaticMode,
SessionTitleModelProvenance,
@@ -258,14 +262,14 @@ export async function generateSessionTitleWithLlm(
sessionId: request.session.id,
signal: callDeadline.signal,
})
await ctx.sessions.appendOutOfBand(request.session, 'session/title-llm-request', {
await appendSessionTitleOutOfBand(ctx, request.session, 'session/title-llm-request', {
titleProvider,
messageSeqs: selectedMessages.map(message => message.seq),
route,
system,
messages,
maxTokens: config.maxOutputTokens,
}, { kind: 'session-title' })
}, callDeadline.signal)
callDeadline.signal.throwIfAborted()
const assembler = new BlockAssembler()
for await (const chunk of ctx.llm.stream(options)) assembler.push(chunk)