fix(gui): harden multimodal image attachments

This commit is contained in:
Yichen Jiang
2026-07-23 19:38:37 +08:00
parent eea595fcb4
commit 580e05b794
61 changed files with 1700 additions and 214 deletions

View File

@@ -8,6 +8,7 @@
export type {
ApiProxy, SessionsApi, SessionSummary, PromptContentPart, HostApi, EventsApi, MuxFrame, HostFrame,
ApprovalResponsePayload, QuestionResponsePayload, HistoryEntry, ToolEventView,
ResponseValue,
} from '@deepseek-ai/dsh-host-apiproxy/api'
export type { ToolCallView, ToolResultView } from '@deepseek-ai/dsh-tools/presentation'
export type {
@@ -20,6 +21,9 @@ export type { IApiClient } from '@deepseek-ai/dsh-host-apiproxy/client'
export type { SessionId, SessionEvent } from '@deepseek-ai/dsh-session/types'
export type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm/types'
/** Successful value returned by the connection-generation host handshake. */
export type HostDescription = import('@deepseek-ai/dsh-host-apiproxy/api').ResponseValue<'host.describe'>
import type { RpcResponse, RpcResult } from '@deepseek-ai/dsh-host-apiproxy/api'
/**

View File

@@ -1,4 +1,4 @@
import type { IApiClient, HostFrame, MuxFrame, RpcRequest } from './api.ts'
import type { HostDescription, IApiClient, HostFrame, MuxFrame, RpcRequest } from './api.ts'
/** Reconnect/backoff tunables (deployment-varying — no hardcoded tunables; web-cordis §B.1 lists
* these as the future `ctx.connection` plugin Config). All fields optional; defaults below. */
@@ -44,6 +44,8 @@ export type ConnectionState = 'connected' | 'reconnecting'
export interface ConnectionSinks {
onMuxEnvelope?: (envelope: RpcRequest<MuxFrame>) => void
onHostEnvelope?: (envelope: RpcRequest<HostFrame>) => void
/** Latest successful host capability snapshot for this connection generation. */
onDescription?: (description: HostDescription) => void
/** After each connection generation is established (both streams open + describe succeeded), first connect included. */
onConnected?: () => void
/** Coarse state transitions (deduplicated: fires only on change). The initial pre-connect
@@ -131,13 +133,18 @@ export class ConnectionController {
// subscribed baseline. The timeout guards against a carrier that never fires onOpen
// (see ConnectionConfig.streamOpenTimeoutMs).
const timeout = new AbortController()
await Promise.all([
const [description] = await Promise.all([
this.api.host.describe({}),
Promise.race([streamsOpen, sleep(this.config.streamOpenTimeoutMs, timeout.signal)]),
])
timeout.abort()
const descriptionResult = description.result
if (!descriptionResult.ok) {
throw new Error(`host.describe failed: ${descriptionResult.error.code}: ${descriptionResult.error.message}`)
}
if (ac.signal.aborted) throw new Error('generation aborted during readiness handshake')
this.attempt = 0
this.callSink(() => { this.sinks.onDescription?.(descriptionResult.value) })
this.emitState('connected')
this.callSink(this.sinks.onConnected)
} catch {

View File

@@ -19,7 +19,7 @@ export type {
ToolCallView, ToolResultView,
RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,
ClientRequest, ServerResponse, ServerRequest, ClientResponse, RpcMessage, RpcReceipt,
IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk,
HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk,
} from './api.ts'
export { RpcId, AbstractApiClient, transportError } from './api.ts'