@jcy2387/dsh-models-input-modalities 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs","names":["NS","useState","useCallback","css","Button"],"sources":["../src/client/controller.ts","../src/image-input.ts","../src/client/ImageInputCard.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/** Settings reads and writes for the image-input card, over the settings Remote. */\n\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type { SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client'\nimport type { ProviderDirectoryEntry } from '@deepseek-ai/dsh-client-ui-settings-models/client'\nimport type { JsonValue } from '@deepseek-ai/dsh-util-values'\nimport type { ModelRow } from '../image-input.ts'\n\n/** The settings namespace every pi-ai provider card addresses. */\nconst NS = 'llm-pi-ai'\n\n/** What one load answers for a provider card. */\nexport interface ImageInputView {\n /** Whether the deployment accepts settings writes at all. */\n writable: boolean\n /** Revision fence the next save must carry. */\n revision: number\n /** The rows to show: the user layer's when it owns the list, else the effective ones. */\n models: readonly ModelRow[]\n /** Whether the shown rows already live in the user layer. */\n fromUser: boolean\n}\n\n/** What one save answered. */\nexport type ImageInputSaveOutcome =\n | { readonly kind: 'written'; readonly revision: number }\n | { readonly kind: 'conflict'; readonly message: string }\n | { readonly kind: 'refused'; readonly message: string }\n\n/** Read one plain-object path; anything off-path answers undefined. */\nfunction at(source: unknown, path: readonly string[]): unknown {\n let current: unknown = source\n for (const key of path) {\n if (typeof current !== 'object' || current === null || Array.isArray(current)) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n}\n\n/** Coerce a stored models value into open row records. */\nfunction rows(value: unknown): ModelRow[] {\n return Array.isArray(value)\n ? value.map(entry =>\n typeof entry === 'object' && entry !== null && !Array.isArray(entry) ? entry as ModelRow : {})\n : []\n}\n\n/** Joins the settings Remote's document view and fenced writes for one card. */\nexport class ImageInputController {\n /**\n * @param ctx - the plugin's client context, which declares `remote.settings`\n * in its own `inject`.\n */\n constructor(private readonly ctx: ClientContext) {}\n\n /**\n * Read one provider's model rows and the revision fence for writing them.\n * @param entry - the card's directory row (its settings address names the profile).\n * @returns the view, or undefined when the settings face or namespace is unavailable.\n */\n async load(entry: ProviderDirectoryEntry): Promise<ImageInputView | undefined> {\n const response = await this.ctx.remote.settings.describe()\n if (!response.ok) return undefined\n const namespace = response.value.namespaces.find(view => view.ns === NS)\n if (namespace === undefined) return undefined\n const path = [...entry.settingsPath, 'models']\n const userModel = at(namespace.user, path)\n const fromUser = Array.isArray(userModel)\n return {\n writable: response.value.writable,\n revision: namespace.revision,\n models: rows(fromUser ? userModel : at(namespace.value, path)),\n fromUser,\n }\n }\n\n /**\n * Write the rows back as the profile's whole `models` array, under the fence\n * the load answered. The array is replaced by value — the adapter's own\n * semantics — so untouched rows ride along exactly as stored.\n * @param entry - the card's directory row.\n * @param models - the edited rows.\n * @param revision - the fence from the load this draft was opened at.\n * @returns the write outcome the card renders from.\n */\n async save(\n entry: ProviderDirectoryEntry,\n models: readonly ModelRow[],\n revision: number,\n ): Promise<ImageInputSaveOutcome> {\n // The rows came out of a stored JSON document and the edit only ever sets\n // string arrays, so the array is JSON by construction.\n const value = models as unknown as JsonValue\n const ops: SettingsPathOpView[] = [{ op: 'set', path: [...entry.settingsPath, 'models'], value }]\n const response = await this.ctx.remote.settings.mutate(NS, ops, revision)\n if (response.ok) return { kind: 'written', revision: response.value.revision }\n const { code, message } = response.error\n return code === 'settings/conflict' ? { kind: 'conflict', message } : { kind: 'refused', message }\n }\n}\n","/** Pure row helpers for the per-model image-input claim. */\n\n/** One configured model row, structurally open so hidden fields survive an edit. */\nexport type ModelRow = Record<string, unknown>\n\n/** The three image-input states a row's select offers. */\nexport type ImageInputChoice = 'inherit' | 'text' | 'image'\n\n/**\n * The choice a row's stored `input` displays. Absent and empty mean the same\n * inheritance — the installed catalog's modalities, then the route's\n * `defaultInput` — and any list naming `image` is the image-capable claim\n * however else it is spelled.\n * @param row - one stored model row.\n * @returns the choice the row's select shows.\n */\nexport function imageInputChoice(row: ModelRow): ImageInputChoice {\n const value = row['input']\n if (!Array.isArray(value) || value.length === 0) return 'inherit'\n return value.includes('image') ? 'image' : 'text'\n}\n\n/**\n * The row with one choice applied: `inherit` removes the field, the others\n * store exactly the modality list the adapter reads. Every other field,\n * including ones this card never shows, survives.\n * @param row - the row to patch.\n * @param choice - the selected state.\n * @returns a new row carrying the choice.\n */\nexport function withImageInput(row: ModelRow, choice: ImageInputChoice): ModelRow {\n const next = { ...row }\n delete next['input']\n if (choice === 'text') next['input'] = ['text']\n else if (choice === 'image') next['input'] = ['text', 'image']\n return next\n}\n\n/**\n * Read a select's submitted value. The DOM hands over a bare string, so an\n * unrecognized one is refused rather than cast into the union.\n * @param value - the submitted option value.\n * @returns the choice, or undefined for anything else.\n */\nexport function parseImageInputChoice(value: string): ImageInputChoice | undefined {\n return value === 'inherit' || value === 'text' || value === 'image' ? value : undefined\n}\n\n/**\n * The row's model id for labels.\n * @param row - one stored model row.\n * @param index - the row's zero-based position.\n * @returns the id, or a positional name for an id-less row.\n */\nexport function rowId(row: ModelRow, index: number): string {\n const id = row['id']\n return typeof id === 'string' && id.length > 0 ? id : `#${String(index + 1)}`\n}\n","/**\n * One pi-ai provider card's image-input fold: the per-model modality claim the\n * Models page's own form does not carry. The fold loads the provider's stored\n * rows when first opened, edits them locally, and writes the whole `models`\n * array back under the revision fence the load answered — the same array\n * semantics the page's own cards use.\n */\n\nimport { useCallback, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport { Button } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { ProviderDirectoryEntry } from '@deepseek-ai/dsh-client-ui-settings-models/client'\nimport type { ImageInputSaveOutcome, ImageInputView } from './controller.ts'\nimport { imageInputChoice, parseImageInputChoice, rowId, withImageInput } from '../image-input.ts'\nimport type { ImageInputChoice, ModelRow } from '../image-input.ts'\nimport css from './styles.module.css'\n\n/** The registration-side face this card receives. */\nexport interface ImageInputFace {\n /** Read the provider's rows and revision fence; undefined when the settings face is unavailable. */\n loadModels(entry: ProviderDirectoryEntry): Promise<ImageInputView | undefined>\n /** Write the rows back under the fence the load answered. */\n saveModels(\n entry: ProviderDirectoryEntry,\n models: readonly ModelRow[],\n revision: number,\n ): Promise<ImageInputSaveOutcome>\n}\n\n/** Props the provider-card slot binds. */\nexport type ImageInputCardProps =\n PropsRuntime<'settings.models.provider-card'>\n & PropsLocale<'settings.models.imageInput'>\n & InjectFace<ImageInputFace>\n\n/** Lifecycle of one fold: closed, loading, editable, or writing. */\ntype Status = 'idle' | 'loading' | 'ready' | 'saving'\n\n/**\n * Render the image-input fold of one provider card.\n * @param props - the card's directory row plus the bound face and copy.\n * @returns the fold, or nothing while the provider is still a dormant row.\n */\nexport function ImageInputCard(props: ImageInputCardProps): ReactNode {\n const { provider, configured, t, loadModels, saveModels } = props\n const [status, setStatus] = useState<Status>('idle')\n const [view, setView] = useState<ImageInputView | undefined>(undefined)\n const [rows, setRows] = useState<readonly ModelRow[]>([])\n const [failure, setFailure] = useState<string | undefined>(undefined)\n const [saved, setSaved] = useState(false)\n\n const reload = useCallback(async (): Promise<void> => {\n setStatus('loading')\n setFailure(undefined)\n setSaved(false)\n const loaded = await loadModels(provider)\n if (loaded === undefined) {\n setView(undefined)\n setFailure(t('loadFailed'))\n setStatus('ready')\n return\n }\n setView(loaded)\n setRows(loaded.models.map(row => ({ ...row })))\n setStatus('ready')\n }, [loadModels, provider, t])\n\n // A dormant directory row has no profile to read models from; the create\n // card dispatches this seat only after the provider is saved anyway.\n if (configured !== true) return null\n\n const dirty = view !== undefined && JSON.stringify(rows) !== JSON.stringify(view.models)\n\n const choose = (index: number, choice: ImageInputChoice): void => {\n setRows(current => current.map((row, at) => at === index ? withImageInput(row, choice) : row))\n }\n\n const submit = async (): Promise<void> => {\n if (view === undefined) return\n setStatus('saving')\n const outcome = await saveModels(provider, rows, view.revision)\n if (outcome.kind === 'written') {\n setView({ ...view, revision: outcome.revision, models: rows.map(row => ({ ...row })), fromUser: true })\n setSaved(true)\n setStatus('ready')\n return\n }\n setFailure(outcome.kind === 'conflict' ? t('conflict') : outcome.message)\n if (outcome.kind === 'conflict') await reload()\n else setStatus('ready')\n }\n\n return (\n <details\n className={css['fold']}\n onToggle={(event) => {\n // Load on first open only: a reopened fold keeps the loaded (possibly\n // just-saved) view, and every write is revision-fenced regardless.\n if (event.currentTarget.open && status === 'idle') void reload()\n }}\n >\n <summary className={css['summary']}>{t('title')}</summary>\n <div className={css['body']}>\n {status === 'loading' ? <p className={css['status']}>{t('loading')}</p> : null}\n {status !== 'loading' && view === undefined\n ? (\n <>\n <p className={css['error']}>{failure ?? t('loadFailed')}</p>\n <div className={css['footer']}>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => { void reload() }}>{t('retry')}</Button>\n </div>\n </>\n )\n : null}\n {status !== 'loading' && view !== undefined\n ? (view.models.length === 0\n ? <p className={css['hint']}>{t('empty')}</p>\n : (\n <>\n {view.fromUser ? null : <p className={css['hint']}>{t('inheritsHint')}</p>}\n {rows.map((row, index) => (\n <label key={index} className={css['row']}>\n <span className={css['rowId']}>{rowId(row, index)}</span>\n <select\n className={css['select']}\n value={imageInputChoice(row)}\n aria-label={`${t('title')} ${rowId(row, index)}`}\n disabled={!view.writable || status === 'saving'}\n onChange={(event) => {\n const next = parseImageInputChoice(event.target.value)\n if (next !== undefined) choose(index, next)\n }}\n >\n <option value=\"inherit\">{t('choiceDefault')}</option>\n <option value=\"text\">{t('choiceText')}</option>\n <option value=\"image\">{t('choiceImage')}</option>\n </select>\n </label>\n ))}\n {failure !== undefined ? <p className={css['error']}>{failure}</p> : null}\n <div className={css['footer']}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={!view.writable || status === 'saving' || !dirty}\n onClick={() => { void submit() }}\n >\n {status === 'saving' ? t('saving') : t('save')}\n </Button>\n {failure !== undefined\n ? (\n <Button variant=\"ghost\" size=\"sm\" disabled={status === 'saving'} onClick={() => { void reload() }}>\n {t('retry')}\n </Button>\n )\n : null}\n {saved ? <p className={css['status']}>{t('saved')}</p> : null}\n {view.writable ? null : <p className={css['hint']}>{t('readOnly')}</p>}\n </div>\n </>\n ))\n : null}\n </div>\n </details>\n )\n}\n","/** Copy dictionaries for the image-input card. */\n\n/** English strings (the key-set source of truth for this pair). */\nexport const en = {\n title: 'Input modalities',\n loading: 'Loading the model list…',\n loadFailed: 'Loading the model configuration failed.',\n retry: 'Retry',\n empty: 'No explicit model list yet — add models in the catalog above, then declare their input modalities here.',\n inheritsHint: 'Showing the inherited model list; saving copies it into your user settings.',\n readOnly: 'The settings document is read-only in this deployment.',\n choiceDefault: 'Provider default',\n choiceText: 'Text only',\n choiceImage: 'Text and image',\n save: 'Save',\n saving: 'Saving…',\n saved: 'Saved. The adapter picks it up on its next request.',\n conflict: 'These settings changed elsewhere while this card was open; the latest values were reloaded.',\n}\n\n/** The settings.models.imageInput namespace key union. */\nexport type ImageInputKey = keyof typeof en\n\n/** Chinese strings (same keys as {@link en}). */\nexport const zh: { [Key in keyof typeof en]: string } = {\n title: '输入模态',\n loading: '正在读取模型列表…',\n loadFailed: '读取模型配置失败。',\n retry: '重试',\n empty: '还没有显式模型列表——请先在上方模型目录中添加模型,再回到这里声明输入模态。',\n inheritsHint: '当前显示的是继承的模型列表;保存会将其复制到你的用户设置层。',\n readOnly: '当前部署的设置文档为只读。',\n choiceDefault: '提供方默认',\n choiceText: '仅文本',\n choiceImage: '文本和图片',\n save: '保存',\n saving: '保存中…',\n saved: '已保存。适配器会在下一次请求时生效。',\n conflict: '这张卡片打开期间,设置已被其他地方改动;已重新加载最新值。',\n}\n","/**\n * Browser half: the per-model image-input fold inside every llm-pi-ai provider\n * card of the Models settings page. The Host half is empty; provider routes\n * are created and edited through the page's own forms, and this plugin only\n * adds the one field those forms do not carry.\n */\n\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\n// Type-only: pulls the ctx.slots service merge (SlotRegistry).\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: pulls the ctx.locale merge.\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the ctx.remote merge (the settings describe/mutate face).\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\n// Type-only: pulls the 'settings.models.provider-card' SlotMap entry and the\n// ProviderDirectoryEntry owner data.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-models/client'\nimport { ImageInputController } from './controller.ts'\nimport { ImageInputCard } from './ImageInputCard.tsx'\nimport type { ImageInputFace } from './ImageInputCard.tsx'\nimport { en, zh } from './locales.ts'\nimport type { ImageInputKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Per-model image-input copy on the Models page. */\n 'settings.models.imageInput': ImageInputKey\n }\n}\n\n/** Dictionary namespace owned by this plugin. */\nconst NS = 'settings.models.imageInput'\n\n/** The effect label prefix. */\nconst PKG = '@jcy2387/dsh-models-input-modalities'\n\n/** Required browser services. */\nexport const inject = ['slots', 'locale', 'remote', 'remote.settings']\n\n/**\n * Register the image-input fold on every llm-pi-ai provider card once the\n * Models section has declared the seat.\n * @param ctx - the plugin's client context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), `${PKG}: dictionaries`)\n const controller = new ImageInputController(ctx)\n const face: ImageInputFace = {\n loadModels: entry => controller.load(entry),\n saveModels: (entry, models, revision) => controller.save(entry, models, revision),\n }\n ctx.slots.inject('settings.models.provider-card', () => ctx.slots.register({\n name: 'settings.models.provider-card',\n key: 'llm-pi-ai',\n locale: NS,\n inject: () => face,\n }, ImageInputCard))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.cjs","names":["NS","useState","useRef","useCallback","css","Button"],"sources":["../src/client/controller.ts","../src/image-input.ts","../src/model-row.ts","../src/reasoning-efforts.ts","../src/client/ModelCapabilityCard.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/** Settings reads and writes for the model-capability card, over the settings Remote. */\n\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type { SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client'\nimport type { ProviderDirectoryEntry } from '@deepseek-ai/dsh-client-ui-settings-models/client'\nimport type { JsonValue } from '@deepseek-ai/dsh-util-values'\nimport type { ModelRow } from '../model-row.ts'\n\n/** The settings namespace every pi-ai provider card addresses. */\nconst NS = 'llm-pi-ai'\n\n/** What one load answers for a provider card. */\nexport interface ModelCapabilityView {\n /** Whether the deployment accepts settings writes at all. */\n writable: boolean\n /** Revision fence the next save must carry. */\n revision: number\n /** The rows to show: the user layer's when it owns the list, else the effective ones. */\n models: readonly ModelRow[]\n /** Whether the shown rows already live in the user layer. */\n fromUser: boolean\n}\n\n/** What one save answered. */\nexport type ModelCapabilitySaveOutcome =\n | { readonly kind: 'written'; readonly revision: number }\n | { readonly kind: 'conflict'; readonly message: string }\n | { readonly kind: 'refused'; readonly message: string }\n\n/** Read one plain-object path; anything off-path answers undefined. */\nfunction at(source: unknown, path: readonly string[]): unknown {\n let current: unknown = source\n for (const key of path) {\n if (typeof current !== 'object' || current === null || Array.isArray(current)) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n}\n\n/** Coerce a stored models value into open row records. */\nfunction rows(value: unknown): ModelRow[] {\n return Array.isArray(value)\n ? value.map(entry =>\n typeof entry === 'object' && entry !== null && !Array.isArray(entry) ? entry as ModelRow : {})\n : []\n}\n\n/** Joins the settings Remote's document view and fenced writes for one card. */\nexport class ModelCapabilityController {\n /** The cards waiting to hear that this namespace's stored section changed. */\n private readonly listeners = new Set<(revision: number) => void>()\n\n /**\n * @param ctx - the plugin's client context, which declares `remote.settings`\n * in its own `inject`.\n */\n constructor(private readonly ctx: ClientContext) {}\n\n /**\n * Start forwarding this namespace's pushed document invalidations to the\n * cards. The Host emits one per committed write — including the Models page's\n * own model-list edits — so a card never has to poll or wait for a remount.\n * @returns the disposer that withdraws the Remote subscription.\n */\n watch(): () => void {\n return this.ctx.remote.$on('settings/document-updated', (ns, revision) => {\n if (String(ns) !== NS) return\n for (const listener of [...this.listeners]) listener(revision)\n })\n }\n\n /**\n * Subscribe one card to namespace invalidations.\n * @param listener - called with the namespace's new revision on each change.\n * @returns the disposer for this one subscription.\n */\n subscribe(listener: (revision: number) => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n /**\n * Read one provider's model rows and the revision fence for writing them.\n * @param entry - the card's directory row (its settings address names the profile).\n * @returns the view, or undefined when the settings face or namespace is unavailable.\n */\n async load(entry: ProviderDirectoryEntry): Promise<ModelCapabilityView | undefined> {\n const response = await this.ctx.remote.settings.describe()\n if (!response.ok) return undefined\n const namespace = response.value.namespaces.find(view => view.ns === NS)\n if (namespace === undefined) return undefined\n const path = [...entry.settingsPath, 'models']\n const userModel = at(namespace.user, path)\n const fromUser = Array.isArray(userModel)\n return {\n writable: response.value.writable,\n revision: namespace.revision,\n models: rows(fromUser ? userModel : at(namespace.value, path)),\n fromUser,\n }\n }\n\n /**\n * Write the rows back as the profile's whole `models` array, under the fence\n * the load answered. The array is replaced by value — the adapter's own\n * semantics — so untouched rows ride along exactly as stored.\n * @param entry - the card's directory row.\n * @param models - the edited rows.\n * @param revision - the fence from the load this draft was opened at.\n * @returns the write outcome the card renders from.\n */\n async save(\n entry: ProviderDirectoryEntry,\n models: readonly ModelRow[],\n revision: number,\n ): Promise<ModelCapabilitySaveOutcome> {\n // The rows came out of a stored JSON document and the edits only ever set\n // string arrays, `false`, and dicts of strings and nulls, so the array is\n // JSON by construction.\n const value = models as unknown as JsonValue\n const ops: SettingsPathOpView[] = [{ op: 'set', path: [...entry.settingsPath, 'models'], value }]\n const response = await this.ctx.remote.settings.mutate(NS, ops, revision)\n if (response.ok) return { kind: 'written', revision: response.value.revision }\n const { code, message } = response.error\n return code === 'settings/conflict' ? { kind: 'conflict', message } : { kind: 'refused', message }\n }\n}\n","/** Pure row helpers for the per-model image-input claim. */\n\nimport type { ModelRow } from './model-row.ts'\n\n/** The three image-input states a row's select offers. */\nexport type ImageInputChoice = 'inherit' | 'text' | 'image'\n\n/**\n * The choice a row's stored `input` displays. Absent and empty mean the same\n * inheritance — the installed catalog's modalities, then the route's\n * `defaultInput` — and any list naming `image` is the image-capable claim\n * however else it is spelled.\n * @param row - one stored model row.\n * @returns the choice the row's select shows.\n */\nexport function imageInputChoice(row: ModelRow): ImageInputChoice {\n const value = row['input']\n if (!Array.isArray(value) || value.length === 0) return 'inherit'\n return value.includes('image') ? 'image' : 'text'\n}\n\n/**\n * The row with one choice applied: `inherit` removes the field, the others\n * store exactly the modality list the adapter reads. Every other field,\n * including ones this card never shows, survives.\n * @param row - the row to patch.\n * @param choice - the selected state.\n * @returns a new row carrying the choice.\n */\nexport function withImageInput(row: ModelRow, choice: ImageInputChoice): ModelRow {\n const next = { ...row }\n delete next['input']\n if (choice === 'text') next['input'] = ['text']\n else if (choice === 'image') next['input'] = ['text', 'image']\n return next\n}\n\n/**\n * Read a select's submitted value. The DOM hands over a bare string, so an\n * unrecognized one is refused rather than cast into the union.\n * @param value - the submitted option value.\n * @returns the choice, or undefined for anything else.\n */\nexport function parseImageInputChoice(value: string): ImageInputChoice | undefined {\n return value === 'inherit' || value === 'text' || value === 'image' ? value : undefined\n}\n","/** Row vocabulary every per-model claim shares. */\n\n/** One configured model row, structurally open so hidden fields survive an edit. */\nexport type ModelRow = Record<string, unknown>\n\n/**\n * The row's model id for labels.\n * @param row - one stored model row.\n * @param index - the row's zero-based position.\n * @returns the id, or a positional name for an id-less row.\n */\nexport function rowId(row: ModelRow, index: number): string {\n const id = row['id']\n return typeof id === 'string' && id.length > 0 ? id : `#${String(index + 1)}`\n}\n","/**\n * Pure row helpers for the per-model reasoning-effort claim.\n *\n * The adapter's field is `reasoningEfforts`, and it says which thinking levels a\n * model *offers* — the levels the composer's model picker lists — not which one\n * a request uses. Absent keeps the installed catalog's capability (a\n * hand-declared model has none, so its picker offers no level at all); `false`\n * declares a non-reasoning model; a dict declares the offered levels and, per\n * level, the spelling dispatch sends on the wire.\n */\n\nimport type { ModelRow } from './model-row.ts'\n\n/** Every level a row may declare, in escalation order — the adapter's own key set. */\nexport const THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const\n\n/** One level a row may declare. */\nexport type ThinkingLevel = (typeof THINKING_LEVELS)[number]\n\n/** The three reasoning states a row's select offers. */\nexport type ReasoningChoice = 'inherit' | 'none' | 'custom'\n\n/** One level's editor state. */\nexport interface ReasoningLevelDraft {\n /** Whether the row declares the level, i.e. whether selectors offer it. */\n readonly offered: boolean\n /**\n * The wire spelling dispatch sends for the level. Only `off` may leave it\n * empty — \"supported, send nothing\" — because for most providers not thinking\n * is the parameter's absence; every other declared level needs a value.\n */\n readonly wire: string\n}\n\n/** The editor's level rows, one per {@link THINKING_LEVELS} entry. */\nexport type ReasoningLevels = Readonly<Record<ThinkingLevel, ReasoningLevelDraft>>\n\n/** Why a row's declared levels cannot be saved; the adapter's own two rules. */\nexport type ReasoningFailure = 'needsLevel' | 'needsWire'\n\n/**\n * The spelling a newly offered level starts from: its own name, except for\n * `off`, whose absence is the spelling most providers read as \"do not think\".\n * @param level - the level being offered.\n * @returns its default wire spelling.\n */\nfunction defaultWire(level: ThinkingLevel): string {\n return level === 'off' ? '' : level\n}\n\n/**\n * A level set offering exactly `offered`, each at its default spelling.\n * @param offered - the levels to declare.\n * @returns the seven level drafts.\n */\nfunction declaring(offered: readonly ThinkingLevel[]): ReasoningLevels {\n const drafts = {} as Record<ThinkingLevel, ReasoningLevelDraft>\n for (const level of THINKING_LEVELS) {\n const on = offered.includes(level)\n drafts[level] = { offered: on, wire: on ? defaultWire(level) : '' }\n }\n return drafts\n}\n\n/**\n * The levels a row with no dict of its own starts from when it is switched to a\n * declared set: the three efforts an OpenAI-compatible gateway is most likely\n * to serve, each spelled as its own name. `off` is deliberately left unticked:\n * on the plain `reasoning_effort` wire an empty `off` is the very same request\n * as naming no effort at all, so pre-offering it would promise a \"stop\n * thinking\" choice the endpoint may not honour — the author ticks it, and\n * spells it, once their endpoint says how.\n */\nexport const DEFAULT_LEVELS: ReasoningLevels = declaring(['low', 'medium', 'high'])\n\n/**\n * The choice a row's stored `reasoningEfforts` displays. Anything that is not\n * `false` and not a plain object states no claim this card understands, and\n * reads as the inheritance it leaves in place; an empty object reads as a\n * declared set offering nothing, which the validator then names.\n * @param row - one stored model row.\n * @returns the choice the row's select shows.\n */\nexport function reasoningChoice(row: ModelRow): ReasoningChoice {\n const value = row['reasoningEfforts']\n if (value === false) return 'none'\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) return 'custom'\n return 'inherit'\n}\n\n/**\n * The row's declared levels as editor state. A level the dict omits is not\n * offered; one it carries is, with its wire spelling — an empty value, which is\n * what a valueless `off:` stores as, and a value of a type the schema refuses\n * both read as no spelling yet. A row declaring nothing shows the defaults a\n * fresh declaration starts from.\n * @param row - one stored model row.\n * @returns the seven level drafts.\n */\nexport function reasoningLevels(row: ModelRow): ReasoningLevels {\n const value = row['reasoningEfforts']\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return DEFAULT_LEVELS\n const stored = value as Record<string, unknown>\n const drafts = {} as Record<ThinkingLevel, ReasoningLevelDraft>\n for (const level of THINKING_LEVELS) {\n if (!(level in stored)) {\n drafts[level] = { offered: false, wire: '' }\n continue\n }\n const wire = stored[level]\n drafts[level] = { offered: true, wire: typeof wire === 'string' ? wire : '' }\n }\n return drafts\n}\n\n/**\n * The row with one reasoning choice applied: `inherit` removes the field,\n * `none` stores `false`, and `custom` stores exactly the levels offered — in\n * escalation order, a valueless `off` as `null`, spellings trimmed so a stray\n * space cannot reach the wire. Every other field, including ones this card\n * never shows, survives.\n * @param row - the row to patch.\n * @param choice - the selected state.\n * @param levels - the editor's level drafts; read only for `custom`.\n * @returns a new row carrying the choice.\n */\nexport function withReasoning(row: ModelRow, choice: ReasoningChoice, levels: ReasoningLevels): ModelRow {\n const next = { ...row }\n delete next['reasoningEfforts']\n if (choice === 'none') {\n next['reasoningEfforts'] = false\n return next\n }\n if (choice !== 'custom') return next\n const declared: Record<string, string | null> = {}\n for (const level of THINKING_LEVELS) {\n const draft = levels[level]\n if (!draft.offered) continue\n const wire = draft.wire.trim()\n declared[level] = wire.length === 0 ? null : wire\n }\n next['reasoningEfforts'] = declared\n return next\n}\n\n/**\n * The level set with one level offered or withdrawn. Offering a level always\n * starts its spelling from the default, so a tick is never a blank the save\n * then refuses, and what a withdrawn level carried is not what a reticked one\n * silently revives; levels left alone keep theirs.\n * @param levels - the drafts to patch.\n * @param level - the level toggled.\n * @param offered - whether the row should declare it.\n * @returns the patched drafts, or the same object when nothing changes.\n */\nexport function toggleLevel(levels: ReasoningLevels, level: ThinkingLevel, offered: boolean): ReasoningLevels {\n const current = levels[level]\n if (current.offered === offered) return levels\n return { ...levels, [level]: { offered, wire: offered ? defaultWire(level) : '' } }\n}\n\n/**\n * The level set with one level's wire spelling retyped.\n * @param levels - the drafts to patch.\n * @param level - the level whose spelling changed.\n * @param wire - the text the field now carries.\n * @returns the patched drafts.\n */\nexport function setWire(levels: ReasoningLevels, level: ThinkingLevel, wire: string): ReasoningLevels {\n return { ...levels, [level]: { ...levels[level], wire } }\n}\n\n/**\n * Why a row's declared levels would be refused, checked before the write so the\n * card can name the row instead of answering a rejected settings mutation. The\n * rules are the adapter's: every declared level but `off` needs a wire spelling,\n * and a set offering no level beyond `off` declares nothing worth declaring.\n * @param row - one stored model row.\n * @returns the failure, or undefined for a row that can be saved.\n */\nexport function reasoningFailure(row: ModelRow): ReasoningFailure | undefined {\n if (reasoningChoice(row) !== 'custom') return undefined\n const levels = reasoningLevels(row)\n let thinks = false\n for (const level of THINKING_LEVELS) {\n const draft = levels[level]\n if (!draft.offered || level === 'off') continue\n if (draft.wire.trim().length === 0) return 'needsWire'\n thinks = true\n }\n return thinks ? undefined : 'needsLevel'\n}\n\n/**\n * Read a select's submitted value. The DOM hands over a bare string, so an\n * unrecognized one is refused rather than cast into the union.\n * @param value - the submitted option value.\n * @returns the choice, or undefined for anything else.\n */\nexport function parseReasoningChoice(value: string): ReasoningChoice | undefined {\n return value === 'inherit' || value === 'none' || value === 'custom' ? value : undefined\n}\n","/**\n * One pi-ai provider card's model-capability fold: the per-model claims the\n * Models page's own form does not carry — which inputs a model accepts, and\n * which reasoning levels it offers. The fold loads the provider's stored rows\n * when first opened, edits them locally, and writes the whole `models` array\n * back under the revision fence the load answered — the same array semantics\n * the page's own cards use, and the reason both claims live in one fold: two\n * folds would write the same array and fence each other into conflicts. A\n * stored change elsewhere on the page (a model added or removed in the catalog\n * above) reaches the fold through the pushed settings invalidation, so the list\n * it shows never waits for the section to remount.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport { Button } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { ProviderDirectoryEntry } from '@deepseek-ai/dsh-client-ui-settings-models/client'\nimport type { ModelCapabilitySaveOutcome, ModelCapabilityView } from './controller.ts'\nimport { imageInputChoice, parseImageInputChoice, withImageInput } from '../image-input.ts'\nimport type { ImageInputChoice } from '../image-input.ts'\nimport { rowId } from '../model-row.ts'\nimport type { ModelRow } from '../model-row.ts'\nimport {\n THINKING_LEVELS,\n parseReasoningChoice,\n reasoningChoice,\n reasoningFailure,\n reasoningLevels,\n setWire,\n toggleLevel,\n withReasoning,\n} from '../reasoning-efforts.ts'\nimport type { ReasoningChoice, ReasoningLevels, ThinkingLevel } from '../reasoning-efforts.ts'\nimport css from './styles.module.css'\n\n/** The registration-side face this card receives. */\nexport interface ModelCapabilityFace {\n /** Read the provider's rows and revision fence; undefined when the settings face is unavailable. */\n loadModels(entry: ProviderDirectoryEntry): Promise<ModelCapabilityView | undefined>\n /** Write the rows back under the fence the load answered. */\n saveModels(\n entry: ProviderDirectoryEntry,\n models: readonly ModelRow[],\n revision: number,\n ): Promise<ModelCapabilitySaveOutcome>\n /** Listen for stored changes in the provider namespace; receives its new revision. */\n subscribeChanges(listener: (revision: number) => void): () => void\n}\n\n/** Props the provider-card slot binds. */\nexport type ModelCapabilityCardProps =\n PropsRuntime<'settings.models.provider-card'>\n & PropsLocale<'settings.models.modelCapabilities'>\n & InjectFace<ModelCapabilityFace>\n\n/** Lifecycle of one fold: closed, loading, editable, or writing. */\ntype Status = 'idle' | 'loading' | 'ready' | 'saving'\n\n/**\n * Render the model-capability fold of one provider card.\n * @param props - the card's directory row plus the bound face and copy.\n * @returns the fold, or nothing while the provider is still a dormant row.\n */\nexport function ModelCapabilityCard(props: ModelCapabilityCardProps): ReactNode {\n const { provider, configured, t, loadModels, saveModels, subscribeChanges } = props\n const [status, setStatus] = useState<Status>('idle')\n const [view, setView] = useState<ModelCapabilityView | undefined>(undefined)\n const [rows, setRows] = useState<readonly ModelRow[]>([])\n const [failure, setFailure] = useState<string | undefined>(undefined)\n const [saved, setSaved] = useState(false)\n const [open, setOpen] = useState(false)\n // Whether the stored section moved past what this fold shows. A change that\n // cannot be adopted yet — the fold is closed, it holds an unsaved draft, or\n // a newer commit outran the read in flight — parks here until a safe moment\n // takes it.\n const [stale, setStale] = useState(false)\n\n /** Latest read wins: an older response never overwrites a newer one. */\n const generation = useRef(0)\n /**\n * The newest revision this fold holds adopted data for. An announcement at\n * or below it is old news — the card's own committed write's echo included.\n */\n const seen = useRef(0)\n /**\n * The newest revision the namespace has announced. A completed read or\n * write compares against it to tell whether a commit outran it mid-flight.\n */\n const noticed = useRef(0)\n const dirtyRef = useRef(false)\n const savingRef = useRef(false)\n\n const dirty = view !== undefined && JSON.stringify(rows) !== JSON.stringify(view.models)\n useEffect(() => {\n dirtyRef.current = dirty\n }, [dirty])\n useEffect(() => {\n savingRef.current = status === 'saving'\n }, [status])\n\n /**\n * Re-read the provider's rows.\n * @param silent - keep the rendered list and copy in place while reading, for\n * a background refresh the user never asked for.\n */\n const reload = useCallback(async (silent: boolean): Promise<void> => {\n const ticket = ++generation.current\n if (!silent) {\n setStatus('loading')\n setFailure(undefined)\n setSaved(false)\n }\n const loaded = await loadModels(provider)\n if (ticket !== generation.current) return\n if (silent && (dirtyRef.current || savingRef.current)) {\n setStale(true)\n return\n }\n if (loaded === undefined) {\n // A background read that found nothing keeps the last good list and\n // retries at the fold's next open; only an asked-for read may fail visibly.\n if (silent) {\n setStale(true)\n return\n }\n setView(undefined)\n setFailure(t('loadFailed'))\n setStatus('ready')\n return\n }\n seen.current = loaded.revision\n setView(loaded)\n setRows(loaded.models.map(row => ({ ...row })))\n setFailure(undefined)\n // A commit that outran this read mid-flight keeps the notice parked; the\n // adoption effect sees the fresh view and reads again until level.\n setStale(loaded.revision < noticed.current)\n setStatus('ready')\n }, [loadModels, provider, t])\n\n // The page writes the same namespace this fold reads, so every pushed\n // invalidation is the fold's notice that its list moved underneath it.\n useEffect(() => {\n if (configured !== true) return undefined\n return subscribeChanges((revision) => {\n if (revision > noticed.current) noticed.current = revision\n // Data already in hand covers this revision: the card's own write's\n // echo, or a notice whose commit the last adopted read already caught.\n if (revision <= seen.current) return\n setStale(true)\n })\n }, [configured, subscribeChanges])\n\n // An open fold with nothing at stake adopts the change immediately; one that\n // is closed or holds a draft waits for the toggle handler or the fence. The\n // view dep re-runs the check after every completed read, so a read a newer\n // commit outran is followed by another until the fold is level.\n useEffect(() => {\n if (stale && open && status === 'ready' && !dirty) void reload(true)\n }, [stale, open, status, dirty, view, reload])\n\n // A dormant directory row has no profile to read models from; the create\n // card dispatches this seat only after the provider is saved anyway.\n if (configured !== true) return null\n\n const chooseInput = (index: number, choice: ImageInputChoice): void => {\n setRows(current => current.map((row, at) => at === index ? withImageInput(row, choice) : row))\n }\n\n const chooseReasoning = (index: number, choice: ReasoningChoice): void => {\n setRows(current => current.map((row, at) =>\n at === index ? withReasoning(row, choice, reasoningLevels(row)) : row))\n }\n\n /**\n * Patch one row's declared levels. The edit runs inside the state updater and\n * re-reads the row there, so two controls changed in one batch each see what\n * the previous one wrote.\n * @param index - the row to patch.\n * @param edit - the level-set transformation.\n */\n const patchLevels = (index: number, edit: (levels: ReasoningLevels) => ReasoningLevels): void => {\n setRows(current => current.map((row, at) =>\n at === index ? withReasoning(row, 'custom', edit(reasoningLevels(row))) : row))\n }\n\n // A row the adapter would refuse is named here, next to the control that\n // caused it, instead of answered as a rejected settings mutation.\n const unsavable = rows.some(row => reasoningFailure(row) !== undefined)\n const locked = view === undefined || !view.writable || status === 'saving'\n\n const submit = async (): Promise<void> => {\n if (view === undefined || unsavable) return\n setStatus('saving')\n const outcome = await saveModels(provider, rows, view.revision)\n if (outcome.kind === 'written') {\n // The fold now holds data for the committed revision, so its own echo\n // is old news; a notice that raced the write stays parked for the\n // adoption effect, which the just-settled draft unlocks.\n seen.current = outcome.revision\n setView({ ...view, revision: outcome.revision, models: rows.map(row => ({ ...row })), fromUser: true })\n setSaved(true)\n setFailure(undefined)\n setStale(noticed.current > outcome.revision)\n setStatus('ready')\n return\n }\n setFailure(outcome.kind === 'conflict' ? t('conflict') : outcome.message)\n if (outcome.kind === 'conflict') await reload(false)\n else setStatus('ready')\n }\n\n return (\n <details\n className={css['fold']}\n onToggle={(event) => {\n const opened = event.currentTarget.open\n setOpen(opened)\n // First open reads; a clean reopen adopts whatever the parked notice\n // held. A reopen over an unsaved draft keeps it: the notice stays\n // parked until the draft settles — saved into a fence conflict, or\n // reverted into a silent re-read.\n if (opened && (status === 'idle' || (stale && !dirty))) void reload(false)\n }}\n >\n <summary className={css['summary']}>{t('title')}</summary>\n <div className={css['body']}>\n {status === 'loading' ? <p className={css['status']}>{t('loading')}</p> : null}\n {status !== 'loading' && view === undefined\n ? (\n <>\n <p className={css['error']}>{failure ?? t('loadFailed')}</p>\n <div className={css['footer']}>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => { void reload(false) }}>{t('retry')}</Button>\n </div>\n </>\n )\n : null}\n {status !== 'loading' && view !== undefined\n ? (view.models.length === 0\n ? <p className={css['hint']}>{t('empty')}</p>\n : (\n <>\n {view.fromUser ? null : <p className={css['hint']}>{t('inheritsHint')}</p>}\n {rows.map((row, index) => {\n const id = rowId(row, index)\n const reasoning = reasoningChoice(row)\n const levels = reasoningLevels(row)\n const invalid = reasoningFailure(row)\n return (\n <div key={index} className={css['model']}>\n <div className={css['row']}>\n <span className={css['rowId']}>{id}</span>\n <label className={css['field']}>\n <span className={css['fieldLabel']}>{t('inputLabel')}</span>\n <select\n className={css['select']}\n value={imageInputChoice(row)}\n aria-label={`${t('inputLabel')} ${id}`}\n disabled={locked}\n onChange={(event) => {\n const next = parseImageInputChoice(event.target.value)\n if (next !== undefined) chooseInput(index, next)\n }}\n >\n <option value=\"inherit\">{t('choiceDefault')}</option>\n <option value=\"text\">{t('choiceText')}</option>\n <option value=\"image\">{t('choiceImage')}</option>\n </select>\n </label>\n <label className={css['field']}>\n <span className={css['fieldLabel']}>{t('reasoningLabel')}</span>\n <select\n className={css['select']}\n value={reasoning}\n aria-label={`${t('reasoningLabel')} ${id}`}\n disabled={locked}\n onChange={(event) => {\n const next = parseReasoningChoice(event.target.value)\n if (next !== undefined) chooseReasoning(index, next)\n }}\n >\n <option value=\"inherit\">{t('reasoningInherit')}</option>\n <option value=\"none\">{t('reasoningNone')}</option>\n <option value=\"custom\">{t('reasoningCustom')}</option>\n </select>\n </label>\n </div>\n {reasoning === 'custom'\n ? (\n <div className={css['levels']}>\n <div className={css['levelGrid']}>\n {THINKING_LEVELS.map((level: ThinkingLevel) => (\n <div key={level} className={css['levelRow']}>\n <label className={css['levelOffered']}>\n <input\n type=\"checkbox\"\n checked={levels[level].offered}\n disabled={locked}\n onChange={(event) => {\n // Read the control here: a state updater runs later, against\n // a value React has already reconciled back.\n const offered = event.target.checked\n patchLevels(index, current => toggleLevel(current, level, offered))\n }}\n />\n <span>{level}</span>\n </label>\n <input\n className={css['wire']}\n type=\"text\"\n value={levels[level].wire}\n placeholder={level === 'off' ? t('wireNothing') : level}\n aria-label={`${t('wireLabel')} ${level}`}\n spellCheck={false}\n disabled={locked || !levels[level].offered}\n onChange={(event) => {\n const wire = event.target.value\n patchLevels(index, current => setWire(current, level, wire))\n }}\n />\n </div>\n ))}\n </div>\n <p className={css['hint']}>{t('reasoningHint')}</p>\n {invalid === 'needsWire'\n ? <p className={css['error']}>{`${id}: ${t('needsWire')}`}</p>\n : null}\n {invalid === 'needsLevel'\n ? <p className={css['error']}>{`${id}: ${t('needsLevel')}`}</p>\n : null}\n </div>\n )\n : null}\n </div>\n )\n })}\n {failure !== undefined ? <p className={css['error']}>{failure}</p> : null}\n <div className={css['footer']}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={locked || !dirty || unsavable}\n onClick={() => { void submit() }}\n >\n {status === 'saving' ? t('saving') : t('save')}\n </Button>\n {failure !== undefined\n ? (\n <Button variant=\"ghost\" size=\"sm\" disabled={status === 'saving'} onClick={() => { void reload(false) }}>\n {t('retry')}\n </Button>\n )\n : null}\n {saved ? <p className={css['status']}>{t('saved')}</p> : null}\n {view.writable ? null : <p className={css['hint']}>{t('readOnly')}</p>}\n </div>\n </>\n ))\n : null}\n </div>\n </details>\n )\n}\n","/** Copy dictionaries for the model-capability card. */\n\n/** English strings (the key-set source of truth for this pair). */\nexport const en = {\n title: 'Model capabilities',\n loading: 'Loading the model list…',\n loadFailed: 'Loading the model configuration failed.',\n retry: 'Retry',\n empty: 'No explicit model list yet — add models in the catalog above, then declare what each one accepts and reasons with here.',\n inheritsHint: 'Showing the inherited model list; saving copies it into your user settings.',\n readOnly: 'The settings document is read-only in this deployment.',\n inputLabel: 'Input modalities',\n choiceDefault: 'Provider default',\n choiceText: 'Text only',\n choiceImage: 'Text and image',\n reasoningLabel: 'Reasoning levels',\n reasoningInherit: 'Catalog default',\n reasoningNone: 'Not a reasoning model',\n reasoningCustom: 'Declare levels',\n reasoningHint: 'A ticked level is one the model picker offers; the value beside it is the spelling sent on the wire, which a gateway may name its own way. Only off may stay empty — offered, and sent as no parameter at all. A level left unticked is not offered.',\n wireLabel: 'Wire value for',\n wireNothing: 'send nothing',\n needsLevel: 'declare at least one level beyond off, or choose “Not a reasoning model”.',\n needsWire: 'every level except off needs the wire value to send.',\n save: 'Save',\n saving: 'Saving…',\n saved: 'Saved. The adapter picks it up on its next request.',\n conflict: 'These settings changed elsewhere while this card was open; the latest values were reloaded.',\n}\n\n/** The settings.models.modelCapabilities namespace key union. */\nexport type ModelCapabilityKey = keyof typeof en\n\n/** Chinese strings (same keys as {@link en}). */\nexport const zh: { [Key in keyof typeof en]: string } = {\n title: '模型能力',\n loading: '正在读取模型列表…',\n loadFailed: '读取模型配置失败。',\n retry: '重试',\n empty: '还没有显式模型列表——请先在上方模型目录中添加模型,再回到这里声明每个模型接受的输入与推理等级。',\n inheritsHint: '当前显示的是继承的模型列表;保存会将其复制到你的用户设置层。',\n readOnly: '当前部署的设置文档为只读。',\n inputLabel: '输入模态',\n choiceDefault: '提供方默认',\n choiceText: '仅文本',\n choiceImage: '文本和图片',\n reasoningLabel: '推理等级',\n reasoningInherit: '目录默认',\n reasoningNone: '非推理模型',\n reasoningCustom: '声明等级',\n reasoningHint: '勾选的等级就是模型选择器会提供的选项;旁边的值是实际发到网关的拼写,网关可以有自己的叫法。只有 off 可以留空——表示提供该等级但完全不发送参数。未勾选的等级不会提供。',\n wireLabel: '发送值:',\n wireNothing: '不发送',\n needsLevel: '至少声明一个 off 以外的等级,或选择「非推理模型」。',\n needsWire: '除 off 外,每个勾选的等级都要填写发送值。',\n save: '保存',\n saving: '保存中…',\n saved: '已保存。适配器会在下一次请求时生效。',\n conflict: '这张卡片打开期间,设置已被其他地方改动;已重新加载最新值。',\n}\n","/**\n * Browser half: the per-model capability fold inside every llm-pi-ai provider\n * card of the Models settings page — the input modalities and the reasoning\n * levels a model offers, the two per-model claims the page's own forms\n * deliberately leave to `settings.yaml`. The Host half is empty; provider\n * routes are created and edited through the page's own forms.\n */\n\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\n// Type-only: pulls the ctx.slots service merge (SlotRegistry).\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: pulls the ctx.locale merge.\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the ctx.remote merge (the settings describe/mutate face).\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\n// Type-only: pulls the 'settings.models.provider-card' SlotMap entry and the\n// ProviderDirectoryEntry owner data.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-models/client'\nimport { ModelCapabilityController } from './controller.ts'\nimport { ModelCapabilityCard } from './ModelCapabilityCard.tsx'\nimport type { ModelCapabilityFace } from './ModelCapabilityCard.tsx'\nimport { en, zh } from './locales.ts'\nimport type { ModelCapabilityKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Per-model capability copy on the Models page. */\n 'settings.models.modelCapabilities': ModelCapabilityKey\n }\n}\n\n/** Dictionary namespace owned by this plugin. */\nconst NS = 'settings.models.modelCapabilities'\n\n/** The effect label prefix. */\nconst PKG = '@jcy2387/dsh-models-input-modalities'\n\n/** Required browser services. */\nexport const inject = ['slots', 'locale', 'remote', 'remote.settings']\n\n/**\n * Register the model-capability fold on every llm-pi-ai provider card once the\n * Models section has declared the seat.\n * @param ctx - the plugin's client context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), `${PKG}: dictionaries`)\n const controller = new ModelCapabilityController(ctx)\n ctx.effect(() => controller.watch(), `${PKG}: settings invalidations`)\n const face: ModelCapabilityFace = {\n loadModels: entry => controller.load(entry),\n saveModels: (entry, models, revision) => controller.save(entry, models, revision),\n subscribeChanges: listener => controller.subscribe(listener),\n }\n ctx.slots.inject('settings.models.provider-card', () => ctx.slots.register({\n name: 'settings.models.provider-card',\n key: 'llm-pi-ai',\n locale: NS,\n inject: () => face,\n }, ModelCapabilityCard))\n}\n"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * One pi-ai provider card's model-capability fold: the per-model claims the
3
+ * Models page's own form does not carry — which inputs a model accepts, and
4
+ * which reasoning levels it offers. The fold loads the provider's stored rows
5
+ * when first opened, edits them locally, and writes the whole `models` array
6
+ * back under the revision fence the load answered — the same array semantics
7
+ * the page's own cards use, and the reason both claims live in one fold: two
8
+ * folds would write the same array and fence each other into conflicts. A
9
+ * stored change elsewhere on the page (a model added or removed in the catalog
10
+ * above) reaches the fold through the pushed settings invalidation, so the list
11
+ * it shows never waits for the section to remount.
12
+ */
13
+ import type { ReactNode } from 'react';
14
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
15
+ import type { ProviderDirectoryEntry } from '@deepseek-ai/dsh-client-ui-settings-models/client';
16
+ import type { ModelCapabilitySaveOutcome, ModelCapabilityView } from './controller.ts';
17
+ import type { ModelRow } from '../model-row.ts';
18
+ /** The registration-side face this card receives. */
19
+ export interface ModelCapabilityFace {
20
+ /** Read the provider's rows and revision fence; undefined when the settings face is unavailable. */
21
+ loadModels(entry: ProviderDirectoryEntry): Promise<ModelCapabilityView | undefined>;
22
+ /** Write the rows back under the fence the load answered. */
23
+ saveModels(entry: ProviderDirectoryEntry, models: readonly ModelRow[], revision: number): Promise<ModelCapabilitySaveOutcome>;
24
+ /** Listen for stored changes in the provider namespace; receives its new revision. */
25
+ subscribeChanges(listener: (revision: number) => void): () => void;
26
+ }
27
+ /** Props the provider-card slot binds. */
28
+ export type ModelCapabilityCardProps = PropsRuntime<'settings.models.provider-card'> & PropsLocale<'settings.models.modelCapabilities'> & InjectFace<ModelCapabilityFace>;
29
+ /**
30
+ * Render the model-capability fold of one provider card.
31
+ * @param props - the card's directory row plus the bound face and copy.
32
+ * @returns the fold, or nothing while the provider is still a dormant row.
33
+ */
34
+ export declare function ModelCapabilityCard(props: ModelCapabilityCardProps): ReactNode;
@@ -1,9 +1,9 @@
1
- /** Settings reads and writes for the image-input card, over the settings Remote. */
1
+ /** Settings reads and writes for the model-capability card, over the settings Remote. */
2
2
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
3
3
  import type { ProviderDirectoryEntry } from '@deepseek-ai/dsh-client-ui-settings-models/client';
4
- import type { ModelRow } from '../image-input.ts';
4
+ import type { ModelRow } from '../model-row.ts';
5
5
  /** What one load answers for a provider card. */
6
- export interface ImageInputView {
6
+ export interface ModelCapabilityView {
7
7
  /** Whether the deployment accepts settings writes at all. */
8
8
  writable: boolean;
9
9
  /** Revision fence the next save must carry. */
@@ -14,7 +14,7 @@ export interface ImageInputView {
14
14
  fromUser: boolean;
15
15
  }
16
16
  /** What one save answered. */
17
- export type ImageInputSaveOutcome = {
17
+ export type ModelCapabilitySaveOutcome = {
18
18
  readonly kind: 'written';
19
19
  readonly revision: number;
20
20
  } | {
@@ -25,19 +25,34 @@ export type ImageInputSaveOutcome = {
25
25
  readonly message: string;
26
26
  };
27
27
  /** Joins the settings Remote's document view and fenced writes for one card. */
28
- export declare class ImageInputController {
28
+ export declare class ModelCapabilityController {
29
29
  private readonly ctx;
30
+ /** The cards waiting to hear that this namespace's stored section changed. */
31
+ private readonly listeners;
30
32
  /**
31
33
  * @param ctx - the plugin's client context, which declares `remote.settings`
32
34
  * in its own `inject`.
33
35
  */
34
36
  constructor(ctx: ClientContext);
37
+ /**
38
+ * Start forwarding this namespace's pushed document invalidations to the
39
+ * cards. The Host emits one per committed write — including the Models page's
40
+ * own model-list edits — so a card never has to poll or wait for a remount.
41
+ * @returns the disposer that withdraws the Remote subscription.
42
+ */
43
+ watch(): () => void;
44
+ /**
45
+ * Subscribe one card to namespace invalidations.
46
+ * @param listener - called with the namespace's new revision on each change.
47
+ * @returns the disposer for this one subscription.
48
+ */
49
+ subscribe(listener: (revision: number) => void): () => void;
35
50
  /**
36
51
  * Read one provider's model rows and the revision fence for writing them.
37
52
  * @param entry - the card's directory row (its settings address names the profile).
38
53
  * @returns the view, or undefined when the settings face or namespace is unavailable.
39
54
  */
40
- load(entry: ProviderDirectoryEntry): Promise<ImageInputView | undefined>;
55
+ load(entry: ProviderDirectoryEntry): Promise<ModelCapabilityView | undefined>;
41
56
  /**
42
57
  * Write the rows back as the profile's whole `models` array, under the fence
43
58
  * the load answered. The array is replaced by value — the adapter's own
@@ -47,5 +62,5 @@ export declare class ImageInputController {
47
62
  * @param revision - the fence from the load this draft was opened at.
48
63
  * @returns the write outcome the card renders from.
49
64
  */
50
- save(entry: ProviderDirectoryEntry, models: readonly ModelRow[], revision: number): Promise<ImageInputSaveOutcome>;
65
+ save(entry: ProviderDirectoryEntry, models: readonly ModelRow[], revision: number): Promise<ModelCapabilitySaveOutcome>;
51
66
  }
@@ -1,21 +1,22 @@
1
1
  /**
2
- * Browser half: the per-model image-input fold inside every llm-pi-ai provider
3
- * card of the Models settings page. The Host half is empty; provider routes
4
- * are created and edited through the page's own forms, and this plugin only
5
- * adds the one field those forms do not carry.
2
+ * Browser half: the per-model capability fold inside every llm-pi-ai provider
3
+ * card of the Models settings page the input modalities and the reasoning
4
+ * levels a model offers, the two per-model claims the page's own forms
5
+ * deliberately leave to `settings.yaml`. The Host half is empty; provider
6
+ * routes are created and edited through the page's own forms.
6
7
  */
7
8
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
8
- import type { ImageInputKey } from './locales.ts';
9
+ import type { ModelCapabilityKey } from './locales.ts';
9
10
  declare module '@deepseek-ai/dsh-client-ui-slots' {
10
11
  interface LocaleNamespaceMap {
11
- /** Per-model image-input copy on the Models page. */
12
- 'settings.models.imageInput': ImageInputKey;
12
+ /** Per-model capability copy on the Models page. */
13
+ 'settings.models.modelCapabilities': ModelCapabilityKey;
13
14
  }
14
15
  }
15
16
  /** Required browser services. */
16
17
  export declare const inject: string[];
17
18
  /**
18
- * Register the image-input fold on every llm-pi-ai provider card once the
19
+ * Register the model-capability fold on every llm-pi-ai provider card once the
19
20
  * Models section has declared the seat.
20
21
  * @param ctx - the plugin's client context.
21
22
  */
@@ -1,4 +1,4 @@
1
- /** Copy dictionaries for the image-input card. */
1
+ /** Copy dictionaries for the model-capability card. */
2
2
  /** English strings (the key-set source of truth for this pair). */
3
3
  export declare const en: {
4
4
  title: string;
@@ -8,16 +8,26 @@ export declare const en: {
8
8
  empty: string;
9
9
  inheritsHint: string;
10
10
  readOnly: string;
11
+ inputLabel: string;
11
12
  choiceDefault: string;
12
13
  choiceText: string;
13
14
  choiceImage: string;
15
+ reasoningLabel: string;
16
+ reasoningInherit: string;
17
+ reasoningNone: string;
18
+ reasoningCustom: string;
19
+ reasoningHint: string;
20
+ wireLabel: string;
21
+ wireNothing: string;
22
+ needsLevel: string;
23
+ needsWire: string;
14
24
  save: string;
15
25
  saving: string;
16
26
  saved: string;
17
27
  conflict: string;
18
28
  };
19
- /** The settings.models.imageInput namespace key union. */
20
- export type ImageInputKey = keyof typeof en;
29
+ /** The settings.models.modelCapabilities namespace key union. */
30
+ export type ModelCapabilityKey = keyof typeof en;
21
31
  /** Chinese strings (same keys as {@link en}). */
22
32
  export declare const zh: {
23
33
  [Key in keyof typeof en]: string;
@@ -1,6 +1,5 @@
1
1
  /** Pure row helpers for the per-model image-input claim. */
2
- /** One configured model row, structurally open so hidden fields survive an edit. */
3
- export type ModelRow = Record<string, unknown>;
2
+ import type { ModelRow } from './model-row.ts';
4
3
  /** The three image-input states a row's select offers. */
5
4
  export type ImageInputChoice = 'inherit' | 'text' | 'image';
6
5
  /**
@@ -28,10 +27,3 @@ export declare function withImageInput(row: ModelRow, choice: ImageInputChoice):
28
27
  * @returns the choice, or undefined for anything else.
29
28
  */
30
29
  export declare function parseImageInputChoice(value: string): ImageInputChoice | undefined;
31
- /**
32
- * The row's model id for labels.
33
- * @param row - one stored model row.
34
- * @param index - the row's zero-based position.
35
- * @returns the id, or a positional name for an id-less row.
36
- */
37
- export declare function rowId(row: ModelRow, index: number): string;
@@ -0,0 +1,10 @@
1
+ /** Row vocabulary every per-model claim shares. */
2
+ /** One configured model row, structurally open so hidden fields survive an edit. */
3
+ export type ModelRow = Record<string, unknown>;
4
+ /**
5
+ * The row's model id for labels.
6
+ * @param row - one stored model row.
7
+ * @param index - the row's zero-based position.
8
+ * @returns the id, or a positional name for an id-less row.
9
+ */
10
+ export declare function rowId(row: ModelRow, index: number): string;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Pure row helpers for the per-model reasoning-effort claim.
3
+ *
4
+ * The adapter's field is `reasoningEfforts`, and it says which thinking levels a
5
+ * model *offers* — the levels the composer's model picker lists — not which one
6
+ * a request uses. Absent keeps the installed catalog's capability (a
7
+ * hand-declared model has none, so its picker offers no level at all); `false`
8
+ * declares a non-reasoning model; a dict declares the offered levels and, per
9
+ * level, the spelling dispatch sends on the wire.
10
+ */
11
+ import type { ModelRow } from './model-row.ts';
12
+ /** Every level a row may declare, in escalation order — the adapter's own key set. */
13
+ export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
14
+ /** One level a row may declare. */
15
+ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
16
+ /** The three reasoning states a row's select offers. */
17
+ export type ReasoningChoice = 'inherit' | 'none' | 'custom';
18
+ /** One level's editor state. */
19
+ export interface ReasoningLevelDraft {
20
+ /** Whether the row declares the level, i.e. whether selectors offer it. */
21
+ readonly offered: boolean;
22
+ /**
23
+ * The wire spelling dispatch sends for the level. Only `off` may leave it
24
+ * empty — "supported, send nothing" — because for most providers not thinking
25
+ * is the parameter's absence; every other declared level needs a value.
26
+ */
27
+ readonly wire: string;
28
+ }
29
+ /** The editor's level rows, one per {@link THINKING_LEVELS} entry. */
30
+ export type ReasoningLevels = Readonly<Record<ThinkingLevel, ReasoningLevelDraft>>;
31
+ /** Why a row's declared levels cannot be saved; the adapter's own two rules. */
32
+ export type ReasoningFailure = 'needsLevel' | 'needsWire';
33
+ /**
34
+ * The levels a row with no dict of its own starts from when it is switched to a
35
+ * declared set: the three efforts an OpenAI-compatible gateway is most likely
36
+ * to serve, each spelled as its own name. `off` is deliberately left unticked:
37
+ * on the plain `reasoning_effort` wire an empty `off` is the very same request
38
+ * as naming no effort at all, so pre-offering it would promise a "stop
39
+ * thinking" choice the endpoint may not honour — the author ticks it, and
40
+ * spells it, once their endpoint says how.
41
+ */
42
+ export declare const DEFAULT_LEVELS: ReasoningLevels;
43
+ /**
44
+ * The choice a row's stored `reasoningEfforts` displays. Anything that is not
45
+ * `false` and not a plain object states no claim this card understands, and
46
+ * reads as the inheritance it leaves in place; an empty object reads as a
47
+ * declared set offering nothing, which the validator then names.
48
+ * @param row - one stored model row.
49
+ * @returns the choice the row's select shows.
50
+ */
51
+ export declare function reasoningChoice(row: ModelRow): ReasoningChoice;
52
+ /**
53
+ * The row's declared levels as editor state. A level the dict omits is not
54
+ * offered; one it carries is, with its wire spelling — an empty value, which is
55
+ * what a valueless `off:` stores as, and a value of a type the schema refuses
56
+ * both read as no spelling yet. A row declaring nothing shows the defaults a
57
+ * fresh declaration starts from.
58
+ * @param row - one stored model row.
59
+ * @returns the seven level drafts.
60
+ */
61
+ export declare function reasoningLevels(row: ModelRow): ReasoningLevels;
62
+ /**
63
+ * The row with one reasoning choice applied: `inherit` removes the field,
64
+ * `none` stores `false`, and `custom` stores exactly the levels offered — in
65
+ * escalation order, a valueless `off` as `null`, spellings trimmed so a stray
66
+ * space cannot reach the wire. Every other field, including ones this card
67
+ * never shows, survives.
68
+ * @param row - the row to patch.
69
+ * @param choice - the selected state.
70
+ * @param levels - the editor's level drafts; read only for `custom`.
71
+ * @returns a new row carrying the choice.
72
+ */
73
+ export declare function withReasoning(row: ModelRow, choice: ReasoningChoice, levels: ReasoningLevels): ModelRow;
74
+ /**
75
+ * The level set with one level offered or withdrawn. Offering a level always
76
+ * starts its spelling from the default, so a tick is never a blank the save
77
+ * then refuses, and what a withdrawn level carried is not what a reticked one
78
+ * silently revives; levels left alone keep theirs.
79
+ * @param levels - the drafts to patch.
80
+ * @param level - the level toggled.
81
+ * @param offered - whether the row should declare it.
82
+ * @returns the patched drafts, or the same object when nothing changes.
83
+ */
84
+ export declare function toggleLevel(levels: ReasoningLevels, level: ThinkingLevel, offered: boolean): ReasoningLevels;
85
+ /**
86
+ * The level set with one level's wire spelling retyped.
87
+ * @param levels - the drafts to patch.
88
+ * @param level - the level whose spelling changed.
89
+ * @param wire - the text the field now carries.
90
+ * @returns the patched drafts.
91
+ */
92
+ export declare function setWire(levels: ReasoningLevels, level: ThinkingLevel, wire: string): ReasoningLevels;
93
+ /**
94
+ * Why a row's declared levels would be refused, checked before the write so the
95
+ * card can name the row instead of answering a rejected settings mutation. The
96
+ * rules are the adapter's: every declared level but `off` needs a wire spelling,
97
+ * and a set offering no level beyond `off` declares nothing worth declaring.
98
+ * @param row - one stored model row.
99
+ * @returns the failure, or undefined for a row that can be saved.
100
+ */
101
+ export declare function reasoningFailure(row: ModelRow): ReasoningFailure | undefined;
102
+ /**
103
+ * Read a select's submitted value. The DOM hands over a bare string, so an
104
+ * unrecognized one is refused rather than cast into the union.
105
+ * @param value - the submitted option value.
106
+ * @returns the choice, or undefined for anything else.
107
+ */
108
+ export declare function parseReasoningChoice(value: string): ReasoningChoice | undefined;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@jcy2387/dsh-models-input-modalities",
3
3
  "displayName": "Models input modalities",
4
4
  "description": "DeepSeek Harness Web plugin: per-model input-modality selector on the Models settings page for third-party (pi-ai) providers",
5
- "version": "0.1.0",
5
+ "version": "0.1.2",
6
6
  "author": "jcy2387",
7
7
  "type": "module",
8
8
  "engines": {
@@ -1,27 +0,0 @@
1
- /**
2
- * One pi-ai provider card's image-input fold: the per-model modality claim the
3
- * Models page's own form does not carry. The fold loads the provider's stored
4
- * rows when first opened, edits them locally, and writes the whole `models`
5
- * array back under the revision fence the load answered — the same array
6
- * semantics the page's own cards use.
7
- */
8
- import type { ReactNode } from 'react';
9
- import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
10
- import type { ProviderDirectoryEntry } from '@deepseek-ai/dsh-client-ui-settings-models/client';
11
- import type { ImageInputSaveOutcome, ImageInputView } from './controller.ts';
12
- import type { ModelRow } from '../image-input.ts';
13
- /** The registration-side face this card receives. */
14
- export interface ImageInputFace {
15
- /** Read the provider's rows and revision fence; undefined when the settings face is unavailable. */
16
- loadModels(entry: ProviderDirectoryEntry): Promise<ImageInputView | undefined>;
17
- /** Write the rows back under the fence the load answered. */
18
- saveModels(entry: ProviderDirectoryEntry, models: readonly ModelRow[], revision: number): Promise<ImageInputSaveOutcome>;
19
- }
20
- /** Props the provider-card slot binds. */
21
- export type ImageInputCardProps = PropsRuntime<'settings.models.provider-card'> & PropsLocale<'settings.models.imageInput'> & InjectFace<ImageInputFace>;
22
- /**
23
- * Render the image-input fold of one provider card.
24
- * @param props - the card's directory row plus the bound face and copy.
25
- * @returns the fold, or nothing while the provider is still a dormant row.
26
- */
27
- export declare function ImageInputCard(props: ImageInputCardProps): ReactNode;