Merge master and reconcile translation records
This commit is contained in:
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))
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user