mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-12 04:01:20 +00:00
Merge remote-tracking branch 'origin/worktree/session-format-04-live-assistant-stream' into worktree/session-format-05-v1-v2-chunk-migration
This commit is contained in:
@@ -17,11 +17,7 @@ import { SessionCommandController } from './commands.ts'
|
||||
import { SessionControlController } from './control.ts'
|
||||
import { SessionHistoryController } from './history.ts'
|
||||
import { SessionFileReferences } from './file-references.ts'
|
||||
import {
|
||||
ApiSessionList,
|
||||
DEFAULT_COLD_BLANK_PROBE_MAX_BYTES,
|
||||
DEFAULT_COLD_BLANK_PROBE_MAX_EVENTS,
|
||||
} from './list.ts'
|
||||
import { ApiSessionList } from './list.ts'
|
||||
import { buildModelCatalog } from './catalog.ts'
|
||||
import { installModelSelectionProjection } from './model-selection-projection.ts'
|
||||
import { SessionSkillCatalog } from './skill-catalog.ts'
|
||||
@@ -70,10 +66,6 @@ declare module '@deepseek-ai/cordis' {
|
||||
|
||||
/** Session Controller deployment policy. */
|
||||
export interface Config {
|
||||
/** Maximum stat-reported event count eligible for one full cold projection observation; `0` disables the event-count gate. */
|
||||
readonly coldBlankProbeMaxEvents?: number
|
||||
/** Maximum stat-reported artifact byte size eligible for one full cold projection observation; `0` disables the byte-size gate. */
|
||||
readonly coldBlankProbeMaxBytes?: number
|
||||
/** Override platform desktop-opener detection. */
|
||||
readonly nativeOpen?: boolean
|
||||
}
|
||||
@@ -101,8 +93,6 @@ export class SessionController extends TypertRemoteService {
|
||||
]
|
||||
|
||||
static Config: z<Config> = z.object({
|
||||
coldBlankProbeMaxEvents: z.natural().default(DEFAULT_COLD_BLANK_PROBE_MAX_EVENTS),
|
||||
coldBlankProbeMaxBytes: z.natural().default(DEFAULT_COLD_BLANK_PROBE_MAX_BYTES),
|
||||
nativeOpen: z.boolean(),
|
||||
})
|
||||
|
||||
@@ -117,7 +107,7 @@ export class SessionController extends TypertRemoteService {
|
||||
|
||||
/**
|
||||
* @param ctx - Host context containing the Session capability assembly.
|
||||
* @param config - cold-list observation and native-opener deployment policy.
|
||||
* @param config - native-opener deployment policy.
|
||||
* @param internals - host integrations replaceable by direct unit tests.
|
||||
*/
|
||||
constructor(ctx: Context, config: Config, internals: SessionControllerInternals = {}) {
|
||||
@@ -132,10 +122,7 @@ export class SessionController extends TypertRemoteService {
|
||||
await Promise.allSettled([...this.promotions])
|
||||
}, 'session-controller.promotions')
|
||||
this.history = new SessionHistoryController(ctx, (observation) => { this.promote(observation) })
|
||||
this.listState = new ApiSessionList(ctx, {
|
||||
coldBlankProbeMaxEvents: config.coldBlankProbeMaxEvents ?? DEFAULT_COLD_BLANK_PROBE_MAX_EVENTS,
|
||||
coldBlankProbeMaxBytes: config.coldBlankProbeMaxBytes ?? DEFAULT_COLD_BLANK_PROBE_MAX_BYTES,
|
||||
})
|
||||
this.listState = new ApiSessionList(ctx)
|
||||
this.openPath = internals.openPath ?? openNativePath
|
||||
this.canOpenPath = internals.canOpenPath
|
||||
?? (() => config.nativeOpen ?? (internals.openPath !== undefined || canOpenNativePath()))
|
||||
|
||||
@@ -19,21 +19,6 @@ import type {
|
||||
SessionSearchValue, SessionSummary,
|
||||
} from './types.ts'
|
||||
|
||||
/** Default maximum stat-reported event count eligible for one cold projection observation. */
|
||||
export const DEFAULT_COLD_BLANK_PROBE_MAX_EVENTS = 16
|
||||
|
||||
/** Default maximum stat-reported artifact size eligible for one cold projection observation. */
|
||||
export const DEFAULT_COLD_BLANK_PROBE_MAX_BYTES = 1024
|
||||
|
||||
/** Resolved cold-blank probe policy: each threshold gates its stat metric; `0` disables that gate. */
|
||||
export interface ColdBlankProbePolicy {
|
||||
/** Maximum stat-reported `eventCount` eligible for a full observation. */
|
||||
readonly coldBlankProbeMaxEvents: number
|
||||
/** Maximum stat-reported `sizeBytes` eligible for a full observation. */
|
||||
readonly coldBlankProbeMaxBytes: number
|
||||
}
|
||||
|
||||
const COLD_SUMMARY_BATCH_SIZE = 16
|
||||
const SEARCH_PROVIDER_CALL_LIMIT = 100
|
||||
const SESSION_SEARCH_QUERY_MAX_CHARS = 500
|
||||
const MESSAGE_TYPES = new Set(['user/message', 'assistant/message'])
|
||||
@@ -90,14 +75,8 @@ export function truncateUnicodeCodePoints(value: string, maximum: number): strin
|
||||
|
||||
/** Owns list projection registration, bounded cold summaries, and authorized search. */
|
||||
export class ApiSessionList {
|
||||
/**
|
||||
* @param ctx - Host context carrying Session, query, persistence, and projection services.
|
||||
* @param probe - stat-metadata thresholds gating a full cold observation.
|
||||
*/
|
||||
constructor(
|
||||
private readonly ctx: Context,
|
||||
private readonly probe: ColdBlankProbePolicy,
|
||||
) {
|
||||
/** @param ctx - Host context carrying Session, query, persistence, and projection services. */
|
||||
constructor(private readonly ctx: Context) {
|
||||
ctx.sessionProjections.register<'sessionListMetadata', SessionListMetadata>({
|
||||
key: 'sessionListMetadata',
|
||||
stateSchema: sessionListMetadataSchema,
|
||||
@@ -159,26 +138,13 @@ export class ApiSessionList {
|
||||
if (record.header.cwd === undefined) continue
|
||||
cold.push(record.header)
|
||||
}
|
||||
for (let offset = 0; offset < cold.length; offset += COLD_SUMMARY_BATCH_SIZE) {
|
||||
const settled = await Promise.allSettled(cold.slice(offset, offset + COLD_SUMMARY_BATCH_SIZE)
|
||||
.map(header => this.summarizeCold(header, signal)))
|
||||
for (const result of settled) {
|
||||
if (result.status === 'rejected') throw result.reason
|
||||
items.push(result.value)
|
||||
}
|
||||
}
|
||||
for (const header of cold) items.push(this.summarizeCold(header))
|
||||
items.sort((left, right) => right.updatedAt - left.updatedAt)
|
||||
return items
|
||||
}
|
||||
|
||||
private async summarizeCold(
|
||||
header: SessionHeader,
|
||||
signal: AbortSignal | undefined,
|
||||
): Promise<SessionSummary> {
|
||||
const cached = this.projectionsFor(header, undefined)
|
||||
const projections = cached?.values.sessionListMetadata?.blank === false
|
||||
? cached
|
||||
: await this.probeSmallCold(header, signal) ?? cached
|
||||
private summarizeCold(header: SessionHeader): SessionSummary {
|
||||
const projections = this.projectionsFor(header, undefined)
|
||||
const raced = this.ctx.sessions.get(header.id)
|
||||
if (raced !== undefined) return this.summaryFor(raced)
|
||||
const metadata = projections?.values.sessionListMetadata
|
||||
@@ -193,54 +159,6 @@ export class ApiSessionList {
|
||||
}
|
||||
}
|
||||
|
||||
private async probeSmallCold(
|
||||
header: SessionHeader,
|
||||
signal: AbortSignal | undefined,
|
||||
): Promise<SessionProjectionHints | undefined> {
|
||||
const { coldBlankProbeMaxEvents, coldBlankProbeMaxBytes } = this.probe
|
||||
if (coldBlankProbeMaxEvents === 0 && coldBlankProbeMaxBytes === 0) return undefined
|
||||
const persistence = this.ctx.get('sessionPersistence')
|
||||
if (persistence === undefined) return undefined
|
||||
signal?.throwIfAborted()
|
||||
let snapshot: Awaited<ReturnType<typeof persistence.stat>>
|
||||
try {
|
||||
snapshot = await persistence.stat(header.id, signal === undefined ? {} : { signal })
|
||||
} catch (error: unknown) {
|
||||
// An unreadable single session degrades to unknown state instead of
|
||||
// failing the whole list request.
|
||||
signal?.throwIfAborted()
|
||||
this.ctx.logger.warn(
|
||||
`api-session.list: cold stat for "${header.id}" failed; serving it as visible: ${String(error)}`,
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
if (snapshot === undefined) return undefined
|
||||
if (snapshot.eventCount !== undefined) {
|
||||
if (coldBlankProbeMaxEvents === 0 || snapshot.eventCount > coldBlankProbeMaxEvents) return undefined
|
||||
} else if (snapshot.sizeBytes !== undefined) {
|
||||
if (coldBlankProbeMaxBytes === 0 || snapshot.sizeBytes > coldBlankProbeMaxBytes) return undefined
|
||||
} else {
|
||||
// The backend offers no cheap size hint, so a full observation is unbounded work.
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
using observation = await this.ctx.sessionQuery.observeSession(header.id, {
|
||||
...(signal === undefined ? {} : { signal }),
|
||||
projectionMode: 'all',
|
||||
})
|
||||
const block = observation.projections
|
||||
return block === undefined
|
||||
? undefined
|
||||
: { asOfSeq: block.asOfSeq, values: block.values as SessionProjectionValues }
|
||||
} catch (error: unknown) {
|
||||
signal?.throwIfAborted()
|
||||
this.ctx.logger.warn(
|
||||
`api-session.list: small cold observation for "${header.id}" failed; serving it as visible: ${String(error)}`,
|
||||
)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search current visible message content without activating any matching Session.
|
||||
* @param query - literal message-content query.
|
||||
@@ -354,10 +272,12 @@ export class ApiSessionList {
|
||||
session: Session | undefined,
|
||||
): SessionProjectionHints | undefined {
|
||||
try {
|
||||
const cache = this.ctx.get('sessionProjectionCache')
|
||||
const block = session === undefined
|
||||
? header.isSeeded
|
||||
? undefined
|
||||
: this.ctx.get('sessionProjectionCache')?.cachedSnapshot(header, SessionLogOffset(0))
|
||||
: cache?.cachedSnapshot(header, SessionLogOffset(0))
|
||||
?? cache?.cachedPredecessorTitle(header, SessionLogOffset(0))
|
||||
: this.ctx.sessionProjections.cachedSnapshot(session)
|
||||
return block !== undefined && Object.keys(block.values).length > 0
|
||||
? {
|
||||
|
||||
Reference in New Issue
Block a user