dsh-model-capability-sync 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/client/index.ts", "../src/client/SyncSection.tsx", "../src/sort.ts", "../src/client/locale.ts", "../src/client/apply.ts"],
4
+ "sourcesContent": ["/**\n * Browser-half entry: the module loader imports this file and calls its\n * `apply` once the injected services exist.\n * @module dsh-model-capability-sync/client\n */\nexport { apply, inject } from './apply.ts'\n", "/**\n * The capability-sync settings section: pick the provider entry, analyze its\n * models against models.dev, review what would change, then apply.\n *\n * Pure props \u2014 no context access, no subscriptions. Everything arrives through\n * the inject face `apply` builds, including the bound translate function.\n */\nimport { useEffect, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport type { CapabilityFields, LiveResponse, LiveRow, PreviewResponse, PreviewRow, RpcEnvelope } from '../types.ts'\nimport { sortByModelId } from '../sort.ts'\n\n/** One settings descriptor as the remote reports it (only the fields used here). */\nexport interface SettingsDescriptor {\n ns: string\n revision: number\n value: Record<string, unknown>\n}\n\n/** One ordered form edit understood by `settings.mutate`. */\nexport interface SettingsPathOp {\n op: 'set' | 'unset'\n path: string[]\n value?: unknown\n}\n\n/** What `apply` injects into this component. */\nexport interface SyncSectionInjected {\n preview(request: { ids: string[]; aliases?: Record<string, string>; refresh?: boolean }): Promise<RpcEnvelope<PreviewResponse>>\n live(request: { provider: string; ids: string[] }): Promise<RpcEnvelope<LiveResponse>>\n describeSettings(): Promise<SettingsDescriptor[]>\n mutateSettings(ns: string, ops: SettingsPathOp[], expectedRevision?: number): Promise<SettingsDescriptor>\n}\n\nexport interface SyncSectionProps extends SyncSectionInjected {\n t: (key: string, params?: Record<string, unknown>) => string\n}\n\n/** A model entry as the provider stores it: structurally open, so unknown fields survive. */\ntype ModelDraft = Record<string, unknown>\n\n/** The official adapter this plugin fills capabilities into. */\nconst TARGET_DEFAULT = 'llm-pi-ai'\n\n/**\n * How a writable settings namespace reads to a person.\n *\n * A namespace id is an internal identity, so the page names the thing it\n * configures instead. Provenance is a table, not a guess: the client cannot\n * see which package owns a namespace (`settings.describe` reports only\n * `ns/revision/value/base/user`), and inferring \"official\" from an `llm-`\n * prefix would eventually mislabel a third-party plugin. A namespace this\n * table does not know is shown as a plugin, with its id.\n */\nconst TARGET_DIRECTORY: Record<string, { native: boolean; nameKey: string }> = {\n 'llm-pi-ai': { native: true, nameKey: 'nameLlmPiAi' },\n 'llm-deepseek': { native: true, nameKey: 'nameLlmDeepseek' },\n 'llm-deepseek-account': { native: true, nameKey: 'nameLlmDeepseekAccount' },\n 'llm-newapi': { native: false, nameKey: 'nameLlmNewapi' },\n}\n\n/**\n * Render one namespace as provenance plus a name.\n * @param ns - the settings namespace id.\n * @param t - the bound translate function.\n * @returns copy such as `\u539F\u751F\u914D\u7F6E \u00B7 pi-ai`, never a bare namespace id.\n */\nfunction targetLabelOf(ns: string, t: SyncSectionProps['t']): string {\n const known = TARGET_DIRECTORY[ns]\n if (known === undefined) return `${t('sourcePlugin')} \u00B7 ${ns}`\n return `${known.native ? t('sourceNative') : t('sourcePlugin')} \u00B7 ${t(known.nameKey)}`\n}\n\n/** One capability field that applying a row would rewrite. */\ninterface FieldChange {\n key: keyof CapabilityFields\n before: unknown\n after: unknown\n}\n\n/** One preview row joined with the stored entry it would rewrite. */\ninterface PlanRow {\n row: PreviewRow\n model: ModelDraft\n changes: FieldChange[]\n}\n\n/** Read a string field, or the empty string. */\nfunction textOf(model: ModelDraft, key: string): string {\n const value = model[key]\n return typeof value === 'string' ? value : ''\n}\n\n/** The provider dict inside one descriptor's live value. */\nfunction providersOf(descriptor: SettingsDescriptor | undefined): Record<string, Record<string, unknown>> {\n const providers = descriptor?.value?.providers\n return typeof providers === 'object' && providers !== null\n ? providers as Record<string, Record<string, unknown>>\n : {}\n}\n\n/** The models array of one provider profile. */\nfunction modelsOf(provider: Record<string, unknown> | undefined): ModelDraft[] {\n const models = provider?.models\n return Array.isArray(models) ? models.filter((entry): entry is ModelDraft => typeof entry === 'object' && entry !== null) : []\n}\n\n/** The models of one provider profile, in display order. */\nfunction sortedModelsOf(provider: Record<string, unknown> | undefined): ModelDraft[] {\n return sortByModelId(modelsOf(provider), model => textOf(model, 'id'))\n}\n\n/** Human token count: 128000 \u2192 `128K`, 1000000 \u2192 `1M`. */\nfunction tokens(value: unknown): string {\n if (typeof value !== 'number') return '\u2014'\n if (value >= 1_000_000) return `${String(Number((value / 1_000_000).toFixed(1)))}M`\n if (value >= 1000) return `${String(Number((value / 1000).toFixed(0)))}K`\n return String(value)\n}\n\n/** Which rule produced a match, as section copy. */\nfunction viaLabel(row: PreviewRow, t: SyncSectionProps['t']): string {\n switch (row.match?.via) {\n case 'alias': return t('viaAlias')\n case 'exact': return t('viaExact')\n case 'folded': return t('viaFolded')\n case 'basename': return t('viaBasename')\n case 'suffix': return t('viaSuffix')\n default: return ''\n }\n}\n\n/**\n * Whether a stored value carries no decision of its own.\n *\n * The provider schema materializes defaults, so a field the user never set can\n * arrive as `[]` or `{}` rather than as `undefined`. Treating those as \"already\n * set\" is what made an empty `input: []` survive every fill \u2014 the field looked\n * answered while it said nothing. `false` and `0` are decisions, not blanks.\n * @param value - the stored value.\n * @returns true when filling the field loses no user intent.\n */\nfunction isBlank(value: unknown): boolean {\n if (value === undefined || value === null || value === '') return true\n if (Array.isArray(value)) return value.length === 0\n if (typeof value === 'object') return Object.keys(value as Record<string, unknown>).length === 0\n return false\n}\n\n/**\n * Merge one row's capability fields into an existing model entry.\n *\n * `name` is never written: a label is the user's or the gateway's choice, and\n * renaming models is not this page's job. The host no longer returns one, and\n * the explicit skip keeps that true even against an older host build.\n * @param model - the entry as stored.\n * @param fields - catalog capabilities.\n * @param overwrite - replace values the user already set.\n * @returns a new entry; the input is never mutated.\n */\nfunction mergeModel(model: ModelDraft, fields: CapabilityFields, overwrite: boolean): ModelDraft {\n const next: ModelDraft = { ...model }\n for (const [key, value] of Object.entries(fields)) {\n if (key === 'name' || value === undefined) continue\n if (!overwrite && !isBlank(next[key])) continue\n next[key] = value\n }\n return next\n}\n\n/**\n * The fields applying one row would actually rewrite.\n * @param model - the entry as stored.\n * @param fields - catalog capabilities.\n * @param overwrite - replace values the user already set.\n * @returns one entry per changed field, in field order; empty when nothing moves.\n */\nfunction changesBetween(model: ModelDraft, fields: CapabilityFields, overwrite: boolean): FieldChange[] {\n const next = mergeModel(model, fields, overwrite)\n const changes: FieldChange[] = []\n for (const key of Object.keys(fields) as Array<keyof CapabilityFields>) {\n const before = model[key]\n const after = next[key]\n if (JSON.stringify(before) === JSON.stringify(after)) continue\n changes.push({ key, before, after })\n }\n return changes\n}\n\n/** The image verdict of one modality list. */\nfunction imageText(value: unknown, t: SyncSectionProps['t']): string {\n if (Array.isArray(value) && value.includes('image')) return t('imageYes')\n if (isBlank(value)) return t('fieldEmpty')\n return t('imageNo')\n}\n\n/** The reasoning verdict of one effort value. */\nfunction reasoningText(value: unknown): string {\n if (value === false) return 'off'\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const keys = Object.keys(value as Record<string, unknown>)\n return keys.length === 0 ? '\u2014' : keys.join(' / ')\n }\n return '\u2014'\n}\n\n/** The image verdict the owning adapter reports right now. */\nfunction liveLabel(row: LiveRow | undefined, t: SyncSectionProps['t']): string {\n if (row === undefined) return '\u2014'\n if (row.error !== undefined) return t('liveError')\n if (row.input === undefined) return '\u2014'\n return row.input.includes('image') ? t('imageYes') : t('imageNo')\n}\n\n/**\n * One value cell: the stored value alone, or `before \u2192 after` when it moves.\n * @param change - the change for this field, when there is one.\n * @param current - the stored value.\n * @param format - renders one value as section copy.\n * @returns the cell body.\n */\nfunction valueCell(change: FieldChange | undefined, current: unknown, format: (value: unknown) => string): ReactNode {\n if (change === undefined) return <span className=\"cs-dim\">{format(current)}</span>\n return (\n <span className=\"cs-change\">\n <span className=\"cs-from\">{format(change.before)}</span>\n {' \u2192 '}{format(change.after)}\n </span>\n )\n}\n\n/**\n * Render the section.\n * @param props - injected wire face plus the bound translate function.\n * @returns the section body.\n */\nexport function SyncSection(props: SyncSectionProps): ReactNode {\n const { t, preview, live, describeSettings, mutateSettings } = props\n const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading')\n const [errorText, setErrorText] = useState<string | undefined>(undefined)\n const [descriptors, setDescriptors] = useState<SettingsDescriptor[]>([])\n const [targetNs, setTargetNs] = useState(TARGET_DEFAULT)\n const [providerKey, setProviderKey] = useState('')\n const [models, setModels] = useState<ModelDraft[]>([])\n const [rows, setRows] = useState<PreviewRow[] | undefined>(undefined)\n const [liveRows, setLiveRows] = useState<ReadonlyMap<string, LiveRow>>(new Map())\n const [selected, setSelected] = useState<ReadonlySet<string>>(new Set())\n const [overwrite, setOverwrite] = useState(false)\n const [query, setQuery] = useState('')\n const [onlyChanged, setOnlyChanged] = useState(false)\n const [busy, setBusy] = useState<'idle' | 'analyzing' | 'applying'>('idle')\n const [notice, setNotice] = useState<string | undefined>(undefined)\n const [catalogInfo, setCatalogInfo] = useState<string | undefined>(undefined)\n const [staleInfo, setStaleInfo] = useState<string | undefined>(undefined)\n\n /** Everything a write would touch, joined with the stored entries. */\n const plan: PlanRow[] = (() => {\n if (rows === undefined) return []\n const stored = new Map(models.map(model => [textOf(model, 'id'), model]))\n return rows.map((row) => {\n const model = stored.get(row.id) ?? {}\n const fields = row.fields\n return {\n row,\n model,\n changes: fields === undefined ? [] : changesBetween(model, fields, overwrite),\n }\n })\n })()\n const matched = plan.filter(entry => entry.row.fields !== undefined)\n const unmatched = plan.filter(entry => entry.row.fields === undefined)\n const changing = matched.filter(entry => entry.changes.length > 0)\n const changeFields = changing.reduce((total, entry) => total + entry.changes.length, 0)\n const liveImage = matched.filter(entry => liveRows.get(entry.row.id)?.input?.includes('image') === true).length\n const liveText = matched.filter((entry) => {\n const answer = liveRows.get(entry.row.id)\n return answer?.input !== undefined && !answer.input.includes('image')\n }).length\n\n /** Read every configurable entry and select the default target. */\n const load = async (): Promise<void> => {\n setStatus('loading')\n setErrorText(undefined)\n try {\n const list = await describeSettings()\n setDescriptors(list)\n const candidates = list.filter(entry => Object.keys(providersOf(entry)).length > 0)\n const target = candidates.find(entry => entry.ns === TARGET_DEFAULT) ?? candidates[0]\n if (target === undefined) {\n setStatus('error')\n setErrorText(t('noTarget'))\n return\n }\n const providers = providersOf(target)\n const firstKey = Object.keys(providers)[0] ?? ''\n setTargetNs(target.ns)\n setProviderKey(firstKey)\n setModels(sortedModelsOf(providers[firstKey]))\n setRows(undefined)\n setLiveRows(new Map())\n setSelected(new Set())\n setQuery('')\n setStatus('ready')\n } catch (error) {\n setStatus('error')\n setErrorText(error instanceof Error ? error.message : String(error))\n }\n }\n\n useEffect(() => { void load() }, [])\n\n /** Show one provider's models, in display order, with a clean slate. */\n const showProvider = (ns: string, key: string): void => {\n const providers = providersOf(descriptors.find(entry => entry.ns === ns))\n const resolved = key !== '' && providers[key] !== undefined ? key : Object.keys(providers)[0] ?? ''\n setTargetNs(ns)\n setProviderKey(resolved)\n setModels(sortedModelsOf(providers[resolved]))\n setRows(undefined)\n setLiveRows(new Map())\n setSelected(new Set())\n setNotice(undefined)\n setQuery('')\n }\n\n /** Ask the owning adapter what a request sees for these ids. */\n const refreshLive = async (provider: string, ids: string[]): Promise<void> => {\n try {\n const answer = await live({ provider, ids })\n setLiveRows(answer.ok ? new Map(answer.value.rows.map(row => [row.id, row])) : new Map())\n } catch {\n setLiveRows(new Map())\n }\n }\n\n /** Re-read the configuration without losing the analysis on screen. */\n const reloadConfig = async (): Promise<void> => {\n const list = await describeSettings()\n setDescriptors(list)\n const descriptor = list.find(entry => entry.ns === targetNs)\n if (descriptor !== undefined) setModels(sortedModelsOf(providersOf(descriptor)[providerKey]))\n }\n\n /** Preselect every row that would actually change. */\n const selectChanging = (source: readonly PreviewRow[], stored: readonly ModelDraft[], mode: boolean): void => {\n const byId = new Map(stored.map(model => [textOf(model, 'id'), model]))\n setSelected(new Set(source\n .filter(row => row.fields !== undefined && changesBetween(byId.get(row.id) ?? {}, row.fields, mode).length > 0)\n .map(row => row.id)))\n }\n\n /** Resolve every configured id against the catalog. */\n const analyze = async (refresh: boolean): Promise<void> => {\n setBusy('analyzing')\n setErrorText(undefined)\n setNotice(undefined)\n setStaleInfo(undefined)\n try {\n const ids = models.map(model => textOf(model, 'id')).filter(id => id.length > 0)\n const result = await preview({ ids, ...refresh ? { refresh: true } : {} })\n if (!result.ok) throw new Error(result.error.message)\n setRows(result.value.rows)\n selectChanging(result.value.rows, models, overwrite)\n setCatalogInfo(t('catalogInfo', {\n models: result.value.catalogModels,\n time: new Date(result.value.fetchedAt).toLocaleString(),\n }))\n setStaleInfo(result.value.stale === true\n ? t('catalogStale', { time: new Date(result.value.fetchedAt).toLocaleString() })\n : undefined)\n // The entry we are about to write may not be the route a request takes,\n // so this is the only honest answer to \"did the write take effect\".\n await refreshLive(providerKey, ids)\n } catch (error) {\n setErrorText(error instanceof Error ? error.message : String(error))\n } finally {\n setBusy('idle')\n }\n }\n\n /** Switch the overwrite mode and follow it with the selection. */\n const setOverwriteMode = (mode: boolean): void => {\n setOverwrite(mode)\n if (rows === undefined) return\n selectChanging(rows, models, mode)\n }\n\n /** Write the selected rows into the provider entry. */\n const applyRows = async (): Promise<void> => {\n const descriptor = descriptors.find(entry => entry.ns === targetNs)\n if (descriptor === undefined) return\n const targets = new Map(changing\n .filter(entry => selected.has(entry.row.id))\n .map(entry => [entry.row.id, entry]))\n if (targets.size === 0) {\n setNotice(t('nothingSelected'))\n return\n }\n let written = 0\n const next = models.map((model) => {\n const entry = targets.get(textOf(model, 'id'))\n if (entry?.row.fields === undefined) return model\n written += 1\n return mergeModel(model, entry.row.fields, overwrite)\n })\n setBusy('applying')\n setErrorText(undefined)\n setNotice(undefined)\n try {\n const view = await mutateSettings(targetNs, [{\n op: 'set',\n path: ['providers', providerKey, 'models'],\n value: next,\n }], descriptor.revision)\n setModels(next)\n // Keep the held revision current, so a second apply does not conflict.\n setDescriptors(previous => previous.map(entry =>\n entry.ns === targetNs && typeof view.revision === 'number'\n ? { ...entry, revision: view.revision }\n : entry))\n setNotice(t('applied', { count: written }))\n await refreshLive(providerKey, next.map(model => textOf(model, 'id')).filter(id => id.length > 0))\n } catch (error) {\n const code = (error as { code?: string } | undefined)?.code\n if (code === 'settings/conflict') {\n // Someone else wrote this entry while the page was open. Reload the\n // baseline instead of retrying blindly: a silent retry would overwrite\n // whatever they just saved.\n try {\n await reloadConfig()\n } catch {\n // Keep the conflict message; the reload is only a convenience.\n }\n setNotice(t('conflictReload'))\n } else {\n setErrorText(error instanceof Error ? error.message : String(error))\n }\n } finally {\n setBusy('idle')\n }\n }\n\n if (status === 'loading') return <div className=\"cs-hint\">{t('loading')}</div>\n\n if (status === 'error') {\n return (\n <div className=\"cs-field\">\n <p className=\"cs-error\">{t('loadFailed')}: {errorText}</p>\n <button type=\"button\" className=\"cs-button\" onClick={() => { void load() }}>{t('retry')}</button>\n </div>\n )\n }\n\n const descriptor = descriptors.find(entry => entry.ns === targetNs)\n const candidates = descriptors.filter(entry => Object.keys(providersOf(entry)).length > 0)\n const providerKeys = Object.keys(providersOf(descriptor))\n const needle = query.trim().toLowerCase()\n const visible = matched.filter((entry) => {\n if (onlyChanged && entry.changes.length === 0) return false\n if (needle === '') return true\n return entry.row.id.toLowerCase().includes(needle)\n || entry.row.match?.modelId.toLowerCase().includes(needle) === true\n })\n const selectable = visible.filter(entry => entry.changes.length > 0)\n const toggle = (id: string): void => {\n const next = new Set(selected)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n setSelected(next)\n }\n\n return (\n <div>\n <p className=\"cs-hint\">{t('intro')}</p>\n\n <div className=\"cs-row\">\n {candidates.length > 1\n ? (\n <label className=\"cs-field\">\n <span className=\"cs-hint\">{t('writeTarget')}</span>\n <select className=\"cs-select\" value={targetNs} onChange={(event) => { showProvider(event.target.value, '') }}>\n {candidates.map(entry => (\n <option key={entry.ns} value={entry.ns}>{targetLabelOf(entry.ns, t)}</option>\n ))}\n </select>\n </label>\n )\n : (\n <div className=\"cs-field\" title={targetNs}>\n <span className=\"cs-hint\">{t('writeTarget')}</span>\n <span className=\"cs-target\">{targetLabelOf(targetNs, t)}</span>\n </div>\n )}\n <label className=\"cs-field\">\n <span className=\"cs-hint\">{t('provider')}</span>\n <select className=\"cs-select\" value={providerKey} onChange={(event) => { showProvider(targetNs, event.target.value) }}>\n {providerKeys.map(key => <option key={key} value={key}>{key}</option>)}\n </select>\n </label>\n <span className=\"cs-hint\">{t('modelCount', { count: models.length })}</span>\n <span className=\"cs-hint\">{t('willSort')}</span>\n </div>\n\n {models.length === 0\n ? <p className=\"cs-hint\">{t('noModels')}</p>\n : (\n <div className=\"cs-row\">\n <button type=\"button\" className=\"cs-button cs-button--primary\" disabled={busy !== 'idle'} onClick={() => { void analyze(false) }}>\n {busy === 'analyzing' ? t('analyzing') : t('analyze')}\n </button>\n <button type=\"button\" className=\"cs-button\" disabled={busy !== 'idle'} onClick={() => { void analyze(true) }}>\n {t('refreshCatalog')}\n </button>\n <label className=\"cs-hint\" title={t('overwriteHint')}>\n <input type=\"checkbox\" checked={overwrite} onChange={(event) => { setOverwriteMode(event.target.checked) }} />\n {' '}{t('overwrite')}\n </label>\n </div>\n )}\n\n {errorText !== undefined ? <p className=\"cs-error\">{errorText}</p> : null}\n {notice !== undefined ? <p className=\"cs-hint\">{notice}</p> : null}\n {catalogInfo !== undefined ? <p className=\"cs-hint\">{catalogInfo}</p> : null}\n {staleInfo !== undefined ? <p className=\"cs-warn\">{staleInfo}</p> : null}\n\n {rows !== undefined && matched.length > 0\n ? (\n <>\n <div className=\"cs-row\">\n <input\n className=\"cs-input\"\n type=\"search\"\n value={query}\n placeholder={t('search')}\n onChange={(event) => { setQuery(event.target.value) }}\n />\n <label className=\"cs-hint\">\n <input type=\"checkbox\" checked={onlyChanged} onChange={(event) => { setOnlyChanged(event.target.checked) }} />\n {' '}{t('onlyChanged')}\n </label>\n <span className=\"cs-hint\">\n {changing.length === 0\n ? t('noChanges')\n : t('changeSummary', { rows: changing.length, fields: changeFields })}\n </span>\n </div>\n\n {visible.length === 0\n ? <p className=\"cs-hint\">{t('filteredEmpty')}</p>\n : (\n <div className=\"cs-table-wrap\">\n <table className=\"cs-table\">\n <thead>\n <tr>\n <th>\n <input\n type=\"checkbox\"\n aria-label={t('selectAll')}\n checked={selectable.length > 0 && selectable.every(entry => selected.has(entry.row.id))}\n onChange={(event) => {\n const keep = event.target.checked ? new Set(selectable.map(entry => entry.row.id)) : new Set<string>()\n setSelected(keep)\n }}\n />\n </th>\n <th>{t('colModel')}</th>\n <th>{t('colMatch')}</th>\n <th>{t('colContext')}</th>\n <th>{t('colOutput')}</th>\n <th>{t('colImage')}</th>\n <th title={t('colLiveHint')}>{t('colLive')}</th>\n <th>{t('colReasoning')}</th>\n </tr>\n </thead>\n <tbody>\n {visible.map(({ row, model, changes }) => {\n const changeOf = (key: keyof CapabilityFields): FieldChange | undefined =>\n changes.find(change => change.key === key)\n return (\n <tr key={row.id} className={changes.length > 0 ? 'cs-row--changed' : undefined}>\n <td>\n <input\n type=\"checkbox\"\n aria-label={row.id}\n checked={selected.has(row.id)}\n disabled={changes.length === 0}\n onChange={() => { toggle(row.id) }}\n />\n </td>\n <td className=\"cs-id\">{row.id}</td>\n <td className=\"cs-dim\">\n {row.match === undefined ? '\u2014' : `${row.match.provider}/${row.match.modelId}`}\n {' '}<span className=\"cs-badge\">{viaLabel(row, t)}</span>\n </td>\n <td>{valueCell(changeOf('contextWindow'), model.contextWindow, tokens)}</td>\n <td>{valueCell(changeOf('maxTokens'), model.maxTokens, tokens)}</td>\n <td>\n {valueCell(changeOf('input'), model.input, value => imageText(value, t))}\n </td>\n <td>\n <span className={liveRows.get(row.id)?.input?.includes('image') === true ? 'cs-badge cs-badge--image' : 'cs-dim'}>\n {liveLabel(liveRows.get(row.id), t)}\n </span>\n </td>\n <td className=\"cs-dim\">\n {valueCell(changeOf('reasoningEfforts'), model.reasoningEfforts, reasoningText)}\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n )}\n\n {liveRows.size > 0\n ? <p className=\"cs-hint\">{t('liveSummary', { image: liveImage, text: liveText })}</p>\n : null}\n <div className=\"cs-row\">\n <button\n type=\"button\"\n className=\"cs-button cs-button--primary\"\n disabled={busy !== 'idle' || !changing.some(entry => selected.has(entry.row.id))}\n onClick={() => { void applyRows() }}\n >\n {busy === 'applying' ? t('applying') : t('apply')}\n </button>\n </div>\n </>\n )\n : null}\n\n {unmatched.length > 0\n ? (\n <div>\n <p className=\"cs-hint\">{t('unmatchedHint')}</p>\n <ul className=\"cs-unmatched\">\n {unmatched.map(entry => (\n <li key={entry.row.id}>\n {entry.row.id}\n {entry.row.reason === undefined ? null : <span className=\"cs-dim\"> \u2014 {entry.row.reason}</span>}\n </li>\n ))}\n </ul>\n </div>\n )\n : null}\n </div>\n )\n}\n", "/**\n * Ordering for one provider's model list.\n *\n * A gateway id carries its vendor as a path prefix (`qoder-gj/Qwen3.8-Flash`),\n * so sorting on the whole id would group by nothing useful: the model family\n * is the part after the slash, and the vendor is what a reader scans for. The\n * order is therefore: vendor prefix first, then the model name inside it. An\n * id without a prefix has only its name to sort on.\n *\n * Comparison is case-insensitive, with the raw id as the final tie-break, so\n * two runs on the same configuration always produce the same array.\n * @module dsh-model-capability-sync/sort\n */\n\n/** The two ordering keys of one id: the vendor group, then the model name. */\nexport interface ModelSortKey {\n /** The prefix before the last slash, or the whole id when there is none. */\n group: string\n /** The part after the last slash, or the whole id when there is none. */\n name: string\n}\n\n/**\n * Split one id into its ordering keys.\n * @param id - the gateway model id exactly as configured.\n * @returns folded keys; the caller compares them, never displays them.\n */\nexport function modelSortKey(id: string): ModelSortKey {\n const slash = id.lastIndexOf('/')\n if (slash === -1) {\n const folded = id.toLowerCase()\n return { group: folded, name: folded }\n }\n return { group: id.slice(0, slash).toLowerCase(), name: id.slice(slash + 1).toLowerCase() }\n}\n\n/**\n * Compare two ids in display order.\n * @param left - first id.\n * @param right - second id.\n * @returns negative when `left` sorts first, positive when `right` does.\n */\nexport function compareModelIds(left: string, right: string): number {\n const a = modelSortKey(left)\n const b = modelSortKey(right)\n if (a.group !== b.group) return a.group < b.group ? -1 : 1\n if (a.name !== b.name) return a.name < b.name ? -1 : 1\n // Same folded pair: keep the raw spelling as the tie-break so the result is\n // deterministic instead of depending on the input order.\n if (left === right) return 0\n return left < right ? -1 : 1\n}\n\n/**\n * Return a sorted copy of a model list.\n * @param items - entries in any order; the input array is never mutated.\n * @param idOf - reads the model id out of one entry.\n * @returns the entries in vendor-then-name order.\n */\nexport function sortByModelId<T>(items: readonly T[], idOf: (item: T) => string): T[] {\n return [...items].sort((left, right) => compareModelIds(idOf(left), idOf(right)))\n}\n", "/**\n * Copy dictionaries for the capability-sync section. Chinese first (the dsh\n * web convention); English mirrors it key for key.\n */\n\nexport const zh = {\n nav: '\u6A21\u578B\u80FD\u529B\u540C\u6B65',\n loading: '\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E\u2026',\n intro: '\u4ECE models.dev \u67E5\u8BE2\u5DF2\u914D\u7F6E\u6A21\u578B\u7684\u80FD\u529B\uFF08\u4E0A\u4E0B\u6587\u7A97\u53E3\u3001\u8F93\u51FA\u4E0A\u9650\u3001\u662F\u5426\u652F\u6301\u56FE\u7247\u3001\u63A8\u7406\u6863\u4F4D\uFF09\uFF0C\u5E76\u5199\u5165\u6240\u9009\u4F9B\u5E94\u5546\u7684\u914D\u7F6E\u3002\u672C\u9875\u53EA\u5199\u914D\u7F6E\uFF0C\u4E0D\u53D1\u9001\u4EFB\u4F55\u63A8\u7406\u8BF7\u6C42\u3002',\n writeTarget: '\u5199\u5165\u4F4D\u7F6E',\n sourceNative: '\u539F\u751F\u914D\u7F6E',\n sourcePlugin: '\u63D2\u4EF6\u914D\u7F6E',\n nameLlmPiAi: 'pi-ai \u9002\u914D\u5668',\n nameLlmDeepseek: 'DeepSeek \u5B98\u65B9',\n nameLlmDeepseekAccount: 'DeepSeek \u8D26\u53F7',\n nameLlmNewapi: 'NewAPI',\n provider: '\u4F9B\u5E94\u5546',\n modelCount: '\u5171 {count} \u4E2A\u6A21\u578B',\n willSort: '\u5199\u5165\u65F6\u6309\u300C\u524D\u7F00\u5206\u7EC4 + \u540D\u79F0\u300D\u6392\u5E8F',\n search: '\u641C\u7D22\u6A21\u578B ID \u6216\u76EE\u5F55 ID',\n onlyChanged: '\u53EA\u770B\u6709\u53D8\u5316\u7684',\n changeSummary: '\u5C06\u4FEE\u6539 {rows} \u884C \u00B7 {fields} \u4E2A\u5B57\u6BB5',\n noChanges: '\u6CA1\u6709\u9700\u8981\u5199\u5165\u7684\u53D8\u5316\uFF08\u76EE\u5F55\u503C\u4E0E\u4F60\u73B0\u6709\u914D\u7F6E\u4E00\u81F4\uFF09\u3002',\n filteredEmpty: '\u6CA1\u6709\u7B26\u5408\u7B5B\u9009\u6761\u4EF6\u7684\u6A21\u578B\u3002',\n fieldEmpty: '\u672A\u8BBE\u7F6E',\n conflictReload: '\u914D\u7F6E\u5DF2\u88AB\u5176\u4ED6\u6539\u52A8\u66F4\u65B0\uFF0C\u5DF2\u91CD\u65B0\u8BFB\u53D6\u6700\u65B0\u503C\uFF1B\u8BF7\u786E\u8BA4\u540E\u518D\u70B9\u4E00\u6B21\u300C\u5E94\u7528\u300D\u3002',\n analyze: '\u5206\u6790\u80FD\u529B',\n analyzing: '\u6B63\u5728\u5339\u914D\u2026',\n refreshCatalog: '\u91CD\u65B0\u4E0B\u8F7D\u76EE\u5F55',\n apply: '\u5E94\u7528',\n applying: '\u6B63\u5728\u5199\u5165\u2026',\n applied: '\u5DF2\u5199\u5165 {count} \u4E2A\u6A21\u578B',\n overwrite: '\u8986\u76D6\u5DF2\u6709\u503C',\n overwriteHint: '\u4E0D\u52FE\u9009\u65F6\u53EA\u586B\u7A7A\u767D\u5B57\u6BB5\uFF0C\u4F60\u624B\u586B\u8FC7\u7684\u503C\u4E0D\u4F1A\u88AB\u6539\u5199\u3002',\n selectAll: '\u5168\u9009',\n colModel: '\u6A21\u578B ID',\n colMatch: '\u5339\u914D',\n colContext: '\u4E0A\u4E0B\u6587',\n colOutput: '\u8F93\u51FA',\n colImage: '\u56FE\u7247',\n colLive: '\u5F53\u524D\u751F\u6548',\n colLiveHint: '\u7531\u771F\u6B63\u63A5\u7BA1\u8FD9\u6761\u8DEF\u7531\u7684\u9002\u914D\u5668\u4E0A\u62A5\uFF0C\u4E5F\u5C31\u662F\u804A\u5929\u8BF7\u6C42\u5B9E\u9645\u770B\u5230\u7684\u503C\u3002\u82E5\u5B83\u4E0E\u300C\u56FE\u7247\u300D\u5217\u4E0D\u4E00\u81F4\uFF0C\u8BF4\u660E\u914D\u7F6E\u88AB\u53E6\u4E00\u4E2A\u63D2\u4EF6\u62A2\u5360\u4E86\u8DEF\u7531\u3002',\n liveError: '\u65E0\u6CD5\u8BFB\u53D6',\n liveSummary: '\u5F53\u524D\u751F\u6548\uFF1A{image} \u4E2A\u652F\u6301\u56FE\u7247\uFF0C{text} \u4E2A\u7EAF\u6587\u672C\u3002\u82E5\u4E0E\u300C\u56FE\u7247\u300D\u5217\u4E0D\u4E00\u81F4\uFF0C\u8BF4\u660E\u8FD9\u6761\u8DEF\u7531\u7531\u522B\u7684\u63D2\u4EF6\u63A5\u7BA1\uFF0C\u5199\u5165\u672C\u914D\u7F6E\u4E0D\u4F1A\u751F\u6548\u3002',\n colReasoning: '\u63A8\u7406\u6863\u4F4D',\n imageYes: '\u652F\u6301',\n imageNo: '\u7EAF\u6587\u672C',\n unmatched: '\u672A\u5339\u914D',\n unmatchedHint: '\u8FD9\u4E9B\u6A21\u578B\u5728 models.dev \u91CC\u627E\u4E0D\u5230\uFF0C\u4FDD\u6301\u539F\u6837\u3002\u53EF\u5728\u522B\u540D\u8868\u91CC\u624B\u52A8\u6307\u5B9A\u3002',\n noModels: '\u8BE5\u4F9B\u5E94\u5546\u4E0B\u8FD8\u6CA1\u6709\u6A21\u578B\u3002',\n noTarget: '\u6CA1\u6709\u627E\u5230\u53EF\u5199\u7684\u914D\u7F6E\u6761\u76EE\uFF08\u9700\u8981\u5DF2\u542F\u7528\u7684 llm-pi-ai \u4F9B\u5E94\u5546\u914D\u7F6E\uFF09\u3002',\n loadFailed: '\u52A0\u8F7D\u5931\u8D25',\n retry: '\u91CD\u8BD5',\n viaAlias: '\u522B\u540D',\n viaExact: '\u7CBE\u786E',\n viaFolded: '\u5FFD\u7565\u5927\u5C0F\u5199',\n viaBasename: '\u53BB\u524D\u7F00',\n viaSuffix: '\u53BB\u53D8\u4F53\u540E\u7F00',\n catalogInfo: '\u76EE\u5F55\uFF1A{models} \u4E2A\u6A21\u578B \u00B7 {time}',\n catalogStale: '\u5728\u7EBF\u76EE\u5F55\u4E0B\u8F7D\u5931\u8D25\uFF0C\u6B63\u5728\u4F7F\u7528\u672C\u673A\u7F13\u5B58\uFF08{time}\uFF09\u3002\u6570\u636E\u53EF\u80FD\u4E0D\u662F\u6700\u65B0\uFF0C\u5B57\u6BB5\u542B\u4E49\u4E0D\u53D8\u3002',\n nothingSelected: '\u6CA1\u6709\u9009\u4E2D\u4EFB\u4F55\u6A21\u578B\u3002',\n done: '\u5B8C\u6210\u3002',\n} as const\n\nexport type SyncKey = keyof typeof zh\n\nexport const en: Record<SyncKey, string> = {\n nav: 'Model capabilities',\n loading: 'Reading configuration\u2026',\n intro: 'Look up capabilities (context window, output cap, image input, reasoning efforts) for the models already configured here, then write them into the selected provider. This page only writes configuration; it never sends a model request.',\n writeTarget: 'Write to',\n sourceNative: 'Built in',\n sourcePlugin: 'Plugin',\n nameLlmPiAi: 'pi-ai adapter',\n nameLlmDeepseek: 'DeepSeek official',\n nameLlmDeepseekAccount: 'DeepSeek account',\n nameLlmNewapi: 'NewAPI',\n provider: 'Provider',\n modelCount: '{count} models',\n willSort: 'Written sorted by vendor prefix, then name',\n search: 'Search model id or catalog id',\n onlyChanged: 'Only rows that change',\n changeSummary: 'Will change {rows} rows \u00B7 {fields} fields',\n noChanges: 'Nothing to write: the catalog agrees with your configuration.',\n filteredEmpty: 'No model matches the filter.',\n fieldEmpty: 'unset',\n conflictReload: 'The configuration changed elsewhere; the latest values were reloaded. Check them, then press Apply again.',\n analyze: 'Analyze',\n analyzing: 'Matching\u2026',\n refreshCatalog: 'Re-download catalog',\n apply: 'Apply',\n applying: 'Writing\u2026',\n applied: 'Wrote {count} models',\n overwrite: 'Overwrite existing values',\n overwriteHint: 'Unchecked: only blank fields are filled, so values you typed are preserved.',\n selectAll: 'Select all',\n colModel: 'Model id',\n colMatch: 'Match',\n colContext: 'Context',\n colOutput: 'Output',\n colImage: 'Image',\n colLive: 'In effect',\n colLiveHint: 'Reported by the adapter that actually serves this route \u2014 what a chat request sees. A value that disagrees with the Image column means another plugin owns the route.',\n liveError: 'unreadable',\n liveSummary: 'In effect: {image} image-capable, {text} text-only. If this disagrees with the Image column, another plugin serves the route and writing this entry changes nothing.',\n colReasoning: 'Reasoning',\n imageYes: 'yes',\n imageNo: 'text only',\n unmatched: 'unmatched',\n unmatchedHint: 'These ids are absent from models.dev and stay untouched. Add an alias to map them by hand.',\n noModels: 'This provider has no models yet.',\n noTarget: 'No writable configuration entry found (an enabled llm-pi-ai provider is required).',\n loadFailed: 'Load failed',\n retry: 'Retry',\n viaAlias: 'alias',\n viaExact: 'exact',\n viaFolded: 'case-folded',\n viaBasename: 'basename',\n viaSuffix: 'suffix',\n catalogInfo: 'Catalog: {models} models \u00B7 {time}',\n catalogStale: 'The online catalog download failed; using the local copy from {time}. The facts still mean the same thing, they may just be older.',\n nothingSelected: 'Nothing selected.',\n done: 'Done.',\n}\n", "/**\n * Browser half: one settings section plus the host RPC call it needs.\n *\n * The section is a `settings.section` slot of its own, so no shell edit is\n * required. Configuration reads and writes go through the official settings\n * remote (`remote.settings`), which is the same surface the shell's own Models\n * page uses; the only custom channel is the catalog lookup, which must run\n * host-side to avoid a cross-origin download.\n * @module dsh-model-capability-sync/client\n */\nimport type { RpcEnvelope } from '../types.ts'\nimport type { LiveResponse, PreviewRequest, PreviewResponse } from '../types.ts'\nimport { SyncSection } from './SyncSection.tsx'\nimport type { SettingsDescriptor, SettingsPathOp, SyncSectionInjected } from './SyncSection.tsx'\nimport { en, zh } from './locale.ts'\n\n/** Copy namespace owned by this plugin. */\nconst NS = 'settings.capability-sync'\n/** Own host RPC channel. */\nconst CHANNEL = '/model-capability-sync'\n\n/** The slice of the browser context this plugin uses. */\ninterface ClientContext {\n effect(callback: () => (() => void) | void, label?: string): void\n get(service: string): unknown\n locale: {\n register(ns: string, dictionaries: { zh: unknown; en: unknown }): () => void\n bind(ns: string): (key: string, params?: Record<string, unknown>) => string\n }\n slots: {\n inject(name: string, callback: () => () => void): void\n register(options: Record<string, unknown>, component: unknown): () => void\n }\n}\n\n/** The connection face carrying `rpc.call`. */\ninterface ConnectionFace {\n rpc: { call(channel: string, endpoint: string, payload: unknown): Promise<unknown> }\n}\n\n/** The remote face carrying the settings namespace. */\ninterface RemoteFace {\n settings: {\n describe(): Promise<unknown>\n mutate(ns: string, ops: SettingsPathOp[], expectedRevision?: number): Promise<unknown>\n }\n}\n\n/** The document `settings.describe` answers with, once its envelope is opened. */\ninterface SettingsDocument {\n writable?: boolean\n namespaces?: SettingsDescriptor[]\n}\n\n/** A refusal as the Remote namespace states it. */\ninterface Refusal {\n /** Machine-readable reason; `settings/conflict` is the one callers act on. */\n code?: string\n /** Human message for the page. */\n message: string\n}\n\n/**\n * Read the refusal out of a Remote answer envelope.\n * @param answer - whatever the Remote namespace resolved with.\n * @returns the refusal when the answer carries one, otherwise undefined.\n */\nfunction refusalOf(answer: unknown): Refusal | undefined {\n if (typeof answer !== 'object' || answer === null) return undefined\n const envelope = answer as { ok?: unknown; error?: { code?: unknown; message?: unknown } }\n if (envelope.ok !== false) return undefined\n return {\n ...typeof envelope.error?.code === 'string' ? { code: envelope.error.code } : {},\n message: typeof envelope.error?.message === 'string' ? envelope.error.message : 'settings request refused',\n }\n}\n\n/**\n * Open a Remote answer envelope.\n *\n * Every `remote.settings` method answers `{ok: true, value}` or\n * `{ok: false, error}`; a few surfaces hand back the bare value instead, so\n * both shapes are accepted rather than guessed at one. A refusal keeps its\n * `code`, so the section can tell a stale revision apart from a real failure.\n * @param answer - whatever the Remote namespace resolved with.\n * @returns the value the answer carries.\n * @throws the refusal, carrying `code` when the answer had one.\n */\nfunction unwrap(answer: unknown): unknown {\n const refusal = refusalOf(answer)\n if (refusal !== undefined) {\n throw Object.assign(new Error(refusal.message), refusal.code === undefined ? {} : { code: refusal.code })\n }\n if (typeof answer === 'object' && answer !== null && 'ok' in answer && 'value' in answer) {\n return (answer as { value: unknown }).value\n }\n return answer\n}\n\n/**\n * The namespace views a describe answer carries.\n * @param answer - the `settings.describe` answer, enveloped or bare.\n * @returns every served namespace view.\n * @throws when the answer holds no namespace list.\n */\nfunction namespacesOf(answer: unknown): SettingsDescriptor[] {\n const document = unwrap(answer) as SettingsDocument | SettingsDescriptor[] | undefined\n if (Array.isArray(document)) return document\n const namespaces = document?.namespaces\n if (!Array.isArray(namespaces)) throw new Error('settings.describe returned no namespace list')\n return namespaces\n}\n\n/** Required services (cordis fiber inject). */\nexport const inject = ['slots', 'locale', 'connection', 'remote', 'remote.settings']\n\n/** Section styles, riding the shell's `--dsw-alias-*` tokens for both themes. */\nconst SECTION_CSS = `\n.cs-field { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }\n.cs-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; }\n.cs-select {\n box-sizing: border-box; padding: 6px 10px; border-radius: 8px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font: inherit; font-size: 13px;\n}\n.cs-button {\n padding: 6px 12px; border-radius: 6px; font: inherit; font-size: 13px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: transparent; color: var(--dsw-alias-label-primary);\n cursor: pointer;\n}\n.cs-button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); }\n.cs-button:disabled { opacity: 0.4; cursor: default; }\n.cs-button--primary {\n border-color: transparent;\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n.cs-button--primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover); }\n.cs-hint { font-size: 12px; color: var(--dsw-alias-label-tertiary); line-height: 18px; }\n.cs-error { color: var(--dsw-alias-state-error-primary); font-size: 12px; }\n.cs-warn { color: var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary)); font-size: 12px; line-height: 18px; }\n.cs-input {\n box-sizing: border-box; padding: 6px 10px; border-radius: 8px; min-width: 200px;\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font: inherit; font-size: 13px;\n}\n.cs-target { font-size: 13px; color: var(--dsw-alias-label-primary); line-height: 30px; }\n.cs-table-wrap {\n max-height: 460px; overflow: auto; margin-bottom: 12px;\n border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px;\n}\n.cs-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12px; }\n.cs-table th {\n position: sticky; top: 0; z-index: 1;\n text-align: left; font-weight: 500; color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-1);\n padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l2); white-space: nowrap;\n}\n.cs-table td {\n padding: 6px 8px; border-bottom: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-primary); vertical-align: top;\n}\n.cs-table tr:last-child td { border-bottom: none; }\n.cs-row--changed td { background: var(--dsw-alias-bg-layer-2, transparent); }\n.cs-id { font-variant-numeric: tabular-nums; word-break: break-all; }\n.cs-dim { color: var(--dsw-alias-label-tertiary); }\n.cs-change { white-space: nowrap; }\n.cs-from { color: var(--dsw-alias-label-tertiary); text-decoration: line-through; }\n.cs-badge {\n display: inline-block; padding: 0 6px; border-radius: 9px; font-size: 11px; line-height: 18px;\n border: 1px solid var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary);\n}\n.cs-badge--image { border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-brand-primary); }\n.cs-unmatched { margin: 8px 0 0; padding-left: 18px; color: var(--dsw-alias-label-tertiary); font-size: 12px; }\n`\n\n/**\n * Register the copy dictionary, the section styles, and the section itself.\n * @param ctx - browser root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'model-capability-sync: copy dictionaries')\n\n if (typeof document !== 'undefined') {\n ctx.effect(() => {\n const element = document.createElement('style')\n element.textContent = SECTION_CSS\n document.head.append(element)\n return () => { element.remove() }\n }, 'model-capability-sync: section styles')\n }\n\n const connection = ctx.get('connection') as ConnectionFace\n const remote = ctx.get('remote') as RemoteFace\n const t = ctx.locale.bind(NS)\n\n const injected = (): SyncSectionInjected => ({\n preview: request =>\n connection.rpc.call(CHANNEL, 'preview', request) as Promise<RpcEnvelope<PreviewResponse>>,\n live: request =>\n connection.rpc.call(CHANNEL, 'live', request) as Promise<RpcEnvelope<LiveResponse>>,\n describeSettings: async () => namespacesOf(await remote.settings.describe()),\n mutateSettings: async (ns, ops, expectedRevision) => {\n const view = unwrap(await remote.settings.mutate(ns, ops, expectedRevision)) as SettingsDescriptor | undefined\n if (view === undefined) throw new Error('settings.mutate returned no namespace view')\n return view\n },\n })\n\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'model-capability-sync',\n order: 16,\n label: () => t('nav'),\n locale: NS,\n inject: injected,\n }, SyncSection))\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["candidates", "descriptor", "document"]
7
+ }
package/lib/index.js ADDED
@@ -0,0 +1,332 @@
1
+ // src/catalog.ts
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ var CATALOG_URL = "https://models.dev/api.json";
6
+ var CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
7
+ var DOWNLOAD_ATTEMPTS = 3;
8
+ var RETRY_DELAY_MS = 600;
9
+ var cache;
10
+ var CACHE_FILE = join(homedir(), ".dsh", "model-capability-sync", "catalog.json");
11
+ async function writeDiskCache(body) {
12
+ try {
13
+ const envelope = { fetchedAt: Date.now(), source: CATALOG_URL, body };
14
+ await mkdir(dirname(CACHE_FILE), { recursive: true });
15
+ await writeFile(CACHE_FILE, JSON.stringify(envelope), "utf8");
16
+ } catch {
17
+ }
18
+ }
19
+ async function readDiskCache() {
20
+ try {
21
+ const parsed = JSON.parse(await readFile(CACHE_FILE, "utf8"));
22
+ if (typeof parsed.fetchedAt !== "number" || parsed.body === void 0) return void 0;
23
+ return { fetchedAt: parsed.fetchedAt, source: typeof parsed.source === "string" ? parsed.source : CATALOG_URL, body: parsed.body };
24
+ } catch {
25
+ return void 0;
26
+ }
27
+ }
28
+ function pause(ms) {
29
+ return new Promise((resolve) => {
30
+ setTimeout(resolve, ms);
31
+ });
32
+ }
33
+ function push(index, key, entry) {
34
+ const bucket = index.get(key);
35
+ if (bucket === void 0) index.set(key, [entry]);
36
+ else bucket.push(entry);
37
+ }
38
+ function buildCatalog(body, fetchedAt) {
39
+ const byId = /* @__PURE__ */ new Map();
40
+ const byFolded = /* @__PURE__ */ new Map();
41
+ let modelCount = 0;
42
+ if (typeof body === "object" && body !== null) {
43
+ for (const [provider, value] of Object.entries(body)) {
44
+ const models = value?.models;
45
+ if (typeof models !== "object" || models === null) continue;
46
+ for (const [id, model] of Object.entries(models)) {
47
+ if (id.length === 0) continue;
48
+ modelCount += 1;
49
+ const entry = { provider, id, model };
50
+ push(byId, id, entry);
51
+ push(byFolded, id.toLowerCase(), entry);
52
+ }
53
+ }
54
+ }
55
+ if (modelCount === 0) throw new Error("models.dev returned a catalog with no models");
56
+ return { fetchedAt, modelCount, byId, byFolded };
57
+ }
58
+ async function download(proxyUrl, signal) {
59
+ if (proxyUrl === void 0 || proxyUrl.length === 0) {
60
+ const response2 = await fetch(CATALOG_URL, { signal });
61
+ if (!response2.ok) throw new Error(`models.dev download failed (HTTP ${String(response2.status)})`);
62
+ return await response2.json();
63
+ }
64
+ const specifier = "undici";
65
+ let undici;
66
+ try {
67
+ undici = await import(specifier);
68
+ } catch {
69
+ throw new Error('a proxy URL was set but the host cannot load "undici"; clear the proxy or install it');
70
+ }
71
+ const dispatcher = new undici.ProxyAgent(proxyUrl);
72
+ const response = await undici.fetch(CATALOG_URL, { signal, dispatcher });
73
+ if (!response.ok) throw new Error(`models.dev download through the proxy failed (HTTP ${String(response.status)})`);
74
+ return await response.json();
75
+ }
76
+ async function loadCatalog(options = {}, signal) {
77
+ const now = Date.now();
78
+ const fresh = cache !== void 0 && cache.proxyUrl === options.proxyUrl && now - cache.catalog.fetchedAt < CACHE_TTL_MS;
79
+ if (options.refresh !== true && fresh && cache !== void 0) return cache.catalog;
80
+ let lastError;
81
+ for (let attempt = 1; attempt <= DOWNLOAD_ATTEMPTS; attempt += 1) {
82
+ try {
83
+ const body = await download(options.proxyUrl, signal);
84
+ const catalog = buildCatalog(body, Date.now());
85
+ cache = { catalog, proxyUrl: options.proxyUrl };
86
+ void writeDiskCache(body);
87
+ return catalog;
88
+ } catch (error) {
89
+ if (signal?.aborted === true) throw error;
90
+ lastError = error;
91
+ if (attempt < DOWNLOAD_ATTEMPTS) await pause(RETRY_DELAY_MS);
92
+ }
93
+ }
94
+ const disk = await readDiskCache();
95
+ if (disk !== void 0) {
96
+ try {
97
+ const catalog = { ...buildCatalog(disk.body, disk.fetchedAt), stale: true };
98
+ cache = { catalog, proxyUrl: options.proxyUrl };
99
+ return catalog;
100
+ } catch {
101
+ }
102
+ }
103
+ const reason = lastError instanceof Error ? lastError.message : String(lastError);
104
+ throw new Error(`models.dev catalog unavailable (${reason}) and no local copy exists`);
105
+ }
106
+
107
+ // src/mapping.ts
108
+ var THINKING_LEVELS = /* @__PURE__ */ new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
109
+ function positive(value) {
110
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.round(value) : void 0;
111
+ }
112
+ function modalitiesOf(entry) {
113
+ const declared = entry.model.modalities?.input;
114
+ if (declared === void 0) return void 0;
115
+ const accepted = [];
116
+ for (const media of declared) {
117
+ if (media === "image" && !accepted.includes("image")) accepted.push("image");
118
+ }
119
+ return ["text", ...accepted];
120
+ }
121
+ function reasoningOf(entry) {
122
+ const model = entry.model;
123
+ if (model.reasoning === false) return false;
124
+ const option = model.reasoning_options?.find((candidate) => candidate.type === "effort");
125
+ const values = option?.values ?? [];
126
+ const efforts = {};
127
+ for (const value of values) {
128
+ if (typeof value !== "string") continue;
129
+ if (!THINKING_LEVELS.has(value)) continue;
130
+ efforts[value] = value;
131
+ }
132
+ return Object.keys(efforts).length > 0 ? efforts : void 0;
133
+ }
134
+ function capabilitiesOf(entry) {
135
+ const model = entry.model;
136
+ const input = modalitiesOf(entry);
137
+ const reasoningEfforts = reasoningOf(entry);
138
+ return {
139
+ ...positive(model.limit?.context) === void 0 ? {} : { contextWindow: positive(model.limit?.context) },
140
+ ...positive(model.limit?.output) === void 0 ? {} : { maxTokens: positive(model.limit?.output) },
141
+ ...input === void 0 ? {} : { input },
142
+ ...reasoningEfforts === void 0 ? {} : { reasoningEfforts }
143
+ };
144
+ }
145
+
146
+ // src/match.ts
147
+ var VARIANT_SUFFIX = /-(high|low|medium|minimal|max|xhigh|thinking|preview|latest|chat)$/;
148
+ var FIRST_PARTY = /* @__PURE__ */ new Set([
149
+ "openai",
150
+ "anthropic",
151
+ "google",
152
+ "deepseek",
153
+ "xai",
154
+ "meta",
155
+ "mistral",
156
+ "cohere",
157
+ "moonshotai",
158
+ "moonshotai-cn",
159
+ "zai",
160
+ "zhipuai",
161
+ "alibaba",
162
+ "alibaba-cn",
163
+ "qwen",
164
+ "sensenova",
165
+ "minimax",
166
+ "stepfun",
167
+ "bytedance",
168
+ "baidu",
169
+ "nvidia",
170
+ "microsoft",
171
+ "amazon",
172
+ "ai21",
173
+ "perplexity",
174
+ "upstage",
175
+ "01-ai",
176
+ "inclusionai",
177
+ "tencent",
178
+ "kuaishou"
179
+ ]);
180
+ function score(entry) {
181
+ const model = entry.model;
182
+ let value = 0;
183
+ if (FIRST_PARTY.has(entry.provider)) value += 5;
184
+ if (model.limit?.context !== void 0 || model.limit?.output !== void 0) value += 2;
185
+ if (model.modalities?.input !== void 0) value += 2;
186
+ if (model.reasoning_options !== void 0) value += 1;
187
+ if (model.name !== void 0) value += 1;
188
+ return value;
189
+ }
190
+ function pickBest(entries) {
191
+ let best;
192
+ let bestScore = -1;
193
+ for (const entry of entries) {
194
+ const value = score(entry);
195
+ if (value > bestScore || value === bestScore && best !== void 0 && entry.provider < best.provider) {
196
+ best = entry;
197
+ bestScore = value;
198
+ }
199
+ }
200
+ return best;
201
+ }
202
+ function basename(id) {
203
+ const slash = id.lastIndexOf("/");
204
+ return slash === -1 ? id : id.slice(slash + 1);
205
+ }
206
+ function lookup(catalog, key) {
207
+ const exact = catalog.byId.get(key);
208
+ if (exact !== void 0) return { entries: exact, via: "exact" };
209
+ const folded = catalog.byFolded.get(key.toLowerCase());
210
+ if (folded !== void 0) return { entries: folded, via: "folded" };
211
+ return void 0;
212
+ }
213
+ function describe(entry, via) {
214
+ return {
215
+ provider: entry.provider,
216
+ modelId: entry.id,
217
+ via,
218
+ firstParty: FIRST_PARTY.has(entry.provider),
219
+ ...entry.model.name === void 0 ? {} : { name: entry.model.name }
220
+ };
221
+ }
222
+ function matchModel(catalog, id, aliases = {}) {
223
+ const alias = aliases[id];
224
+ if (alias !== void 0 && alias.length > 0) {
225
+ const hit = lookup(catalog, alias);
226
+ const entry = hit === void 0 ? void 0 : pickBest(hit.entries);
227
+ if (entry !== void 0) return describe(entry, "alias");
228
+ }
229
+ const exact = lookup(catalog, id);
230
+ if (exact !== void 0) {
231
+ const entry = pickBest(exact.entries);
232
+ if (entry !== void 0) return describe(entry, exact.via);
233
+ }
234
+ const base = basename(id);
235
+ if (base !== id) {
236
+ const hit = lookup(catalog, base);
237
+ if (hit !== void 0) {
238
+ const entry = pickBest(hit.entries);
239
+ if (entry !== void 0) return describe(entry, "basename");
240
+ }
241
+ }
242
+ const stripped = base.replace(VARIANT_SUFFIX, "");
243
+ if (stripped !== base && stripped.length > 0) {
244
+ const hit = lookup(catalog, stripped);
245
+ if (hit !== void 0) {
246
+ const entry = pickBest(hit.entries);
247
+ if (entry !== void 0) return describe(entry, "suffix");
248
+ }
249
+ }
250
+ return void 0;
251
+ }
252
+
253
+ // src/index.ts
254
+ var name = "model-capability-sync";
255
+ var inject = ["connection", "webServer", "llm"];
256
+ var CHANNEL = "/model-capability-sync";
257
+ function previewOne(catalog, id, aliases) {
258
+ const match = matchModel(catalog, id, aliases);
259
+ if (match === void 0) return { id, reason: "not in the models.dev catalog" };
260
+ const entry = catalog.byId.get(match.modelId)?.find((candidate) => candidate.provider === match.provider);
261
+ if (entry === void 0) return { id, match, reason: "catalog entry vanished between match and read" };
262
+ const fields = capabilitiesOf(entry);
263
+ if (Object.keys(fields).length === 0) return { id, match, reason: "catalog entry states no capability facts" };
264
+ return { id, match, fields };
265
+ }
266
+ async function preview(request, signal) {
267
+ const ids = Array.isArray(request.ids) ? request.ids.filter((id) => typeof id === "string" && id.length > 0) : [];
268
+ const catalog = await loadCatalog({ refresh: request.refresh === true, proxyUrl: request.proxyUrl }, signal);
269
+ const aliases = request.aliases ?? {};
270
+ return {
271
+ fetchedAt: catalog.fetchedAt,
272
+ catalogModels: catalog.modelCount,
273
+ ...catalog.stale === true ? { stale: true } : {},
274
+ rows: ids.map((id) => previewOne(catalog, id, aliases))
275
+ };
276
+ }
277
+ function failure(message) {
278
+ return { ok: false, error: { code: "internal", message, details: {} } };
279
+ }
280
+ async function live(llm, request, signal) {
281
+ const provider = typeof request.provider === "string" ? request.provider : "";
282
+ const ids = Array.isArray(request.ids) ? request.ids.filter((id) => typeof id === "string" && id.length > 0) : [];
283
+ const rows = [];
284
+ for (const id of ids) {
285
+ try {
286
+ const info = await llm.resolveModelInfo(provider, id, signal);
287
+ rows.push({
288
+ id,
289
+ ...info.inputModalities === void 0 ? {} : { input: [...info.inputModalities] },
290
+ ...info.context === void 0 ? {} : { contextWindow: info.context.contextWindow },
291
+ ...info.defaultMaxTokens === void 0 ? {} : { maxTokens: info.defaultMaxTokens }
292
+ });
293
+ } catch (error) {
294
+ rows.push({ id, error: error instanceof Error ? error.message : String(error) });
295
+ }
296
+ }
297
+ return { rows };
298
+ }
299
+ function createHandler(llm) {
300
+ return async (endpoint, payload, signal) => {
301
+ try {
302
+ if (endpoint === "preview") {
303
+ return { ok: true, value: await preview(payload, signal) };
304
+ }
305
+ if (endpoint === "live") {
306
+ if (llm === void 0) return failure("model-capability-sync: no llm service to resolve the live route");
307
+ return { ok: true, value: await live(llm, payload, signal) };
308
+ }
309
+ return failure(`model-capability-sync: unknown endpoint ${JSON.stringify(endpoint)}`);
310
+ } catch (error) {
311
+ return failure(error instanceof Error ? error.message : String(error));
312
+ }
313
+ };
314
+ }
315
+ function apply(ctx) {
316
+ const connection = ctx.get("connection");
317
+ const llm = ctx.get("llm");
318
+ if (connection === void 0) {
319
+ ctx.logger.warn("model-capability-sync: no connection service; the capability channel is unavailable");
320
+ return;
321
+ }
322
+ if (llm === void 0) ctx.logger.warn("model-capability-sync: no llm service; the live route check is unavailable");
323
+ const registrar = connection;
324
+ const handler = createHandler(llm);
325
+ ctx.effect(() => registrar.register(ctx, CHANNEL, handler), "model-capability-sync: RPC channel");
326
+ }
327
+ export {
328
+ apply,
329
+ inject,
330
+ name
331
+ };
332
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/catalog.ts", "../src/mapping.ts", "../src/match.ts", "../src/index.ts"],
4
+ "sourcesContent": ["/**\n * The models.dev catalog: download, cache, and index for lookup.\n *\n * One shared `api.json` carries every provider's model facts. The download\n * happens host-side so the browser never makes a cross-origin request, and a\n * forward proxy stays a plain HTTP proxy the host can use.\n *\n * A copy also lands on disk. The host process may sit behind a network the\n * command line does not share \u2014 a proxy, a firewall, a flaky route \u2014 and a\n * settings page that cannot open because one download failed is worse than a\n * page reading a slightly old catalog. The disk copy is the fallback; its age\n * is reported so the answer is never silently stale.\n * @module dsh-model-capability-sync/catalog\n */\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nconst CATALOG_URL = 'https://models.dev/api.json'\n/** Reuse one download for this long; a `refresh` request bypasses it. */\nconst CACHE_TTL_MS = 6 * 60 * 60 * 1000\n/** Attempts per download before falling back to the disk copy. */\nconst DOWNLOAD_ATTEMPTS = 3\n/** Pause between attempts: long enough to outlast a reset connection. */\nconst RETRY_DELAY_MS = 600\n\n/** One model entry as models.dev publishes it (only the fields we read). */\nexport interface ModelsDevModel {\n id?: string\n name?: string\n description?: string\n /** Whether the model accepts reasoning control at all. */\n reasoning?: boolean\n /** Accepted input/output media; `input` is the modality list we map. */\n modalities?: { input?: string[]; output?: string[] }\n /** Capacity facts: token counts. */\n limit?: { context?: number; input?: number; output?: number }\n /** Reasoning control shapes; the `effort` type carries the selectable levels. */\n reasoning_options?: Array<{ type?: string; values?: Array<string | null> }>\n}\n\n/** One catalog entry with its provider id attached. */\nexport interface CatalogEntry {\n provider: string\n id: string\n model: ModelsDevModel\n}\n\n/** A loaded catalog plus its lookup indexes. */\nexport interface Catalog {\n fetchedAt: number\n modelCount: number\n /** True when this copy came from disk because the download failed. */\n stale?: boolean\n /** Exact id \u2192 entries (a bare id can appear under several providers). */\n byId: Map<string, CatalogEntry[]>\n /** Lower-cased id \u2192 entries, for case-insensitive vendor spellings. */\n byFolded: Map<string, CatalogEntry[]>\n}\n\nlet cache: { catalog: Catalog; proxyUrl: string | undefined } | undefined\n\n/** Where the on-disk copy lives, mirroring the other plugins' data roots. */\nconst CACHE_FILE = join(homedir(), '.dsh', 'model-capability-sync', 'catalog.json')\n\n/** The on-disk envelope: the raw catalog body plus when it was fetched. */\ninterface DiskCache {\n fetchedAt: number\n source: string\n body: unknown\n}\n\n/** Persist one successful download; a cache write must never fail the request. */\nasync function writeDiskCache(body: unknown): Promise<void> {\n try {\n const envelope: DiskCache = { fetchedAt: Date.now(), source: CATALOG_URL, body }\n await mkdir(dirname(CACHE_FILE), { recursive: true })\n await writeFile(CACHE_FILE, JSON.stringify(envelope), 'utf8')\n } catch {\n // A read-only or full home directory only costs the fallback, not the answer.\n }\n}\n\n/** Read the on-disk copy, or undefined when there is none to trust. */\nasync function readDiskCache(): Promise<DiskCache | undefined> {\n try {\n const parsed = JSON.parse(await readFile(CACHE_FILE, 'utf8')) as Partial<DiskCache>\n if (typeof parsed.fetchedAt !== 'number' || parsed.body === undefined) return undefined\n return { fetchedAt: parsed.fetchedAt, source: typeof parsed.source === 'string' ? parsed.source : CATALOG_URL, body: parsed.body }\n } catch {\n return undefined\n }\n}\n\n/** Wait between download attempts. */\nfunction pause(ms: number): Promise<void> {\n return new Promise((resolve) => { setTimeout(resolve, ms) })\n}\n\n/** Push one entry into an index bucket. */\nfunction push(index: Map<string, CatalogEntry[]>, key: string, entry: CatalogEntry): void {\n const bucket = index.get(key)\n if (bucket === undefined) index.set(key, [entry])\n else bucket.push(entry)\n}\n\n/** Build both indexes from the raw catalog body. */\nfunction buildCatalog(body: unknown, fetchedAt: number): Catalog {\n const byId = new Map<string, CatalogEntry[]>()\n const byFolded = new Map<string, CatalogEntry[]>()\n let modelCount = 0\n if (typeof body === 'object' && body !== null) {\n for (const [provider, value] of Object.entries(body as Record<string, unknown>)) {\n const models = (value as { models?: Record<string, ModelsDevModel> } | undefined)?.models\n if (typeof models !== 'object' || models === null) continue\n for (const [id, model] of Object.entries(models)) {\n if (id.length === 0) continue\n modelCount += 1\n const entry: CatalogEntry = { provider, id, model }\n push(byId, id, entry)\n push(byFolded, id.toLowerCase(), entry)\n }\n }\n }\n if (modelCount === 0) throw new Error('models.dev returned a catalog with no models')\n return { fetchedAt, modelCount, byId, byFolded }\n}\n\n/** Download the catalog body, optionally through a forward proxy. */\nasync function download(proxyUrl: string | undefined, signal: AbortSignal | undefined): Promise<unknown> {\n if (proxyUrl === undefined || proxyUrl.length === 0) {\n const response = await fetch(CATALOG_URL, { signal })\n if (!response.ok) throw new Error(`models.dev download failed (HTTP ${String(response.status)})`)\n return await response.json()\n }\n // The proxy path needs undici's dispatcher. It ships with the harness\n // runtime; a missing copy is reported instead of silently going direct. The\n // specifier stays a runtime value so this package needs no undici types.\n interface UndiciFace {\n fetch(input: string, init?: Record<string, unknown>): Promise<Response>\n ProxyAgent: new (url: string) => unknown\n }\n const specifier: string = 'undici'\n let undici: UndiciFace\n try {\n undici = await import(specifier) as UndiciFace\n } catch {\n throw new Error('a proxy URL was set but the host cannot load \"undici\"; clear the proxy or install it')\n }\n const dispatcher = new undici.ProxyAgent(proxyUrl)\n const response = await undici.fetch(CATALOG_URL, { signal, dispatcher })\n if (!response.ok) throw new Error(`models.dev download through the proxy failed (HTTP ${String(response.status)})`)\n return await response.json()\n}\n\n/**\n * Load the catalog, reusing the cached copy unless it expired or the caller\n * asked for a refresh.\n *\n * A failed download is retried, then answered from the on-disk copy with\n * `stale` set. Only when both paths fail does this throw, and the message\n * names the network reason so the page can show something actionable.\n * @param options - refresh flag and optional proxy URL.\n * @param signal - caller cancellation.\n * @returns the indexed catalog.\n */\nexport async function loadCatalog(\n options: { refresh?: boolean; proxyUrl?: string } = {},\n signal?: AbortSignal,\n): Promise<Catalog> {\n const now = Date.now()\n const fresh = cache !== undefined\n && cache.proxyUrl === options.proxyUrl\n && now - cache.catalog.fetchedAt < CACHE_TTL_MS\n if (options.refresh !== true && fresh && cache !== undefined) return cache.catalog\n\n let lastError: unknown\n for (let attempt = 1; attempt <= DOWNLOAD_ATTEMPTS; attempt += 1) {\n try {\n const body = await download(options.proxyUrl, signal)\n const catalog = buildCatalog(body, Date.now())\n cache = { catalog, proxyUrl: options.proxyUrl }\n void writeDiskCache(body)\n return catalog\n } catch (error) {\n if (signal?.aborted === true) throw error\n lastError = error\n if (attempt < DOWNLOAD_ATTEMPTS) await pause(RETRY_DELAY_MS)\n }\n }\n\n const disk = await readDiskCache()\n if (disk !== undefined) {\n try {\n const catalog: Catalog = { ...buildCatalog(disk.body, disk.fetchedAt), stale: true }\n cache = { catalog, proxyUrl: options.proxyUrl }\n return catalog\n } catch {\n // A corrupt copy is no better than no copy; report the download failure.\n }\n }\n const reason = lastError instanceof Error ? lastError.message : String(lastError)\n throw new Error(`models.dev catalog unavailable (${reason}) and no local copy exists`)\n}\n", "/**\n * Turning one catalog entry into the capability fields a provider model entry\n * accepts.\n *\n * Everything here is a pure function of catalog data: no gateway call, no\n * guessing beyond the catalog's own facts. A field the catalog does not state\n * stays absent, so the writer never invents a value.\n * @module dsh-model-capability-sync/mapping\n */\nimport type { CatalogEntry } from './catalog.ts'\nimport type { CapabilityFields, Modality } from './types.ts'\n\n/** Thinking levels the harness recognizes, strongest last. */\nconst THINKING_LEVELS = new Set(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'])\n\n/** A positive integer or nothing. */\nfunction positive(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.round(value) : undefined\n}\n\n/** Map the catalog's media list onto the harness's two modalities. */\nfunction modalitiesOf(entry: CatalogEntry): Modality[] | undefined {\n const declared = entry.model.modalities?.input\n if (declared === undefined) return undefined\n const accepted: Modality[] = []\n for (const media of declared) {\n if (media === 'image' && !accepted.includes('image')) accepted.push('image')\n }\n // A model that lists only images still reads text in practice; the harness\n // always sends text, so `text` is never dropped.\n return ['text', ...accepted]\n}\n\n/**\n * Build the reasoning-effort map from the catalog's `effort` option.\n * @returns the dict, `false` when the catalog says the model cannot reason, or\n * `undefined` when the catalog is silent.\n */\nfunction reasoningOf(entry: CatalogEntry): Record<string, string> | false | undefined {\n const model = entry.model\n if (model.reasoning === false) return false\n const option = model.reasoning_options?.find(candidate => candidate.type === 'effort')\n const values = option?.values ?? []\n const efforts: Record<string, string> = {}\n for (const value of values) {\n if (typeof value !== 'string') continue\n if (!THINKING_LEVELS.has(value)) continue\n efforts[value] = value\n }\n return Object.keys(efforts).length > 0 ? efforts : undefined\n}\n\n/**\n * Read every capability the catalog states for one entry.\n *\n * The catalog's display name is deliberately not returned: this plugin fills\n * facts, it never renames a model entry.\n * @param entry - the matched catalog entry.\n * @returns only the fields the catalog actually declares.\n */\nexport function capabilitiesOf(entry: CatalogEntry): CapabilityFields {\n const model = entry.model\n const input = modalitiesOf(entry)\n const reasoningEfforts = reasoningOf(entry)\n return {\n ...positive(model.limit?.context) === undefined ? {} : { contextWindow: positive(model.limit?.context) as number },\n ...positive(model.limit?.output) === undefined ? {} : { maxTokens: positive(model.limit?.output) as number },\n ...input === undefined ? {} : { input },\n ...reasoningEfforts === undefined ? {} : { reasoningEfforts },\n }\n}\n", "/**\n * Matching one configured gateway model id against the models.dev catalog.\n *\n * A gateway aggregates arbitrary upstreams and invents its own ids, so the\n * match walks from strict to loose and reports which rule fired \u2014 the UI shows\n * it, and an explicit alias always wins.\n * @module dsh-model-capability-sync/match\n */\nimport type { Catalog, CatalogEntry } from './catalog.ts'\nimport type { MatchInfo, MatchVia } from './types.ts'\n\n/**\n * Variant suffixes a gateway appends to one upstream model. Stripping them\n * turns `gemini-3.8-flash-high` back into the catalog's `gemini-3.8-flash`.\n */\nconst VARIANT_SUFFIX = /-(high|low|medium|minimal|max|xhigh|thinking|preview|latest|chat)$/\n\n/**\n * First-party vendor provider ids in the models.dev catalog. The same model\n * appears under dozens of aggregators whose capacity numbers disagree, so a\n * vendor entry outranks every reseller when both exist.\n */\nconst FIRST_PARTY = new Set([\n 'openai', 'anthropic', 'google', 'deepseek', 'xai', 'meta', 'mistral', 'cohere',\n 'moonshotai', 'moonshotai-cn', 'zai', 'zhipuai', 'alibaba', 'alibaba-cn', 'qwen',\n 'sensenova', 'minimax', 'stepfun', 'bytedance', 'baidu', 'nvidia', 'microsoft',\n 'amazon', 'ai21', 'perplexity', 'upstage', '01-ai', 'inclusionai', 'tencent', 'kuaishou',\n])\n\n/** How complete an entry's facts are; the most complete wins a tie. */\nfunction score(entry: CatalogEntry): number {\n const model = entry.model\n let value = 0\n if (FIRST_PARTY.has(entry.provider)) value += 5\n if (model.limit?.context !== undefined || model.limit?.output !== undefined) value += 2\n if (model.modalities?.input !== undefined) value += 2\n if (model.reasoning_options !== undefined) value += 1\n if (model.name !== undefined) value += 1\n return value\n}\n\n/**\n * Pick the best of several provider entries for the same id: the one carrying\n * the most facts, then the alphabetically first provider so repeated runs are\n * stable.\n */\nfunction pickBest(entries: readonly CatalogEntry[]): CatalogEntry | undefined {\n let best: CatalogEntry | undefined\n let bestScore = -1\n for (const entry of entries) {\n const value = score(entry)\n if (value > bestScore || value === bestScore && best !== undefined && entry.provider < best.provider) {\n best = entry\n bestScore = value\n }\n }\n return best\n}\n\n/** The id without its vendor path prefix (`qoder-gj/DeepSeek-Flash` \u2192 `DeepSeek-Flash`). */\nfunction basename(id: string): string {\n const slash = id.lastIndexOf('/')\n return slash === -1 ? id : id.slice(slash + 1)\n}\n\n/** Look one key up exactly, then case-insensitively. */\nfunction lookup(catalog: Catalog, key: string): { entries: readonly CatalogEntry[]; via: MatchVia } | undefined {\n const exact = catalog.byId.get(key)\n if (exact !== undefined) return { entries: exact, via: 'exact' }\n const folded = catalog.byFolded.get(key.toLowerCase())\n if (folded !== undefined) return { entries: folded, via: 'folded' }\n return undefined\n}\n\n/** Build the public match record for one chosen entry. */\nfunction describe(entry: CatalogEntry, via: MatchVia): MatchInfo {\n return {\n provider: entry.provider,\n modelId: entry.id,\n via,\n firstParty: FIRST_PARTY.has(entry.provider),\n ...entry.model.name === undefined ? {} : { name: entry.model.name },\n }\n}\n\n/**\n * Resolve one gateway model id.\n * @param catalog - the indexed catalog.\n * @param id - the gateway model id exactly as configured.\n * @param aliases - explicit gateway id \u2192 catalog id overrides.\n * @returns the match, or `undefined` when nothing fits.\n */\nexport function matchModel(\n catalog: Catalog,\n id: string,\n aliases: Readonly<Record<string, string>> = {},\n): MatchInfo | undefined {\n const alias = aliases[id]\n if (alias !== undefined && alias.length > 0) {\n const hit = lookup(catalog, alias)\n const entry = hit === undefined ? undefined : pickBest(hit.entries)\n if (entry !== undefined) return describe(entry, 'alias')\n }\n\n const exact = lookup(catalog, id)\n if (exact !== undefined) {\n const entry = pickBest(exact.entries)\n if (entry !== undefined) return describe(entry, exact.via)\n }\n\n const base = basename(id)\n if (base !== id) {\n const hit = lookup(catalog, base)\n if (hit !== undefined) {\n const entry = pickBest(hit.entries)\n if (entry !== undefined) return describe(entry, 'basename')\n }\n }\n\n const stripped = base.replace(VARIANT_SUFFIX, '')\n if (stripped !== base && stripped.length > 0) {\n const hit = lookup(catalog, stripped)\n if (hit !== undefined) {\n const entry = pickBest(hit.entries)\n if (entry !== undefined) return describe(entry, 'suffix')\n }\n }\n\n return undefined\n}\n", "/**\n * Host half: one RPC channel that answers \"what does the catalog say about\n * these model ids?\".\n *\n * The plugin registers no LLM provider route on purpose \u2014 it only supplies\n * facts. The browser half decides which provider entry to write and performs\n * the write through the official settings service, so this half never touches\n * configuration.\n * @module dsh-model-capability-sync\n */\nimport { loadCatalog } from './catalog.ts'\nimport type { Catalog } from './catalog.ts'\nimport { capabilitiesOf } from './mapping.ts'\nimport { matchModel } from './match.ts'\nimport type { LiveRequest, LiveResponse, LiveRow, PreviewRequest, PreviewResponse, PreviewRow, RpcEnvelope } from './types.ts'\n\n/** Cordis plugin name; the profile entry id matches. */\nexport const name = 'model-capability-sync'\n\n/** The channel is served over the web server, so both services are required. */\nexport const inject = ['connection', 'webServer', 'llm']\n\n/** Own RPC channel; the browser half calls this exact path. */\nconst CHANNEL = '/model-capability-sync'\n\n/** The slice of the host context this plugin uses. */\ninterface HostContext {\n get(service: string): unknown\n effect(callback: () => (() => void | Promise<void>) | void, label?: string): void\n logger: { warn(message: string): void; error(message: unknown): void }\n}\n\ntype Handler = (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<RpcEnvelope<unknown>>\n\n/** The connection service's owner-taking registration (see DESIGN in llm-newapi). */\ninterface Registrar {\n register(owner: unknown, channel: string, handler: Handler): () => Promise<void>\n}\n\n/** Resolve one id against the catalog into a preview row. */\nfunction previewOne(catalog: Catalog, id: string, aliases: Readonly<Record<string, string>>): PreviewRow {\n const match = matchModel(catalog, id, aliases)\n if (match === undefined) return { id, reason: 'not in the models.dev catalog' }\n const entry = catalog.byId.get(match.modelId)?.find(candidate => candidate.provider === match.provider)\n if (entry === undefined) return { id, match, reason: 'catalog entry vanished between match and read' }\n const fields = capabilitiesOf(entry)\n if (Object.keys(fields).length === 0) return { id, match, reason: 'catalog entry states no capability facts' }\n return { id, match, fields }\n}\n\n/** Serve one `preview` request. */\nasync function preview(request: PreviewRequest, signal: AbortSignal): Promise<PreviewResponse> {\n const ids = Array.isArray(request.ids) ? request.ids.filter(id => typeof id === 'string' && id.length > 0) : []\n const catalog = await loadCatalog({ refresh: request.refresh === true, proxyUrl: request.proxyUrl }, signal)\n const aliases = request.aliases ?? {}\n return {\n fetchedAt: catalog.fetchedAt,\n catalogModels: catalog.modelCount,\n ...catalog.stale === true ? { stale: true } : {},\n rows: ids.map(id => previewOne(catalog, id, aliases)),\n }\n}\n\n/** One RPC envelope, never a thrown value: the transport would turn that into an opaque 500. */\nfunction failure(message: string): RpcEnvelope<never> {\n return { ok: false, error: { code: 'internal', message, details: {} } }\n}\n\n/** The LLM registry face this half reads live model metadata through. */\ninterface LlmFace {\n resolveModelInfo(provider: string, model: string, signal?: AbortSignal): Promise<{\n inputModalities?: readonly string[]\n context?: { contextWindow: number }\n defaultMaxTokens?: number\n }>\n}\n\n/**\n * Read what the adapter that owns `provider` reports right now.\n *\n * A configuration entry and the route a chat request takes can disagree: two\n * plugins may claim one provider route, and only the first registration serves\n * it. This answer is what a request would see, so a filled-in `input` that\n * never takes effect becomes visible instead of inferred.\n * @param llm - the LLM registry.\n * @param request - route and model ids to resolve.\n * @param signal - cancellation for adapter-owned lookups.\n * @returns one row per requested id, in request order.\n */\nasync function live(llm: LlmFace, request: LiveRequest, signal: AbortSignal): Promise<LiveResponse> {\n const provider = typeof request.provider === 'string' ? request.provider : ''\n const ids = Array.isArray(request.ids) ? request.ids.filter(id => typeof id === 'string' && id.length > 0) : []\n const rows: LiveRow[] = []\n for (const id of ids) {\n try {\n const info = await llm.resolveModelInfo(provider, id, signal)\n rows.push({\n id,\n ...info.inputModalities === undefined ? {} : { input: [...info.inputModalities] },\n ...info.context === undefined ? {} : { contextWindow: info.context.contextWindow },\n ...info.defaultMaxTokens === undefined ? {} : { maxTokens: info.defaultMaxTokens },\n })\n } catch (error) {\n rows.push({ id, error: error instanceof Error ? error.message : String(error) })\n }\n }\n return { rows }\n}\n\n/**\n * Build the channel handler bound to one LLM registry.\n * @param llm - the LLM registry the `live` endpoint reads through, when present.\n * @returns the endpoint dispatcher.\n */\nfunction createHandler(llm: LlmFace | undefined): Handler {\n return async (endpoint, payload, signal) => {\n try {\n if (endpoint === 'preview') {\n return { ok: true, value: await preview(payload as PreviewRequest, signal) }\n }\n if (endpoint === 'live') {\n if (llm === undefined) return failure('model-capability-sync: no llm service to resolve the live route')\n return { ok: true, value: await live(llm, payload as LiveRequest, signal) }\n }\n return failure(`model-capability-sync: unknown endpoint ${JSON.stringify(endpoint)}`)\n } catch (error) {\n return failure(error instanceof Error ? error.message : String(error))\n }\n }\n}\n\n/**\n * Register the channel. The registration rides the calling fiber, so a plugin\n * reload removes the route with it.\n * @param ctx - host context carrying `connection`, `webServer` and `llm`.\n */\nexport function apply(ctx: HostContext): void {\n const connection = ctx.get('connection')\n const llm = ctx.get('llm') as LlmFace | undefined\n if (connection === undefined) {\n ctx.logger.warn('model-capability-sync: no connection service; the capability channel is unavailable')\n return\n }\n if (llm === undefined) ctx.logger.warn('model-capability-sync: no llm service; the live route check is unavailable')\n const registrar = connection as Registrar\n const handler = createHandler(llm)\n ctx.effect(() => registrar.register(ctx, CHANNEL, handler), 'model-capability-sync: RPC channel')\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["response"]
7
+ }