The titleInput unit no longer retains the full eligible message history
in bounded reverse-linked chunks. It folds only {first, last, count} —
the values scheduling and fallback reads need — and the full eligible
prefix for one provider generation is scanned from the session log at
execution time. The projection state is O(1) per session instead of
growing with every user message.
The README description updates accordingly and drops the inaccurate
"latest request route" claim; the projection test now asserts the
bounded aggregate and its checkpoint row.
8.5 KiB
Session Titles
English | 中文
Durable latest-wins title state and the optional asynchronous provider vocabulary owned by @deepseek-ai/dsh-session-title. The shared LLM helper owns the exact auxiliary request record. Package READMEs own timing, fallback, failure, and fork behavior; the generated persistence catalog owns the complete event declarations.
Sources: packages/session/session-title/src/index.ts, packages/session/session-title-llm/src/index.ts
Durable title state
SessionTitleProviderId is recorded for provider-produced revisions. SessionTitleEventData lists the exact human-message seqs used for the title, while SessionTitleSnapshot adds the durable event envelope facts retained in the title projection state. ctx.sessionTitle.get() reads that state through stateOf(); the client view remains only the title string or null. foldSessionTitle() provides the same selection for detached logs.
/** Identifies one session-title provider registration. */
type SessionTitleProviderId = Branded<'SessionTitleProviderId'>
/** Exact auxiliary model route that produced a title. */
interface SessionTitleModelProvenance {
/** Registered LLM provider route. */
readonly provider: string
/** Provider model id. */
readonly model: string
}
/** Durable ownership record for an accepted session title. */
type SessionTitleSource =
| { readonly kind: 'fallback' }
| {
readonly kind: 'provider'
readonly provider: SessionTitleProviderId
readonly model?: SessionTitleModelProvenance
}
| {
/** Explicit user rename: pins the title — automatic generation stops scheduling. */
readonly kind: 'user'
}
/** Payload of the log-only `session/title` event. */
interface SessionTitleEventData {
/** Normalized non-empty title text. */
readonly title: string
/** Exact human `user/message` seqs used to derive this title; empty for an explicit user rename. */
readonly messageSeqs: number[]
/** Whether the built-in fallback, a registered provider, or the user supplied the title. */
readonly source: SessionTitleSource
}
/** Latest folded title plus the title event's durable envelope facts. */
interface SessionTitleSnapshot extends SessionTitleEventData {
/** Seq of the latest `session/title` event. */
readonly eventSeq: number
/** Timestamp of the latest `session/title` event. */
readonly updatedAt: number
}
Auxiliary request record
The shared LLM helper records each validated, dispatchable title request before calling the model. The payload reproduces the model-visible system and message input, routing, output limit, provider ownership, and source-message attribution even when generation later fails.
/** Exact model-visible request recorded before one auxiliary title dispatch. */
interface SessionTitleLlmRequestEventData {
/** Registered title-provider identity responsible for the request. */
readonly titleProvider: SessionTitleProviderId
/** Exact human `user/message` seqs represented in `messages`. */
readonly messageSeqs: number[]
/** Exact auxiliary LLM route. */
readonly route: SessionTitleModelProvenance
/** Exact auxiliary system prompt. */
readonly system: string
/** Exact auxiliary message list. */
readonly messages: Message[]
/** Exact auxiliary output-token cap. */
readonly maxTokens: number
}
Provider input and output
The service snapshots eligible messages through one revision. A provider returns only seqs from that request; service-owned acceptance verifies ordering, normalizes the title, enforces the byte limit, and appends the title with its source-message seqs and source kind.
/** One eligible human text message exposed to title providers. */
interface SessionTitleUserMessage {
/** Source `user/message` event seq. */
readonly seq: number
/** Exact concatenated text-block content. */
readonly text: string
}
/** Automatic generation cadence owned by a registered provider. */
type SessionTitleAutomaticMode = 'first-prompt' | 'all-prompts'
/** Immutable input supplied to one title-provider call. */
interface SessionTitleProviderRequest {
/** Live session being titled. */
readonly session: Session
/** All eligible human messages through this generation revision. */
readonly messages: readonly SessionTitleUserMessage[]
/** Exact current logged main-request route, when one has been recorded. */
readonly route?: SessionTitleModelProvenance
/** Cancellation for supersession, disposal, timeout composition, or the explicit caller. */
readonly signal: AbortSignal
}
/** Provider output before service-owned normalization and log acceptance. */
interface SessionTitleProviderResult {
/** Proposed title text. */
readonly title: string
/** Exact seqs from `request.messages` used by this result. */
readonly messageSeqs: readonly number[]
/** Auxiliary LLM route, when generation used a model. */
readonly model?: SessionTitleModelProvenance
}
/** One optional asynchronous title implementation registered with the service. */
interface SessionTitleProvider {
/** Stable id of the provider recorded with the title. */
readonly id: SessionTitleProviderId
/** When new human prompts start automatic generation. */
readonly automatic: SessionTitleAutomaticMode
/**
* Produce one title revision.
* @param request - message snapshot, current route, session, and cancellation.
* @returns proposed title plus exact input seqs and the optional provider/model route used to generate it.
*/
generate(request: SessionTitleProviderRequest): Promise<SessionTitleProviderResult>
}
Cordis API
Generated from source by scripts/gen-cordis-catalog.ts (verified fresh by pnpm run verify-cordis-catalog in doc-sync; regenerate with pnpm run gen-cordis-catalog) — this section is byte-identical in both language sides of the page. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.
ctx.sessionTitle — SessionTitleService
Log-backed title fold plus asynchronous fallback generation.
/**
* Read the latest folded title from one live or replayed session.
* @param session - session whose log is the title source of truth.
* @returns latest title snapshot, or `undefined` before eligible input.
*/
get(session: Session): SessionTitleSnapshot | undefined
/**
* Accept an explicit user title. Appends a `session/title` event with the
* `user` source, which pins the title: in-flight automatic generation is
* superseded and later user messages schedule none (an explicit
* {@link SessionTitleService.refresh} remains the deliberate unpin).
* @param session - exact live session to rename.
* @param title - raw user input; normalized before acceptance.
* @returns the accepted title snapshot.
* @throws {SessionTitleInvalidError} when the title normalizes to empty.
* @throws {Error} when the session is not live or the service is disposed.
*/
rename(session: Session, title: string): SessionTitleSnapshot
/**
* Explicitly retry the registered provider, or materialize the built-in
* fallback when no provider is registered.
* @param session - exact live session to refresh.
* @param signal - optional caller cancellation.
* @returns latest accepted title, or `undefined` when no eligible text exists.
*/
async refresh(session: Session, signal?: AbortSignal): Promise<SessionTitleSnapshot | undefined>
/**
* Register the sole optional title provider. Disposal aborts its pending and
* active work before another provider may register.
* @param provider - provider identity, cadence, and generation function.
* @returns exact Cordis effect disposer, which settles after active calls quiesce.
*/
register(provider: SessionTitleProvider): () => Promise<void>
Types: Session