Merge master and reconcile translation records

This commit is contained in:
Dudu-0223 committed 2026-10-23 18:19:07 +08:00
commit 00a2e76b87
127 files changed
+4167 -557

No files matched your search

@@ -9,7 +9,6 @@ import { installModelSelection } from '@deepseek-ai/dsh-agent'
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
import { brandString } from '@deepseek-ai/dsh-brand'
import type { Session, SessionId } from '@deepseek-ai/dsh-session'
import type { SessionInspection } from '@deepseek-ai/dsh-session-persistence'
import { SessionQueryError, type SessionObservation } from '@deepseek-ai/dsh-session-query'
import { RemoteError, remoteErrorOf } from '@deepseek-ai/dsh-typert-protocol'
import type {} from '@deepseek-ai/dsh-typert-registry'
@@ -123,40 +122,6 @@ export function apiSessionSubagentOwnershipError(sessionId: SessionId): ApiSessi
)
}
/**
* Inspect one cold Session without repairing, resuming, or publishing it.
* @param ctx - Host context carrying Session persistence.
* @param sessionId - durable Session identity.
* @param signal - optional cancellation for persistence reads.
* @returns the persisted header and complete event prefix.
*/
export async function inspectApiSession(
ctx: Context,
sessionId: SessionId,
signal?: AbortSignal,
): Promise<SessionInspection> {
try {
using observation = await ctx.sessionQuery.observeSession(sessionId, {
...(signal === undefined ? {} : { signal }),
projectionMode: 'none',
})
if (observation.header.cwd === undefined) {
throw new ApiSessionNotFound(`session "${sessionId}" not found`)
}
return {
meta: observation.header,
inheritedEventCount: observation.inheritedEventCount,
events: [...observation.events],
}
} catch (error: unknown) {
if (error instanceof SessionQueryError
&& error.code === 'SESSION_QUERY_SESSION_NOT_FOUND') {
throw new ApiSessionNotFound(`session "${sessionId}" not found`)
}
throw error
}
}
/** Owns every operation that may create, resume, or configure a Web Agent. */
export class ApiSessionAgentController {
private readonly resumes = new Map<SessionId, Promise<Agent>>()
@@ -31,7 +31,6 @@ import {
ApiSessionSubagentOwnership,
apiSessionSubagentOwnershipError,
hasApiSessionSubagentOwner,
inspectApiSession,
type ApiSessionCreated,
} from './agent.ts'
import type {
@@ -54,12 +53,6 @@ import type {
SessionRequestId,
} from './types.ts'
interface SessionReadState {
readonly id: SessionId
readonly header: SessionHeader
readonly events: readonly SessionEvent[]
}
type PromptContentCandidate =
| SessionPromptRequest['content'][number]
| Extract<SessionUpdateQueueRequest['action'], { readonly kind: 'edit' }>['content'][number]
@@ -371,11 +364,16 @@ export class SessionCommandController {
* @returns the durable attachment reference and base64-encoded bytes.
*/
async attachment(request: SessionAttachmentRequest): Promise<SessionAttachmentValue> {
let source: SessionReadState
let ref: ImageAttachmentRef | undefined
try {
source = await this.readSessionState(request.sessionId)
using observation = await this.ctx.sessionQuery.observeSession(request.sessionId, { projectionMode: 'none' })
if (observation.source === 'prepared' && observation.header.cwd === undefined) {
throw new ApiSessionNotFound(`session "${request.sessionId}" not found`)
}
ref = referencedImage(observation.events, String(request.attachmentId))
} catch (error) {
if (error instanceof ApiSessionNotFound) {
if (error instanceof ApiSessionNotFound
|| (error instanceof SessionQueryError && error.code === 'SESSION_QUERY_SESSION_NOT_FOUND')) {
throw new RemoteError('session/not-found', error.message, { sessionId: request.sessionId })
}
throw new RemoteError(
@@ -384,7 +382,6 @@ export class SessionCommandController {
{},
)
}
const ref = referencedImage(source.events, String(request.attachmentId))
if (ref === undefined) {
throw new RemoteError(
'session/attachment-invalid',
@@ -533,16 +530,6 @@ export class SessionCommandController {
throw new RemoteError('gateway/internal', `failed to create session: ${String(error)}`, {})
}
private async readSessionState(sessionId: SessionId): Promise<SessionReadState> {
const attached = this.ctx.sessions.get(sessionId)
if (attached !== undefined) {
// oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
return { id: attached.id, header: attached.header, events: attached.snapshotEvents() }
}
const inspected = await inspectApiSession(this.ctx, sessionId)
return { id: inspected.meta.id, header: inspected.meta, events: inspected.events }
}
private async forkWorkspace(source: SessionHeader): Promise<Workspace | undefined> {
const workspaces = this.ctx.workspaceRegistry.list()
const direct = workspaces.find(workspace => workspace.sessionIds.includes(source.id))
+28 -37
View File
@@ -10,16 +10,14 @@ import { errorChain, ReasoningEffortId, type MessageSource } from '@deepseek-ai/
import type {} from '@deepseek-ai/dsh-client-file-upload'
import { canOpenNativePath, nativeFileManager, nativeFileApplications, openNativeFileApplication, openNativeAssociatedPath, revealNativePath } from '@deepseek-ai/dsh-native-command'
import type { SessionId } from '@deepseek-ai/dsh-session'
import type { SessionInspection } from '@deepseek-ai/dsh-session-persistence'
import type {} from '@deepseek-ai/dsh-session-projection-cache'
import { SessionQueryError, type SessionObservation } from '@deepseek-ai/dsh-session-query'
import { Remote, RemoteError, TypertRemoteService, principalOf } from '@deepseek-ai/dsh-typert-protocol'
import { Remote, RemoteError, TypertRemoteService, principalOf, type TypertResourceResolveInput } from '@deepseek-ai/dsh-typert-protocol'
import { canonicalClientTimeZone } from '@deepseek-ai/dsh-util-time'
import { assertNever } from '@deepseek-ai/dsh-util-values'
import type {} from '@deepseek-ai/dsh-api-remote-access'
import {
ApiSessionAgentController,
inspectApiSession,
type ApiSessionAgentResult,
} from './agent.ts'
import { SessionCommandController, hasPromptContent } from './commands.ts'
@@ -80,13 +78,31 @@ declare module '@deepseek-ai/cordis' {
}
/**
* Read the Session resource a history address names: the Session itself, or
* the child Session whose transcript a subagent address reads.
* @param request - history request carrying the durable address.
* @returns the Session whose access the read requires.
* Extract the Session resource from an ID or a history address.
*
* Registers as the `session` kind default, so it applies to every Remote method of
* every namespace: a top-level `sessionId` parameter, otherwise the first object
* parameter carrying `sessionId` or an `address`. A new request type whose first
* object parameter has an unrelated `address` field would be read as an address.
* @param input - validated wire arguments and their ordered parameter descriptions.
* @returns the named Session identity, otherwise the first object's Session identity or history address.
* @throws Error when the request object supplies both sessionId and address.
*/
function addressedSession(request: { readonly address: SessionAddress }): SessionId {
return request.address.kind === 'session' ? request.address.sessionId : request.address.childSessionId
function resolveSessionResource({ args, parameters }: TypertResourceResolveInput): SessionId | undefined {
const sessionParameter = parameters.find(parameter => parameter.name === 'sessionId' || parameter.wire === 'sessionId')
if (sessionParameter !== undefined) return args[sessionParameter.wire] as SessionId | undefined
for (const parameter of parameters) {
const value = args[parameter.wire]
if (typeof value !== 'object' || value === null || Array.isArray(value)) continue
const request = value as { readonly sessionId?: SessionId; readonly address?: SessionAddress }
const address = request.address
if (request.sessionId !== undefined && address !== undefined) {
throw new Error('Session resource request must not contain both sessionId and address')
}
return request.sessionId ?? (address === undefined ? undefined
: address.kind === 'session' ? address.sessionId : address.childSessionId)
}
return undefined
}
/** Session Controller deployment policy. */
@@ -167,7 +183,7 @@ export class SessionController extends TypertRemoteService {
protected async [Service.init](): Promise<void> {
const ctx = this.ctx
ctx.typert.resources.register('session', { field: 'sessionId' })
ctx.typert.resources.register('session', { resolve: resolveSessionResource })
this.agents = new ApiSessionAgentController(ctx)
this.agents.initialize()
this.commands = new SessionCommandController(ctx, this.agents, process.cwd())
@@ -240,28 +256,6 @@ export class SessionController extends TypertRemoteService {
return this.agents.resolveAgent(sessionId)
}
/**
* Inspect one attached or persisted Session without activating its Agent.
* @param sessionId - durable Session identity.
* @param signal - optional caller cancellation for persistence reads.
* @returns the current attached state or persisted header and event prefix.
*/
inspect(
sessionId: SessionId,
signal?: AbortSignal,
): Promise<SessionInspection> {
const attached = this.ctx.sessions.get(sessionId)
if (attached !== undefined) {
return Promise.resolve({
meta: attached.header,
inheritedEventCount: attached.inheritedEventCount,
// oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
events: attached.snapshotEvents(),
})
}
return inspectApiSession(this.ctx, sessionId, signal)
}
/**
* Read all visible Session rows without resuming an Agent.
* @param _request - reserved empty list request.
@@ -517,7 +511,7 @@ export class SessionController extends TypertRemoteService {
* @param signal - cancellation for persistence reads.
* @returns one chronological page.
*/
@Remote({ name: 'page', resources: { session: addressedSession } })
@Remote('page')
page(request: SessionPageRequest, signal: AbortSignal): Promise<SessionPage> {
return this.history.page(request, signal)
}
@@ -529,7 +523,7 @@ export class SessionController extends TypertRemoteService {
* @returns a complete opening snapshot followed by gap-free durable event
* frames and optional cursorless assistant-stream frames.
*/
@Remote({ mode: 'stream', resources: { session: addressedSession } })
@Remote({ mode: 'stream' })
follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame> {
return this.history.follow(request, signal)
}
@@ -610,9 +604,6 @@ export class SessionController extends TypertRemoteService {
}
}
}
}
export { buildModelCatalog }
export default SessionController