fix(headless): dsh run is a direct core front door

This commit is contained in:
Tianyi Cui
2026-08-09 12:13:58 +08:00
parent 772c580ee1
commit 9d5eb37638
159 changed files with 1508 additions and 1042 deletions
@@ -14,7 +14,7 @@ import {
type KeyboardEvent, type FocusEvent,
} from 'react'
import clsx from 'clsx'
import type { ModelReasoningEffort, ModelTarget } from '@deepseek-ai/dsh-client-connection/client'
import type { ModelReasoningEffort, ModelSelection } from '@deepseek-ai/dsh-client-connection/client'
import {
IconCheckOutline16, IconChevronDownOutline14, IconChevronRightOutline14,
} from '@deepseek-ai/dsh-client-ui-primitives'
@@ -58,17 +58,17 @@ export function ModelSelect(
group.models.map(model => ({
group,
model,
target: {
selection: {
provider: group.id,
model: model.id,
...model.reasoning?.defaultEffort === undefined
? {}
: { reasoningEffort: model.reasoning.defaultEffort },
} satisfies ModelTarget,
} satisfies ModelSelection,
}))), [state.groups])
const selectedIndex = state.current === null
? -1
: choices.findIndex(c => c.target.provider === state.current?.provider && c.target.model === state.current.model)
: choices.findIndex(c => c.selection.provider === state.current?.provider && c.selection.model === state.current.model)
const currentChoice = choices[selectedIndex]
const reasoning = currentChoice?.model.reasoning
const effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort
@@ -148,12 +148,12 @@ export function ModelSelect(
close()
}
const choose = (target: ModelTarget): void => {
if (state.current?.provider === target.provider && state.current.model === target.model) {
const choose = (selection: ModelSelection): void => {
if (state.current?.provider === selection.provider && state.current.model === selection.model) {
close(true)
return
}
void select(target).then((accepted) => {
void select(selection).then((accepted) => {
if (accepted && rootRef.current !== null) close(true)
})
}
@@ -164,12 +164,12 @@ export function ModelSelect(
close(true)
return
}
const target: ModelTarget = {
const selection: ModelSelection = {
provider: state.current.provider,
model: state.current.model,
...effort === undefined ? {} : { reasoningEffort: effort },
}
void select(target).then((accepted) => {
void select(selection).then((accepted) => {
if (accepted && rootRef.current !== null) close(true)
})
}
@@ -6,17 +6,17 @@
* either entry is what the other shows next.
*/
import type {
IApiClient, ModelCatalogFailure, ModelProviderGroup, ModelTarget, SessionId, SessionModels,
IApiClient, ModelCatalogFailure, ModelProviderGroup, ModelSelection, SessionId, SessionModels,
} from '@deepseek-ai/dsh-client-connection/client'
import type { SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
/** Directory snapshot both entries render from. */
export interface ModelDirectoryState {
/** Target the host reports for the next assembled step; null before the first load. */
current: ModelTarget | null
/** Model selection the host reports for the next assembled step; null before the first load. */
current: ModelSelection | null
/**
* Whether an adapter serves the current target's route, as the host reports
* Whether an adapter serves the current selection's provider, as the host reports
* it — null before the first load, which is NOT the same as blocked. Read
* this rather than "current matches no group": catalog membership is
* advisory, so a route serving a model it stopped advertising is missing
@@ -57,7 +57,7 @@ export class ModelDirectory {
/**
* Refresh the advisory directory (both entries call this on open).
* Failure preserves the last good groups and current target.
* Failure preserves the last good groups and current selection.
* @returns the fresh directory value.
*/
async load(): Promise<SessionModels> {
@@ -86,22 +86,22 @@ export class ModelDirectory {
}
/**
* Select the complete provider/model/reasoning target (both entries submit through here). Success
* Select the complete provider/model/reasoning selection (both entries submit through here). Success
* updates the shared current; failure surfaces on the store and throws so
* each entry's own retry surface engages.
* @param target - provider, provider-owned model id, and optional adapter-owned effort.
*/
async select(target: ModelTarget): Promise<void> {
* @param selection - provider, provider-owned model id, and optional adapter-owned effort.
*/
async select(selection: ModelSelection): Promise<void> {
this.assertAvailable()
const generation = ++this.generation
this.store.update((s) => { s.status = 'selecting'; s.error = null })
const { result } = await this.sessions.selectModel({
sessionId: this.sessionId,
provider: target.provider,
model: target.model,
...target.reasoningEffort === undefined
provider: selection.provider,
model: selection.model,
...selection.reasoningEffort === undefined
? {}
: { reasoningEffort: target.reasoningEffort },
: { reasoningEffort: selection.reasoningEffort },
})
if (this.disposed || generation !== this.generation) {
if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`)
@@ -124,7 +124,7 @@ export class ModelDirectory {
/**
* Drop the previous Host generation's projection and repull it. Clearing
* first prevents an unconsumed process-local selection from being displayed
* while the restarted Host has restored the last logged request target.
* while the restarted Host has restored the last logged model selection.
*/
resetConnected(): void {
if (this.disposed) return
+10 -10
View File
@@ -4,14 +4,14 @@
* contribution and the composer's named `conversation.input.model` seat both
* load the session's provider-grouped advisory directory (`session.models`)
* and submit through `session.selectModel` via the same directory instance,
* so the host-reported current target is the single fact both surfaces echo
* so the host-reported current selection is the single fact both surfaces echo
* — a switch made in either entry is what the other shows next. Failures
* ride each entry's own retry surface (popup shell error/retry; seat menu
* inline error) without forking the state. Addressed subagent sessions expose
* neither entry because those Agent-bound RPCs would activate persisted
* history outside the direct-parent continuation seam.
*/
import type { ModelTarget, SessionModels } from '@deepseek-ai/dsh-client-connection/client'
import type { ModelSelection, SessionModels } from '@deepseek-ai/dsh-client-connection/client'
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
import type { CommandServiceContract, SelectOption } from '@deepseek-ai/dsh-client-ui-command/client'
// Type-only: pulls the ui-conversation SlotMap merge (the input.model seat).
@@ -68,13 +68,13 @@ function optionsOf(directory: SessionModels, t: TranslateNS<'model'>): SelectOpt
}
/**
* Resolve a picked row back to its target by matching against the loaded
* Resolve a picked row back to its model selection by matching against the loaded
* groups (the same data the rows were built from — ids stay opaque).
* @param state - the session's directory snapshot.
* @param id - the picked row id.
* @returns the row's target, or undefined for failure rows / stale ids.
* @returns the row's model selection, or undefined for failure rows / stale ids.
*/
function targetOf(state: ModelDirectoryState, id: string): ModelTarget | undefined {
function selectionOf(state: ModelDirectoryState, id: string): ModelSelection | undefined {
for (const group of state.groups) {
for (const model of group.models) {
if (rowId(group.id, model.id) !== id) continue
@@ -139,11 +139,11 @@ export function apply(ctx: ClientContext): void {
throw new Error('model selection is unavailable for addressed subagent sessions')
}
const directory = models.directoryFor(session.sessionId)
const target = targetOf(directory.store.getSnapshot(), option.id)
if (target === undefined) {
const selection = selectionOf(directory.store.getSnapshot(), option.id)
if (selection === undefined) {
throw new Error('this provider\'s catalog failed to load — pick a model from a loaded group')
}
await directory.select(target)
await directory.select(selection)
},
},
}), 'ui-model: /model contribution')
@@ -165,8 +165,8 @@ export function apply(ctx: ClientContext): void {
load: () => {
if (available) directory.load().catch(() => { /* surfaced on the store */ })
},
select: (target: ModelTarget) => available
? directory.select(target).then(() => true, () => false)
select: (selection: ModelSelection) => available
? directory.select(selection).then(() => true, () => false)
: Promise.resolve(false),
}
},
+4 -4
View File
@@ -4,7 +4,7 @@
* entry; this package only contributes the single occupant, so no SlotMap
* merge lives here.
*/
import type { ModelTarget } from '@deepseek-ai/dsh-client-connection/client'
import type { ModelSelection } from '@deepseek-ai/dsh-client-connection/client'
import type { SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
import type { ModelDirectoryState } from './directory.ts'
@@ -17,9 +17,9 @@ export interface ModelSelectInjected {
/** Refresh the advisory directory (fire-and-forget; errors land on the store). */
load: () => void
/**
* Select a complete provider/model/reasoning target through the shared route.
* @param target - model target and optional adapter-owned effort.
* Select a complete provider/model/reasoning selection.
* @param selection - model selection and optional adapter-owned effort.
* @returns whether the host accepted the selection.
*/
select: (target: ModelTarget) => Promise<boolean>
select: (selection: ModelSelection) => Promise<boolean>
}