mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
refactor(api): converge the Remote failure vocabulary and client surface
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.
This commit is contained in:
@@ -7,40 +7,15 @@
|
||||
* @module @deepseek-ai/dsh-client-ui-message-feedback/client/controller
|
||||
*/
|
||||
|
||||
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol'
|
||||
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
||||
import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import type { MessageId } from '@deepseek-ai/dsh-client-connection/client'
|
||||
import type { MessageId } from '@deepseek-ai/dsh-api-remotes/client'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||
import type {
|
||||
MessageFeedbackDeleteResult,
|
||||
MessageFeedbackItem,
|
||||
MessageFeedbackListResult,
|
||||
MessageFeedbackPutResult,
|
||||
MessageFeedbackRating,
|
||||
} from '@deepseek-ai/dsh-message-feedback/types'
|
||||
|
||||
/**
|
||||
* The three Remote calls this controller needs. The generated face wraps every
|
||||
* business result in {@link RemoteResult}: a carrier failure arrives as the
|
||||
* `ok: false` branch rather than a rejection, so this controller reads one
|
||||
* envelope and never wraps a call to recover a transport error.
|
||||
*/
|
||||
export interface MessageFeedbackRemote {
|
||||
list: (request: { sessionId: SessionId }) => Promise<RemoteResult<MessageFeedbackListResult>>
|
||||
put: (request: {
|
||||
sessionId: SessionId
|
||||
messageId: MessageId
|
||||
rating: MessageFeedbackRating
|
||||
note?: string
|
||||
ifVersion: MessageFeedbackItem['version'] | null
|
||||
}) => Promise<RemoteResult<MessageFeedbackPutResult>>
|
||||
delete: (request: {
|
||||
sessionId: SessionId
|
||||
messageId: MessageId
|
||||
ifVersion: MessageFeedbackItem['version']
|
||||
}) => Promise<RemoteResult<MessageFeedbackDeleteResult>>
|
||||
}
|
||||
|
||||
/** Load state of the one list read that seeds every per-message control. */
|
||||
export type MessageFeedbackStatus = 'cold' | 'loading' | 'ready' | 'error'
|
||||
|
||||
@@ -111,11 +86,11 @@ export class MessageFeedbackController implements HostObservable<MessageFeedback
|
||||
private disposed = false
|
||||
|
||||
/**
|
||||
* @param remote - the messageFeedback Remote namespace.
|
||||
* @param ctx - the browser plugin context carrying the messageFeedback Remote namespace.
|
||||
* @param sessionId - Session owning every addressed assistant message.
|
||||
*/
|
||||
constructor(
|
||||
private readonly remote: MessageFeedbackRemote,
|
||||
private readonly ctx: ClientContext,
|
||||
private readonly sessionId: SessionId,
|
||||
) {}
|
||||
|
||||
@@ -243,7 +218,7 @@ export class MessageFeedbackController implements HostObservable<MessageFeedback
|
||||
note: string | undefined,
|
||||
observed: MessageFeedbackItem | undefined,
|
||||
): Promise<MessageFeedbackActionResult> {
|
||||
const carried = await this.remote.put({
|
||||
const carried = await this.ctx.remote.messageFeedback.put({
|
||||
sessionId: this.sessionId,
|
||||
messageId,
|
||||
rating,
|
||||
@@ -265,7 +240,7 @@ export class MessageFeedbackController implements HostObservable<MessageFeedback
|
||||
messageId: MessageId,
|
||||
observed: MessageFeedbackItem,
|
||||
): Promise<MessageFeedbackActionResult> {
|
||||
const carried = await this.remote.delete({
|
||||
const carried = await this.ctx.remote.messageFeedback.delete({
|
||||
sessionId: this.sessionId,
|
||||
messageId,
|
||||
ifVersion: observed.version,
|
||||
@@ -288,34 +263,26 @@ export class MessageFeedbackController implements HostObservable<MessageFeedback
|
||||
|
||||
/** Fetch the whole sidecar and publish it as the seeded view. */
|
||||
private async load(): Promise<MessageFeedbackActionResult> {
|
||||
try {
|
||||
const carried = await this.remote.list({ sessionId: this.sessionId })
|
||||
if (this.disposed) return OK
|
||||
if (!carried.ok) {
|
||||
this.publish({ status: 'error', items: this.view.items, error: carried.error.message })
|
||||
return carrierFailure(carried.error)
|
||||
}
|
||||
const result = carried.value
|
||||
if (!result.ok) {
|
||||
this.publish({ status: 'error', items: this.view.items, error: describe(result.error.code) })
|
||||
return fail(result.error.code)
|
||||
}
|
||||
const items = new Map<MessageId, MessageFeedbackItem>()
|
||||
for (const item of result.value.items) items.set(item.messageId, item)
|
||||
this.publish({ status: 'ready', items, error: null })
|
||||
return OK
|
||||
} catch (error) {
|
||||
if (this.disposed) return OK
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
this.publish({ status: 'error', items: this.view.items, error: message })
|
||||
return { ok: false, error: { code: 'transport', message } }
|
||||
const carried = await this.ctx.remote.messageFeedback.list({ sessionId: this.sessionId })
|
||||
if (this.disposed) return OK
|
||||
if (!carried.ok) {
|
||||
this.publish({ status: 'error', items: this.view.items, error: carried.error.message })
|
||||
return carrierFailure(carried.error)
|
||||
}
|
||||
const result = carried.value
|
||||
if (!result.ok) {
|
||||
this.publish({ status: 'error', items: this.view.items, error: describe(result.error.code) })
|
||||
return fail(result.error.code)
|
||||
}
|
||||
const items = new Map<MessageId, MessageFeedbackItem>()
|
||||
for (const item of result.value.items) items.set(item.messageId, item)
|
||||
this.publish({ status: 'ready', items, error: null })
|
||||
return OK
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize one mutation behind this Session's prior mutation so queued
|
||||
* operations always compare against the committed version, and translate a
|
||||
* transport throw into the same settled shape the controls already render.
|
||||
* operations always compare against the committed version.
|
||||
*/
|
||||
private mutate(
|
||||
operation: () => Promise<MessageFeedbackActionResult>,
|
||||
@@ -331,17 +298,7 @@ export class MessageFeedbackController implements HostObservable<MessageFeedback
|
||||
// oxlint-disable-next-line typescript/no-unnecessary-condition -- dispose() can run during the await.
|
||||
if (this.disposed) return DISPOSED
|
||||
}
|
||||
try {
|
||||
return await operation()
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
code: 'transport',
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
}
|
||||
}
|
||||
return await operation()
|
||||
}
|
||||
const result = this.operationTail.then(guarded, guarded)
|
||||
// `guarded` settles every carrier and business failure as a
|
||||
|
||||
@@ -25,7 +25,7 @@ import type { MessageFeedbackInjected } from './slots.ts'
|
||||
import { en, zh } from './locales.ts'
|
||||
|
||||
export type {
|
||||
MessageFeedbackActionResult, MessageFeedbackStatus, MessageFeedbackView, MessageFeedbackRemote,
|
||||
MessageFeedbackActionResult, MessageFeedbackStatus, MessageFeedbackView,
|
||||
} from './controller.ts'
|
||||
export type { MessageFeedbackActionProps, MessageFeedbackInjected } from './slots.ts'
|
||||
export type { MessageFeedbackKey } from './locales.ts'
|
||||
@@ -48,7 +48,7 @@ export function apply(ctx: ClientContext): void {
|
||||
const controllerFor = (sessionId: SessionId): MessageFeedbackController => {
|
||||
let controller = controllers.get(sessionId)
|
||||
if (controller === undefined) {
|
||||
controller = new MessageFeedbackController(ctx.remote.messageFeedback, sessionId)
|
||||
controller = new MessageFeedbackController(ctx, sessionId)
|
||||
controllers.set(sessionId, controller)
|
||||
}
|
||||
return controller
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
HostObservable, InjectFace, PropsLocale, PropsRuntime,
|
||||
} from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import type { MessageId } from '@deepseek-ai/dsh-client-connection/client'
|
||||
import type { MessageId } from '@deepseek-ai/dsh-api-remotes/client'
|
||||
import type { MessageFeedbackRating } from '@deepseek-ai/dsh-message-feedback/types'
|
||||
// Type-only: pulls this package's LocaleNamespaceMap merge (the 'feedback' seat).
|
||||
import type {} from './locales.ts'
|
||||
|
||||
Reference in New Issue
Block a user