KeepLost/harniverse--packages-client-ui-settings-models ↗★ 1

@deepseek-ai/dsh-client-ui-settings-models

Models settings and the product notice over existing settings and credential joins 适合需要配置模型提供商凭据的用户;在模型页显式配置,无提供商专属首次运行步骤。

パッケージ
@deepseek-ai/dsh-client-ui-settings-models
互換性
未検証
Harness ピア範囲
workspace:^
Cordis ピア範囲
workspace:^
バージョン
0.1.0-rc.5
ライセンス
BSD-3-Clause
最終更新
2026/09/25

同名パッケージの別リポジトリ

インストール

検証済み bundle がないか、互換性チェックに失敗しています。先にリポジトリの説明を読んでください。 README 全文を読む ↗

ドキュメント

README 全文を読む ↗

@deepseek-ai/dsh-client-ui-settings-models

English | 中文

Models settings and product-onboarding plugin. The same client Cordis plugin registers the Models page plus a versioned internal-testing notice; provider credentials are configured explicitly on the Models page, without a provider-specific first-run step. The Models plane joins three wire domains into one shared snapshot — llm.providers (the configurable-provider directory with each route's live/dormant state), settings.describe (serialized schemas, layered redacted values, secret slots), and credentials.describe (value-free configured/source/writable badges) — and renders provider rows with one editor card at a time, without presenting route liveness as provider status.

The join and every mutation share the Settings mirror's authenticated-principal generation fence. The connection carrier centrally requires every unary settlement to match its initiating identity before returning it, so ProviderEditor and model discovery do not perform opt-in response-authentication checks. A principal transition synchronously clears rows, namespace views, and credential badges, and generation fences keep older settlements from publishing local state. The carrier additionally sends expected-principal preconditions for mutations and secret-bearing model discovery so the Host rejects a changed principal before route dispatch or provider network invocation.

The Models store also advances a principal generation at that boundary. The slot component keys its complete local-state subtree by this generation, so API-key, endpoint, and model drafts, editor/add/declaration state, setup dismissals, delete targets and failures, pending flags, and saved notices are synchronously unmounted before the next principal can render.

Adapter topology commits produce both the direct llm/adapters-updated owner event and the Host's paired settings/exposure-changed descriptor event. The Models plugin coalesces that pair into one microtask refresh, so it keeps both ownership signals without issuing duplicate directory and Settings reads.

Rows are the configured providers (their profile resolves in the owning namespace); a whole-section provider whose key is not configured anywhere renders as its open setup card instead of a row, but only in the first-run posture — while no provider is registered with the credential its profile names — and only until the user closes that card, after which it is an ordinary row carrying the missing-key dot. Each card kind owns its own open state, so closing one never discards a draft in another. The add flow is a card carrying the dormant-directory provider select — a bare-mounted llm-pi-ai offers its whole installed catalog before any route exists. The pi-ai card additionally edits that route's model list and can ask the provider what it serves. A row labels API-key state with a green solid dot only when a referenced credential is confirmed configured, and with a red solid dot only when a named reference is confirmed missing; reference-free provider-native authentication and unavailable credential enrichment remain unmarked. The editor is a hand-written card per adapter family: the primary field is a single API key input — the page never asks for an environment-variable name; a typed key stores write-only through credentials.set under the profile's reference, deriving _API_KEY when the profile has none, and the pi-ai profile records that derivation as apiKeyEnv, so settings.yaml never carries a key value. Leaving a new pi-ai provider's key blank saves a reference-free profile and therefore preserves provider-native authentication such as the Bedrock credential chain or Vertex ADC. A successful Apply emits a local accessible status message without echoing secret material. The collapsed 自定义设置 fold carries the curated extras — baseURL for both families (the deepseek placeholder shows the public endpoint), each adapter's model catalog, and the display name and API protocol of a pi-ai route the adapter does not ship. Those two are what a hand-declared route names for itself: the create card asks for both because nothing can default them, so the editor reaches both rather than leaving them to settings.yaml. Clearing the name unsets it and the route falls back to its id, which is what the placeholder shows; the protocol has no such fallback. A catalog route gets neither — it defaults its name from its catalog entry, and its models each carry their own protocol, so a route-level one could only override every one of them. The Provider ID stays fixed: it is the settings key, the name every other namespace and every logged session references, and the stem of a credential reference the page cannot read back to move. Reasoning effort is deliberately NOT among them: it is a per-model capability and the models under one provider disagree about which levels they accept, so a provider-scoped control could only be set to a value some of them reject — which would hide even the models that support the level. The composer's model picker offers each model its own levels, and a switch there records provider, model, and effort together as the default for the next session. The profile field stays in settings.yaml for a deployment that knows its route. Each DeepSeek row edits id, optional display name, optional contextWindow/maxTokens, and an image-input checkbox writing inputModalities (unchecked leaves the adapter's text-only default); existing fields outside that curated set survive edits, while every other profile field stays owned by settings.yaml. A row is deletable only when the user layer alone carries it (removal restores the composition base), and its localized confirmation dialog names the provider in the title, description, and final action. A row is tagged Custom when the directory entry says the owning adapter ships nothing under that key. The tag follows that answer alone: having a stored profile does not make a route custom — narrowing a shipped provider's models stores one too — and an adapter that reports nothing leaves its rows untagged rather than being read as shipped.

The notice step owns its exact copy and version in src/onboarding-copy.ts. On loopback it compares and writes ui-onboarding.welcomeNoticeVersion through the existing settings API; only an explicit Continue records the current version. A principal generation change clears acknowledgement synchronously even while a write is pending, and that prior-principal settlement cannot restore or fail the new state. A non-loopback browser cannot use that Host-only namespace, so acknowledgement is process-local and the notice returns after reload.

The product notice is the package's only settings.onboarding contribution. Missing credentials and unavailable adapters remain visible on the Models page and never open a provider-specific dialog during first run.

Every edit lands as settings.mutate path ops against the stored section — a set per changed field, an unset per cleared one, and a single unset for a deleted provider row. The page only ever holds the REDACTED descriptor, so it mutates the fields it can see rather than rebuilding a section. DeepSeek's models is one replace-by-value array: the editor shows inherited effective rows until the first model edit materializes the complete array in the user layer, while reset unsets that override. A row carries the model id and display name; its context window and output cap sit behind the row's own disclosure, with the same fields the pi-ai provider form uses. Either capacity is typed as a count with an optional decimal K or M suffix (256K, 1M; 1M is 1000K) and stored as the plain count, spelled back in the shortest form that round-trips. Empty ids, duplicate ids, empty explicit names, and unreadable, non-positive, or fractional capacities fail before any write. A typed API key is judged on its own field the same way: after trimming, it must be non-empty and every character must be printable ASCII ([\x21-\x7E]), which is exactly what an HTTP header value can carry — the twin of normalizeApiKey in @deepseek-ai/dsh-llm, mirrored here because the source-plane split forbids importing it. A value matching a pasted NAME=value environment line or wrapped in matching quotes is refused as the same format failure; that pasted-line check runs only in the browser, since a false positive in a resolver would leave the environment refusing the key as well. A field holding only whitespace fails rather than being silently dropped, while an empty field is not a failure at all: it means keep the stored key on an editor card, and authenticate some other way on a create card. A refused key blocks both the write and the endpoint interrogation, so the page never spends a round trip to be told what the field already says. Each settings write carries the card's current revision, so a concurrent write from another tab or an external settings.yaml edit is refused as settings-conflict; after settings commit, the card adopts the returned redacted user subtree and revision before storing the credential, which makes a failed credential stage retry only that stage. Deletion removes a configured, writable credential only when the profile names the page's derived _API_KEY target, then unsets the profile; both operations are idempotent, and a partial failure remains in the identified confirmation dialog for retry. Environment credentials, custom references, and credentials whose target cannot be identified remain untouched. Once loaded, the page subscribes directly to forwarded settings/document-updated, credentials/updated, and llm/adapters-updated owner events, plus local connection/reset, so an external settings.yaml edit, a second tab, or a settings-born route converges without polling.

Model list and endpoint interrogation

A pi-ai profile's models list is edited on the card: one row per model showing its id and display name, with the context window and output cap behind a per-row disclosure and two label-free actions — expand and delete — on the right. The same disclosure carries the row's capability declaration: a checkbox for image input (writing input: [text, image]; unchecked leaves the field to the installed entry and the route default), and a reasoning section — checking it declares the offered thinking levels (off/minimal/low/medium/high/xhigh/max, at least one besides off), each level optionally carrying its wire spelling (empty meaning the canonical one, with off sending nothing), plus the level a selection starts from (未设置 inherits the route default; 默认 explicitly sends no effort). An OpenAI-completions route also offers the thinking dispatch format and, under chat-template, custom chat_template_kwargs fields whose values are literals or follow the thinking switch or level; leaving that format drops the kwargs with it. Unchecking reasoning removes the declaration rather than storing false — stripping a catalog model's reasoning stays a settings.yaml edit. A declaration the adapter would refuse (no level beyond off, a default outside the declared set, an unnamed custom field) fails before any write, named by row position. An empty list means "serve this route's built-in catalog", so a row is only ever added deliberately; clearing a capacity drops it rather than storing a value the schema would reject, and the adapter's route-level fallbacks size whatever configuration leaves out — an empty capacity shows those fallbacks' magnitude as its placeholder, a hint rather than a mirror, since the field counts K as 1000 and a deployment may override them. A capacity that is not a positive integer is simply not stored.

Fetch available models asks llm.discoverModels about the endpoint the form currently shows, including a base URL edited but not yet saved and a key typed but not yet stored, so adding a provider is one pass instead of save-then-return. The reply opens a picker rather than being written: candidates already configured start unchecked, so adopting a selection never overwrites a capacity the user corrected. A provider that cannot be interrogated is a detour, not a dead end — the adapter's own message appears beside the rows, which stay editable by hand.

Add a custom provider declares a route pi-ai does not ship. It is its own card rather than the editor with extra fields, because the route id is being chosen here and the settings address does not exist until it is: one settings.mutate sets the whole profile at providers., and the key travels separately through credentials.set under the same _API_KEY derivation an existing provider uses. What a hand-declared route cannot default gates the create button — a unique Provider ID, an endpoint, a protocol, and at least one uniquely-identified model — so the failure names the field while the user is still looking at it. The id must start with a lowercase letter, because it is also the stem of the derived credential reference and a reference is a POSIX shell identifier: a digit-leading id otherwise passes every check this card makes and then fails at the credential seam with a raw regular expression. Capacities do not gate it: the adapter's fallbacks size a model the endpoint described by id alone, which is what most listings return. The protocol choices are read out of the namespace's own schema rather than a wire field or a constant, so they cannot drift from the ones the adapter accepts. The card records the conventional apiKeyEnv reference only when a key is typed, the same rule the editor applies, so a route declared for provider-native authentication is not born pointing at a reference nothing will ever set. When the profile write lands but the key write fails, the provider already exists: the card settles the fields describing it, retries the credential alone — re-running the profile write would carry the revision that write just superseded, so the Host would answer settings-conflict and the key could never be stored from here — and reports the created provider even if the user then cancels.

Model Experience

None, as the section renders a browser configuration UI; nothing here reaches a model request.

KV Cache effect

None; this package neither assembles nor sends a provider request.

Known Limitations and Deferred Work

  • Only the API key and curated fold fields are editable on the card — the hand-written editor traded schema-generic field coverage for the mockup layout (Agent Note). Both families expose baseURL and model id/name/contextWindow/maxTokens; a hand-declared pi-ai route also exposes displayName and api. Retry policy, timeouts, DeepSeek model descriptions, and other advanced fields remain in settings.yaml; existing model fields the editor does not show are preserved. A profile schema without the conventional fields renders the hint alone, and the two curated layouts key on the llm-deepseek/llm-pi-ai namespaces by name.
  • Credential cleanup is intentionally narrow — deleting a row removes the configured, writable credential only when its reference is the exact _API_KEY target this page derives. Custom references, environment credentials, and unidentifiable targets are retained because the row cannot prove ownership of them.
  • Only pi-ai routes can be hand-declared — the custom-provider card writes into llm-pi-ai, the one namespace whose profiles describe a whole provider. A llm-deepseek route is a composition fact, not something this page can create.
  • Interrogation covers OpenAI-compatible endpoints — the adapter reads only that model-list response format, so a gateway speaking another protocol reports that it cannot be asked and its