mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
Single RemoteError with a merge-extensible, domain-prefixed code map; owners throw at the failure point; streams surface marked failures; clients consume ctx.remote directly with isRemoteFailure as the only discrimination point and construct no failure instances.
120 lines
4.4 KiB
TypeScript
120 lines
4.4 KiB
TypeScript
/** Workspace-specific adapter for the Gateway-owned snapshot stream lifecycle. */
|
|
|
|
import type { Context } from '@deepseek-ai/cordis'
|
|
import {
|
|
RemoteSnapshotStream,
|
|
RemoteStreamCarrierError,
|
|
type ClientRemote,
|
|
} from '@deepseek-ai/dsh-api-gateway/client'
|
|
import type { WorkspaceFollowFrame, WorkspaceFollowIncrement } from '../types.ts'
|
|
import type { WorkspaceFollowSink } from './model.ts'
|
|
import { ClientWorkspaceModel } from './model.ts'
|
|
import { WorkspaceController } from './service.ts'
|
|
|
|
export { ClientWorkspaceModel } from './model.ts'
|
|
export type {
|
|
WorkspaceFollowSink, WorkspaceListPhase, WorkspaceRemote, WorkspaceSnapshot,
|
|
} from './model.ts'
|
|
export { WorkspaceController, WorkspaceCreateError } from './service.ts'
|
|
export type { IWorkspaces, WorkspaceSource } from './service.ts'
|
|
export type { WorkspaceId, WorkspaceView } from '../types.ts'
|
|
|
|
type WorkspaceBaselineFrame = Extract<WorkspaceFollowFrame, { type: 'baseline' }>
|
|
|
|
/** Gateway-owned snapshot stream configured for Workspace state. */
|
|
export type WorkspaceStateStream = RemoteSnapshotStream<
|
|
WorkspaceBaselineFrame,
|
|
WorkspaceFollowIncrement
|
|
>
|
|
|
|
declare module '@deepseek-ai/cordis' {
|
|
interface Context {
|
|
/** React-free Client Workspace state and commands. */
|
|
workspaces: import('./service.ts').IWorkspaces
|
|
}
|
|
}
|
|
|
|
/** Required Client Remote services. */
|
|
export const inject = ['remote', 'remote.workspace']
|
|
|
|
/**
|
|
* Install Client Workspace state, commands, and reconnecting follow control.
|
|
* @param ctx - Client root Context.
|
|
*/
|
|
export function apply(ctx: Context): void {
|
|
const model = new ClientWorkspaceModel(ctx.remote.workspace)
|
|
new WorkspaceController(ctx, model)
|
|
const control = createWorkspaceStateStream(ctx.remote, {
|
|
accept: model,
|
|
carrierFailed: () => { model.handleCarrierFailure() },
|
|
failed: (error) => { model.handleStreamFailure(error) },
|
|
})
|
|
control.start()
|
|
ctx.effect(
|
|
() => async () => { await control.dispose() },
|
|
'workspace-controller.client.control',
|
|
)
|
|
}
|
|
|
|
/** Domain sinks used by the Workspace state stream. */
|
|
export interface WorkspaceStateStreamOptions {
|
|
/** Destinations for decoded Workspace state operations. */
|
|
readonly accept: WorkspaceFollowSink
|
|
/** Observe a retryable carrier loss before reconnection. */
|
|
readonly carrierFailed?: (error: RemoteStreamCarrierError) => void
|
|
/** Publish a terminal business or protocol failure. */
|
|
readonly failed: (error: unknown) => void
|
|
}
|
|
|
|
/**
|
|
* Create the reconnecting Workspace state stream.
|
|
* @param remote - Client Remote face carrying the Workspace namespace and the stream factory.
|
|
* @param options - Workspace state destinations.
|
|
* @returns an unstarted stream owned by the Client Workspace runtime.
|
|
*/
|
|
export function createWorkspaceStateStream(
|
|
remote: ClientRemote,
|
|
options: WorkspaceStateStreamOptions,
|
|
): WorkspaceStateStream {
|
|
const stream = remote.$stream<WorkspaceFollowFrame>({
|
|
name: 'Workspace state stream',
|
|
open: signal => remote.workspace.follow(signal),
|
|
ended: accepted => accepted
|
|
? new RemoteStreamCarrierError('Workspace state stream ended without a terminal result')
|
|
: new Error('Workspace state stream ended before its opening snapshot'),
|
|
...(options.carrierFailed === undefined ? {} : { carrierFailed: options.carrierFailed }),
|
|
})
|
|
return new RemoteSnapshotStream<WorkspaceBaselineFrame, WorkspaceFollowIncrement>(stream, {
|
|
name: 'Workspace state stream',
|
|
isSnapshot: (frame): frame is WorkspaceBaselineFrame => frame.type === 'baseline',
|
|
replace: (frame) => { options.accept.replaceBaseline(frame.value) },
|
|
update: (frame) => { acceptIncrement(options.accept, frame) },
|
|
failed: options.failed,
|
|
})
|
|
}
|
|
|
|
function acceptIncrement(accept: WorkspaceFollowSink, frame: WorkspaceFollowIncrement): void {
|
|
switch (frame.type) {
|
|
case 'upsert':
|
|
accept.upsertView(frame.workspace)
|
|
return
|
|
case 'remove':
|
|
accept.removeView(frame.workspaceId)
|
|
return
|
|
case 'order':
|
|
accept.replaceOrder(frame.workspaceIds)
|
|
return
|
|
case 'archived':
|
|
accept.replaceArchived(frame.archivedSessionIds)
|
|
return
|
|
/* v8 ignore next -- the generated Remote codec validates this closed union */
|
|
default:
|
|
return assertNever(frame)
|
|
}
|
|
}
|
|
|
|
/* v8 ignore next 3 -- closed-union backstop after generated Remote validation */
|
|
function assertNever(value: never): never {
|
|
throw new Error(`unreachable Workspace increment: ${JSON.stringify(value)}`)
|
|
}
|