fix(headless): dsh run is a direct core front door
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,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>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user