mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
feat(ui-models): drop the provider-scoped reasoning effort, and red-flag a bad route id
**Reasoning effort leaves the provider cards entirely.** It is a per-MODEL capability and the models under one provider disagree about which levels they accept: setting `anthropic` to `max` made six of its eight models throw UNSUPPORTED_REASONING_EFFORT, and because the catalog build catches per provider, the whole provider vanished from the picker behind one error row. A provider-scoped control can only ever be set to a value some of its models reject. The composer's model picker already offers each model its own levels, and a switch there now records provider, model, and effort together as the next session's default — so the setting has a better home at the right granularity. The profile field stays in `settings.yaml` for a deployment that knows its route; only the control is gone, from both cards and both adapter families. Two `components.spec` cases used the control as the vehicle for their op assertions and now use `baseURL`, which is what they were actually testing. **A rejected Provider ID now reads as a fault.** It shared the neutral hint paragraph with the field's guidance, so the copy telling the user what they got wrong looked like advice. Reuses the existing `.error` style, matching the split the key field already makes.
This commit is contained in:
@@ -14,13 +14,11 @@
|
||||
* and at least one model — are required here rather than at load, so the
|
||||
* failure names the field while the user is still looking at it.
|
||||
*
|
||||
* There is deliberately no reasoning-effort control. A hand-declared model
|
||||
* carries no reasoning capability — pi-ai's installed catalog is what supplies
|
||||
* one, and it has nothing under this route — so a profile effort here makes
|
||||
* `resolveModel` throw UNSUPPORTED_REASONING_EFFORT for every model on the
|
||||
* route, which drops the whole provider out of the model picker. The editor
|
||||
* card hides the control for the same reason once the directory reports the
|
||||
* route as declared.
|
||||
* There is deliberately no reasoning-effort control, here or on the editor
|
||||
* card: effort is a per-MODEL capability, and the models under one provider
|
||||
* disagree about it, so a provider-scoped control can only be set to a value
|
||||
* some of them reject. The composer's model picker offers each model its own
|
||||
* levels instead.
|
||||
*/
|
||||
|
||||
import { useState } from 'react'
|
||||
@@ -206,9 +204,11 @@ export function CustomProviderCard(props: CustomProviderCardProps): ReactNode {
|
||||
onChange={(event) => { setRoute(event.target.value) }}
|
||||
/>
|
||||
</div>
|
||||
<p className={styles['advancedHint']}>
|
||||
{routeInvalid ? t('customRouteInvalid') : routeTaken ? t('customRouteTaken') : t('customRouteHint')}
|
||||
</p>
|
||||
{/* A rejected id reads as a fault, not as guidance — the same split the
|
||||
key field below already makes between its failure and its hint. */}
|
||||
{routeInvalid || routeTaken
|
||||
? <p className={styles['error']}>{t(routeInvalid ? 'customRouteInvalid' : 'customRouteTaken')}</p>
|
||||
: <p className={styles['advancedHint']}>{t('customRouteHint')}</p>}
|
||||
<div className={styles['field']}>
|
||||
<span className={styles['fieldLabel']}>{t('customDisplayName')}</span>
|
||||
<input
|
||||
|
||||
@@ -54,8 +54,6 @@ interface EditorTarget extends ProviderIdentity {
|
||||
settingsPath: readonly string[]
|
||||
/** Writable credential identified under this page's conventional reference. */
|
||||
credentialRef?: string
|
||||
/** Directory passthrough: the owning adapter ships nothing under this route. */
|
||||
declared?: boolean
|
||||
}
|
||||
|
||||
/** Values that vary around the shared provider-editor rendering. */
|
||||
@@ -73,7 +71,6 @@ function renderProviderEditor({ target, ...props }: ProviderEditorRenderProps):
|
||||
provider={target.provider}
|
||||
displayName={target.displayName}
|
||||
settingsPath={target.settingsPath}
|
||||
{...target.declared === undefined ? {} : { declared: target.declared }}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
@@ -140,7 +137,6 @@ function targetOf(row: ProviderRow): EditorTarget {
|
||||
settingsNs: row.entry.settingsNs,
|
||||
settingsPath: row.entry.settingsPath,
|
||||
...credentialRef === undefined ? {} : { credentialRef },
|
||||
...row.entry.declared === undefined ? {} : { declared: row.entry.declared },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
* a key is entered; a blank key materializes a reference-free profile for
|
||||
* provider-native authentication);
|
||||
* the collapsed 自定义设置 area carries the per-family extras (`baseURL` for
|
||||
* both families, `reasoningEffort` for deepseek / `reasoning` for pi-ai —
|
||||
* withheld for a hand-declared route, whose models have no reasoning
|
||||
* capability to configure — and DeepSeek's id/name/context-window model
|
||||
* catalog). Everything else stays
|
||||
* both families and DeepSeek's id/name/context-window model catalog).
|
||||
* Reasoning effort is deliberately absent: it is a per-MODEL capability, and
|
||||
* the models under one provider disagree about it, so a provider-scoped
|
||||
* control can only be set to a value some of them reject. The composer's
|
||||
* model picker offers each model its own levels; `settings.yaml` keeps the
|
||||
* profile field for a deployment that knows its route. Everything else stays
|
||||
* owned by `settings.yaml`. Profile edits land as minimal `settings.mutate`
|
||||
* path ops against the stored section — the card reads the redacted
|
||||
* descriptor, so it names only the fields it can see and a stored literal
|
||||
@@ -29,14 +31,12 @@ import {
|
||||
import { apiKeyFailure } from './apiKey.ts'
|
||||
import { EditorFooter } from './EditorFooter.tsx'
|
||||
import { ModelListEditor } from './ModelListEditor.tsx'
|
||||
import { EFFORT_FIELD, ReasoningEffortField } from './ReasoningEffortField.tsx'
|
||||
import type { EffortFamily } from './ReasoningEffortField.tsx'
|
||||
import { deriveKeyRef, messageOf } from './store.ts'
|
||||
import type { en } from './locales.ts'
|
||||
import styles from './ModelsSection.module.css'
|
||||
|
||||
/** Per-adapter-family curated field sets (unknown namespaces get the hint alone). */
|
||||
type EditorLayout = EffortFamily | 'unknown'
|
||||
type EditorLayout = 'deepseek' | 'pi-ai' | 'unknown'
|
||||
|
||||
/** The public DeepSeek endpoint shown as the deepseek base-URL placeholder. */
|
||||
const DEEPSEEK_PUBLIC_BASE_URL = 'https://api.deepseek.com'
|
||||
@@ -57,13 +57,6 @@ export interface ProviderEditorProps {
|
||||
api: Pick<IApiClient, 'settings' | 'credentials' | 'llm'>
|
||||
/** Section copy. */
|
||||
t: (key: keyof typeof en) => string
|
||||
/**
|
||||
* Whether the owning adapter knows this route only because configuration
|
||||
* declared it. Such a route's models carry no reasoning capability, so the
|
||||
* effort control is withheld; absent means the adapter draws no such
|
||||
* distinction and the control shows.
|
||||
*/
|
||||
declared?: boolean
|
||||
/** Disable writes (read-only settings provider). */
|
||||
readOnly: boolean
|
||||
/** Close the editor; `changed` reports whether an Apply committed. */
|
||||
@@ -303,8 +296,7 @@ export function ProviderEditor(props: ProviderEditorProps): ReactNode {
|
||||
* family as a parameter is what makes `EFFORT_FIELD` total here: an
|
||||
* unknown namespace never reaches this body.
|
||||
*/
|
||||
const curatedFields = (family: EffortFamily): ReactNode => {
|
||||
const effortField = EFFORT_FIELD[family]
|
||||
const curatedFields = (family: 'deepseek' | 'pi-ai'): ReactNode => {
|
||||
const customModels = getPath(draft, ['models'])
|
||||
const modelsOverridden = hasPath(draft, ['models'])
|
||||
const models = modelDrafts(modelsOverridden ? customModels : inheritedModels())
|
||||
@@ -361,21 +353,6 @@ export function ProviderEditor(props: ProviderEditorProps): ReactNode {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* A hand-declared route's models carry no reasoning capability
|
||||
(pi-ai's installed catalog is what supplies one, and it has
|
||||
nothing under such a route), so a profile effort would make
|
||||
`resolveModel` throw for every model on it and drop the whole
|
||||
provider out of the picker. Offering the control at all would
|
||||
be offering a way to break the route. */}
|
||||
{props.declared === true ? null : (
|
||||
<ReasoningEffortField
|
||||
family={family}
|
||||
value={stringAt(draft, effortField) ?? ''}
|
||||
onChange={(effort) => { setField(effortField, effort) }}
|
||||
t={t}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
{/* Both families edit the same rows through the same contract; only
|
||||
the extras differ — DeepSeek's inherited capacities, pi-ai's
|
||||
endpoint interrogation. */}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* The provider-level reasoning-effort select: the profile's own default
|
||||
* effort, applied to every model on the route unless a request names one. The
|
||||
* empty option means "inherit", which on the wire is the field being absent
|
||||
* rather than an empty string.
|
||||
*
|
||||
* It carries the per-family vocabulary and field name so the editor's two
|
||||
* layouts cannot spell them differently. Only routes the adapter ships get
|
||||
* this control at all — a hand-declared model has no reasoning capability to
|
||||
* configure, and a profile effort over one makes its whole route fail to
|
||||
* resolve — so the create card renders nothing here by construction.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import type { en } from './locales.ts'
|
||||
import styles from './ModelsSection.module.css'
|
||||
|
||||
/** The adapter families that expose a provider-level effort, and their vocabularies. */
|
||||
export type EffortFamily = 'deepseek' | 'pi-ai'
|
||||
|
||||
/** Reasoning vocabularies per family; the empty option means "inherit". */
|
||||
export const EFFORT_CHOICES: Record<EffortFamily, readonly string[]> = {
|
||||
deepseek: ['off', 'high', 'max'],
|
||||
'pi-ai': ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'],
|
||||
}
|
||||
|
||||
/** The profile key each family's effort lives under. */
|
||||
export const EFFORT_FIELD: Record<EffortFamily, string> = {
|
||||
deepseek: 'reasoningEffort',
|
||||
'pi-ai': 'reasoning',
|
||||
}
|
||||
|
||||
/** Props of {@link ReasoningEffortField}. */
|
||||
export interface ReasoningEffortFieldProps {
|
||||
/** Which vocabulary to offer. */
|
||||
family: EffortFamily
|
||||
/** Current value; the empty string is the inherit option. */
|
||||
value: string
|
||||
/** Receives the chosen effort, or undefined for inherit. */
|
||||
onChange: (effort: string | undefined) => void
|
||||
/** Section copy. */
|
||||
t: (key: keyof typeof en) => string
|
||||
/** Disable the control (busy or read-only). */
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the provider-level reasoning-effort select.
|
||||
* @param props - family vocabulary, current value, change sink, copy, and disabled state.
|
||||
* @returns the labelled select.
|
||||
*/
|
||||
export function ReasoningEffortField(
|
||||
{ family, value, onChange, t, disabled }: ReasoningEffortFieldProps,
|
||||
): ReactNode {
|
||||
return (
|
||||
<div className={styles['field']}>
|
||||
<span className={styles['fieldLabel']}>{t('effort')}</span>
|
||||
<select
|
||||
className={`${styles['input']} ${styles['selectInput']}`}
|
||||
value={value}
|
||||
aria-label={t('effort')}
|
||||
disabled={disabled}
|
||||
onChange={(event) => { onChange(event.target.value === '' ? undefined : event.target.value) }}
|
||||
>
|
||||
<option value="">{t('effortInherit')}</option>
|
||||
{EFFORT_CHOICES[family].map(choice => (
|
||||
<option key={choice} value={choice}>{choice}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -35,8 +35,6 @@ export const en = {
|
||||
customized: 'Customized settings',
|
||||
baseUrl: 'Base URL',
|
||||
baseUrlDefault: 'Provider default',
|
||||
effort: 'Reasoning effort',
|
||||
effortInherit: 'Default',
|
||||
models: 'Models',
|
||||
modelsInherited: 'Using the adapter defaults',
|
||||
modelsCustomized: 'Customized model catalog',
|
||||
@@ -130,8 +128,6 @@ export const zh: typeof en = {
|
||||
customized: '自定义设置',
|
||||
baseUrl: 'API 地址',
|
||||
baseUrlDefault: '提供方默认',
|
||||
effort: '推理强度',
|
||||
effortInherit: '默认',
|
||||
models: '模型目录',
|
||||
modelsInherited: '正在使用适配器默认模型',
|
||||
modelsCustomized: '已自定义模型目录',
|
||||
|
||||
Reference in New Issue
Block a user