mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-11-09 20:07:43 +08:00
feat(models): show model selector badges from the /models listing (#7027)
* refactor(llm-deepseek): share the /models listing machinery
Move the account route's `/models` request and decoding, its
credential-scoped listing cache, and listed-catalog resolution with the
version gate into the shared DeepSeek Messages library, so a second
route can follow the same listing. Names drop the account prefix, the
cache takes each access's endpoint settings with the call, and it gains
`invalidate()` for a forced reload. `isBuiltInCatalog()` recognizes the
catalog an uncustomized `models` setting resolves to.
The account route keeps its behavior; only its imports and the
override diagnostic wording ("model listing") change.
* feat(llm-deepseek-api-key): follow the official /models listing
While `models` equals the built-in catalog and the Messages origin is
https://api.deepseek.com, `deepseek-official` serves the official
`GET /models` listing, authenticated with the API key in `x-api-key`.
Loading, refresh, fallback, merging, and the client-version gate match
the account route: the first access waits up to `modelsTimeoutMs`, a
listing older than `modelsMaxAgeMs` refreshes in the background on the
next access, a failure keeps the last good listing or the built-in
catalog, and listed models this build cannot serve are refused before
network I/O.
A customized `models` list replaces the catalog and requests nothing.
Switching between a customized catalog and the listing discards the
cached listing, so Restore defaults reloads it on the next access.
Suites that boot the route on the official endpoint now answer the
listing themselves instead of reaching the network.
* docs: describe the official route model listing
Document when `deepseek-official` follows the official `/models`
listing, its timing fields, customization precedence, and the forced
reload after Restore defaults in the API-key README, the shared
transport README, and the provider guide. Record the decision in a new
Agent Note, keep the account note's statement of where the shared
listing code lives current, and regenerate the configuration catalog.
* test(llm-deepseek-api-key): pin the once-per-listing hidden-entry diagnostic
* docs: regenerate module and event graphs for the shared listing
* fix(llm-deepseek-api-key): report credential failures on the model request
On the official endpoint, `connect` waited for the listing view, whose
credential lookup rethrew a malformed key or a credential-store failure
before the agent loop recorded the prompt. A failed lookup now resolves
the connection from the current listing view, so `resolveAuth` reports
the failure on the model request and the turn keeps the prompt and the
failed attempt. A caller abort still rejects.
* docs(llm-deepseek-api-key): name which hidden listing entries are logged
* test: keep unit suites off the official model listing
Suites that boot the DeepSeek routes with a dummy or inherited key now
request `GET https://api.deepseek.com/models` on the official endpoint.
A unit-lane setup file rejects that request at once, so such a suite
serves the built-in catalog instead of reaching production or waiting
out the listing deadline on a network that never answers; suites that
exercise the listing answer `fetch` themselves. Other requests pass
through unchanged.
* feat(llm-deepseek): read tool_update from the model listing
Both DeepSeek routes now take a listed model's Messages tool update mode
from `api_capabilities.anthropic_messages.tool_update`, alongside the
system prompt mode. `in-history` and `addition-only` replace the
bundled mode; any other value reads as `leading-only` and removes it.
An absent or `null` mode keeps the bundled one, so a listing that does
not declare the field leaves `deepseek-flash` on `addition-only`.
`system_prompt_update: null` likewise keeps the bundled mode now; it
previously read as `leading-only`, which a serializer writing empty
values as null would have turned into a cache-unfriendly downgrade.
* fix(llm-deepseek): sync the built-in context window with the official listing
The official `/models` listing declares a 1,048,576-token context window
for `deepseek-flash` and `deepseek-v4-pro`; the built-in catalog said
1,000,000, a decimal-versus-binary mismatch. Both built-in entries now
carry the listed value, so offline starts and listing failures size the
context the same way the listing does. The Models page shows the exact
count for these rows, since 1,048,576 has no decimal K/M spelling.
`defaultContextWindow`, the fallback for model ids the catalog does not
describe, stays 1,000,000.
* test(llm-deepseek-api-key): reject the stubbed listing request with an Error
* test(cli): keep the built SDK smoke off the official model listing
The SDK profile's initialize now resolves the deepseek-official catalog,
which requests GET https://api.deepseek.com/models on the official
endpoint. With the smoke's fixture key that request answers 401 and the
route warns on stderr, so the test's empty-stderr assertion failed in the
e2e and snapshots lanes. Point the run at a loopback mock and assert it
received no model call.
* feat(llm): carry /models badges into the model catalog
The shared DeepSeek listing decoder reads an optional per-entry
`badge: { text, tone }`. Text is one literal or translations keyed by
lowercase language id with a required English entry, each trimmed to 1
to 12 characters on one line; tone is neutral, info, success, or
warning, and an absent or unrecognized tone reads as neutral. An absent
or null badge declares none; an unreadable one is dropped with a listing
problem and never hides its model.
resolveListedCatalog keeps the badges of offered models, both DeepSeek
routes attach them through catalogModelInfo, LlmRuntime.listModels
detaches them, and the Host model catalog forwards them as
ModelCatalogModel.badge. resolveModelInfo omits the badge, so no model
request, Session event, or model-facing tool carries it.
* feat(client): render catalog badges in the model selector
The composer model seat shows a catalog model's badge after its name in
the trigger, the root-menu value, and each option: a Tag in the badge's
tone whose text is resolved for the active locale through
ctx.locale.resolveText. A conversation.input.model.label contribution
replaces the name and badge together and receives the badge in its
owner props.
Remove ui-deepseek-model-label-1017, which hard-coded `1017` beside
DeepSeek-V4.1-Flash for two providers, and its Web bundle row. The badge
now comes only from the model listing, so the build ships none of its
own; the upgrade guide covers profile patches that still target the
removed row. The Web e2e replaces the fixed-badge case with replay
catalogs that declare literal and translated badges, and the replay
adapter validates and advertises a configured badge.
* feat(llm-deepseek): read model badges from client_metadata
Each /models entry now declares its selector badge under
client_metadata["deepseek-harness"].badge, keyed by the User-Agent product
token like client_requirements, so the public model fields stay unchanged
and later DeepSeek Harness fields join the same object. Every field under
client_metadata is ignorable: null at any level shows no badge, and a level
that is not an object drops only the badge with a diagnostic naming it.
* docs: record why the listing badge container is client_metadata
* fix(models): refresh listed badges in open selectors
* refactor(models): share listing cache lifecycle wiring
* docs(models): refresh listing event producer catalog
* fix(models): retain badges when listing refresh fails
* fix(models): validate badge text and preserve tags beside long names
* test(models): cover account listing automatic badge removal
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-22-listed-model-badges.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-10-22-listed-model-badges.md
|
||||
/agent-note-model-selector-badges-come-from-the-model-listing:
|
||||
en: cc4b8fee78e75a98
|
||||
zh: 3cf21a37bea4410c
|
||||
/agent-note-model-selector-badges-come-from-the-model-listing/problem:
|
||||
en: ed011e743511ef25
|
||||
zh: 2389e339f1a6dcbf
|
||||
/agent-note-model-selector-badges-come-from-the-model-listing/decision:
|
||||
en: 84b99f80d8824117
|
||||
zh: 94cd96f59caf5b43
|
||||
/agent-note-model-selector-badges-come-from-the-model-listing/alternatives-considered:
|
||||
en: 079466e0dd56584e
|
||||
zh: 8e33049c422d77cb
|
||||
/agent-note-model-selector-badges-come-from-the-model-listing/consequences:
|
||||
en: 3ba39c2a109e49cd
|
||||
zh: 3eb16676854230f9
|
||||
@@ -0,0 +1,47 @@
|
||||
# Agent Note: Model selector badges come from the model listing
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-22-listed-model-badges.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The Web model selector showed `1017` beside `DeepSeek-V4.1-Flash` through a dedicated client package that matched two DeepSeek providers, one model id, and one exact catalog name. Changing or retiring that release marker, or labeling another model, required a client release, while both DeepSeek routes already read model metadata from the service's `GET /models` listing.
|
||||
|
||||
## Decision
|
||||
|
||||
- Each `/models` entry may declare `badge: { text, tone }` under `client_metadata["deepseek-harness"]`, keyed like `client_requirements` by the User-Agent product token. `client_metadata` holds client-specific data that every build may ignore, so the public model fields stay unchanged and later DeepSeek Harness fields join the same object. `text` uses the package-metadata `LocalizedText` form: one string, or translations keyed by lowercase language id that include `en`. Each text is trimmed and holds 1 to 12 user-perceived characters on one line; controls, Unicode line separators, bidi controls, and text made only of whitespace or default-ignorable characters are rejected.
|
||||
- `tone` is `neutral`, `info`, `success`, or `warning`: the `Tag` palettes for a plain fact, a highlight, a benefit, and attention. The shipped release marker used `info`; the surveyed selectors draw plan and capability tags neutral, new features with an accent, and usage hints in a warning color. `danger` and `solid` keep their failure and selection meanings.
|
||||
- [`parseModelListing`](../../../../packages/llm/llm-deepseek/src/listing.ts) decodes the badge beside the entry's other declarations, and [`resolveListedCatalog`](../../../../packages/llm/llm-deepseek/src/listed-catalog.ts) keeps the badges of offered models; both routes attach them through `catalogModelInfo`. An absent or `null` value at any level of that path shows no badge, and an absent or unrecognized tone shows `neutral`. A level that is not an object or an unreadable badge drops the badge, which is reported with the listing's other problems and never hides its model.
|
||||
- `LlmModelInfo.badge` and `ModelCatalogModel.badge` carry the badge from discovery through the Host model catalog to the browser. `resolveModelInfo` omits it, and no model request, Session event, or model-facing tool reads it.
|
||||
- [`ui-model-selection`](../../../../packages/client/ui-model-selection/README.md) renders the badge before the catalog name in the trigger and after it in the root-menu value and each option, resolving its text with `ctx.locale.resolveText`. A `conversation.input.model.label` contribution replaces the name and badge together and receives the badge in its owner props.
|
||||
- The build ships no badge of its own: a route before its first listing shows none. The API-key route does not follow listings for customized catalogs or non-official endpoints; the account route follows the origin of its configured `baseURL`, including custom endpoints. The fixed `ui-deepseek-model-label-1017` package and its Web bundle row are removed.
|
||||
- After first use, the credential-scoped cache refreshes automatically at `modelsMaxAgeMs` (ten minutes by default) and publishes changed catalogs through `llm/models-updated`. Successful listings replace all badge metadata, so omission or `null` clears an old badge. Failed refreshes retain the last successful listing, including badges: a failed request does not establish that the server withdrew a badge. This avoids independent polling in every browser. A zero interval preserves access-only refresh.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**A top-level `badge` field.** The public `/models` fields describe model capabilities and limits for every client, while the badge is DeepSeek Harness release and campaign copy. A top-level field reads as a model attribute that other clients could start rendering, after which the service could no longer change or retire it freely, and two clients could not show different badges.
|
||||
|
||||
**`badge` inside `client_requirements["deepseek-harness"]`.** It adds no top-level field, and released builds ignore an extra key there. But an unreadable requirement hides its model on every build, released ones included, so a serializer that writes the emptied object as `null` after a badge is retired would hide the model. Requirements gate availability; display data must stay ignorable.
|
||||
|
||||
**A narrower map such as `client_presentation` or `client_annotations`.** These names limit the object to display data or descriptive notes, so the next client-specific field of another kind, such as a default, would need another top-level field; `client_metadata` takes any field a build may ignore.
|
||||
|
||||
**A semantic badge kind mapped to client dictionaries.** OpenCode, ZCode, and DeepSeek-Reasonix translate a closed set of client-owned tag kinds, and none renders text a server sends. A fixed vocabulary cannot express a release code such as `1017`, and each new wording would need the client release the listing exists to avoid. Listing text is provider data, which the [locale-owned copy decision](../architecture/2026-08-23-locale-owned-client-ui-copy.md) renders verbatim; the `LocalizedText` form still lets the service supply translations.
|
||||
|
||||
**Accept colors, CSS, or markup from the listing.** No surveyed product accepts styling from a catalog, and arbitrary colors bypass the theme's light and dark palettes and their contrast. A closed tone set maps onto the existing `Tag` palettes, and an unrecognized tone degrades to `neutral`, so a value a newer service adds never removes the text.
|
||||
|
||||
**Keep `1017` as a built-in default.** A bundled badge would keep the marker offline and on custom endpoints. Under the listing rule that an absent field keeps the build's value, the service could then remove it only with a special empty value, and old builds would keep the stale marker wherever no listing loads. Shipping no badge makes the listing the only source, so omitting the field removes the badge.
|
||||
|
||||
**An expiry or start time on the badge.** The listing cache lives in memory and refreshes on access after `modelsMaxAgeMs`, so a badge the service drops disappears within one refresh. Client-side time windows would add dependence on the client clock without removing reachable staleness.
|
||||
|
||||
**Drop the whole entry or listing for a bad badge.** OpenCode drops a model whose catalog status it cannot decode, and Codex fails its catalog decode on one unknown enum value. A badge is presentation only, so a bad one is dropped by itself.
|
||||
|
||||
**A DeepSeek-specific badge Remote beside the catalog.** Keeping the badge out of `LlmModelInfo` would need a second Remote, a second client fetch, and agreement between two caches about which models exist. One optional field on the existing catalog reaches every placement in one load.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The service changes, localizes, or removes a badge without a client release; a running client shows the change after its next listing refresh and catalog reload.
|
||||
- A condition that older builds must enforce belongs in `client_requirements`; every field under `client_metadata` must be safe for a build that does not read it.
|
||||
- `deepseek-flash` shows no badge until the service declares one and a listing loads. Customized catalogs and non-official endpoints on the API-key route do not receive listed badges.
|
||||
- The `/model` popup, the trigger's accessible name, and the Models settings page keep plain names.
|
||||
- A profile patch that still targets `ui-deepseek-model-label-1017` matches no row and boots with a warning ([upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.2/model-badge-package-removal/guide.md)).
|
||||
@@ -0,0 +1,47 @@
|
||||
# Agent Note: 模型选择器角标来自模型列表
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-22-listed-model-badges.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
Web 模型选择器通过一个专门的客户端包在 `DeepSeek-V4.1-Flash` 旁显示 `1017`,该包写死匹配两个 DeepSeek 提供方、一个模型 id 和一个确切的目录名称。修改或撤下这个版本标记、给其他模型加标签,都要发一次客户端版本;而两条 DeepSeek 路由早已从服务端的 `GET /models` 列表读取模型元数据。
|
||||
|
||||
## 决策
|
||||
|
||||
- 每个 `/models` 条目可以在 `client_metadata["deepseek-harness"]` 下声明 `badge: { text, tone }`,与 `client_requirements` 一样以 User-Agent 的产品名为键。`client_metadata` 存放所有版本都可以忽略的客户端专属数据,因此公开的模型字段保持不变,以后新增的 DeepSeek Harness 字段也放进同一个对象。`text` 采用包元数据的 `LocalizedText` 形式:单个字符串,或以小写语言 id 为键且包含 `en` 的译文。每段文字去掉首尾空白后须为单行,长度为 1 到 12 个用户可见字符;拒绝控制字符、Unicode 行分隔符、双向控制符及仅由空白或默认可忽略字符组成的文字。
|
||||
- `tone` 取 `neutral`、`info`、`success` 或 `warning`,分别是 `Tag` 表示普通事实、强调、利好和需要注意的配色。已发布的版本标记使用 `info`;调研的选择器把套餐和能力标签画成中性色,新功能用强调色,用量提示用警示色。`danger` 和 `solid` 保留各自表示失败和当前选中的含义。
|
||||
- [`parseModelListing`](../../../../packages/llm/llm-deepseek/src/listing.ts) 与条目的其他声明一起解码角标,[`resolveListedCatalog`](../../../../packages/llm/llm-deepseek/src/listed-catalog.ts) 保留已提供模型的角标;两条路由都通过 `catalogModelInfo` 附上角标。这条路径上任何一层缺省或为 `null` 时不显示角标;`tone` 缺省或无法识别时按 `neutral` 显示。某一层不是对象或角标无法读取时,角标被丢弃,并与列表的其他问题一起报告,但不会隐藏所属模型。
|
||||
- `LlmModelInfo.badge` 与 `ModelCatalogModel.badge` 把角标从模型发现经 Host 模型目录送到浏览器。`resolveModelInfo` 不返回它,模型请求、Session 事件和面向模型的工具都不读取它。
|
||||
- [`ui-model-selection`](../../../../packages/client/ui-model-selection/README.zh.md) 在触发按钮中把角标放在目录名称之前,在根菜单当前值和各选项中仍放在名称之后,并用 `ctx.locale.resolveText` 解析其文字。`conversation.input.model.label` 的扩展会同时替换名称与角标,并从 owner props 收到角标。
|
||||
- 本版本不内置任何角标:路由加载到第一份列表之前不显示角标。API key 路由在自定义目录或非官方端点上不跟随列表;账号路由跟随配置的 `baseURL` 的源,包括自定义端点。写死的 `ui-deepseek-model-label-1017` 包及其 Web bundle 条目已移除。
|
||||
- 按凭据区分的缓存首次使用后按 `modelsMaxAgeMs`(默认十分钟)自动刷新,通过 `llm/models-updated` 发布目录变化。成功加载会替换所有角标元数据,因此省略或 `null` 都会清除旧角标。刷新失败时保留上次成功加载的列表及其角标:请求失败并不能证明服务端撤下了角标。这样也避免了每个浏览器独立轮询。间隔为零时仍只在访问时刷新。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**顶层 `badge` 字段。** 公开的 `/models` 字段为所有客户端描述模型的能力与限制,而角标是 DeepSeek Harness 的发版和运营文案。放在顶层会被当作模型属性,其他客户端可能开始显示它,此后服务端就无法随意修改或撤下;两个客户端也无法显示不同的角标。
|
||||
|
||||
**把 `badge` 放进 `client_requirements["deepseek-harness"]`。** 这样不增加顶层字段,已发布版本也会忽略其中多出的键。但版本要求无法读取时,所有版本都会隐藏该模型,已发布的版本也不例外;撤下角标后,序列化器若把清空的对象写成 `null`,模型就会被隐藏。版本要求决定模型能否使用,展示数据必须可以忽略。
|
||||
|
||||
**范围更窄的映射,例如 `client_presentation` 或 `client_annotations`。** 这些名称把对象限定为展示数据或附加说明,下一个其他类型的客户端专属字段(例如默认值)又要新开一个顶层字段;`client_metadata` 可以容纳任何版本都能忽略的字段。
|
||||
|
||||
**用语义化的角标类别映射到客户端字典。** OpenCode、ZCode 和 DeepSeek-Reasonix 都翻译一组封闭的、由客户端持有的标签类别,没有一家直接渲染服务端发来的文字。固定词表表达不了 `1017` 这样的版本代号,每出现一种新说法都要发一次客户端版本,而列表正是为了免去这一步。列表文字属于提供方数据,按[客户端文案归 locale 管理的决定](../architecture/2026-08-23-locale-owned-client-ui-copy.zh.md)原样渲染;`LocalizedText` 形式仍允许服务端提供译文。
|
||||
|
||||
**接受列表下发的颜色、CSS 或标记。** 调研的产品都不接受目录下发的样式;任意颜色会绕过主题的明暗配色及其对比度。封闭的语调集合直接对应现有 `Tag` 配色;无法识别的语调降级为 `neutral`,因此较新的服务端新增的取值不会让文字消失。
|
||||
|
||||
**把 `1017` 保留为内置默认值。** 内置角标能让标记在离线和自定义端点下继续显示。但按照“字段缺失时沿用本版本的值”的列表规则,服务端只能用特殊的空值才能去掉它,而且旧版本在加载不到列表时会一直显示过时的标记。不内置角标,列表就是唯一来源,省略字段即可去掉角标。
|
||||
|
||||
**给角标加过期时间或生效时间。** 列表缓存只在内存中,超过 `modelsMaxAgeMs` 后在访问时刷新,服务端撤下的角标在一次刷新内就会消失。客户端时间窗口会引入对客户端时钟的依赖,却消除不了实际可能出现的陈旧数据。
|
||||
|
||||
**角标有误时丢弃整个条目或整份列表。** OpenCode 会丢掉目录状态无法解码的模型,Codex 遇到一个未知枚举值就让整份目录解码失败。角标只用于展示,有误时只丢弃角标本身。
|
||||
|
||||
**在目录之外为 DeepSeek 单独提供角标 Remote。** 让角标不进入 `LlmModelInfo`,就需要第二个 Remote、客户端的第二次请求,以及两份缓存对“有哪些模型”保持一致。在现有目录上增加一个可选字段,一次加载即可送到所有显示位置。
|
||||
|
||||
## 影响
|
||||
|
||||
- 服务端无需客户端发版即可修改、翻译或撤下角标;运行中的客户端在下一次列表刷新和目录重新加载后显示变化。
|
||||
- 旧版本必须执行的条件放在 `client_requirements`;`client_metadata` 下的每个字段都必须保证不读取它的版本也能安全运行。
|
||||
- 服务端声明并加载到列表之前,`deepseek-flash` 不显示角标。API key 路由的自定义目录与非官方端点不接收列表角标。
|
||||
- `/model` 弹窗、触发按钮的无障碍名称和模型设置页仍只显示名称。
|
||||
- 仍指向 `ui-deepseek-model-label-1017` 的 profile patch 不再匹配任何条目,启动时会给出警告([升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.2/model-badge-package-removal/guide.zh.md))。
|
||||
@@ -1,107 +0,0 @@
|
||||
/** The shipped browser plugin decorates both DeepSeek routes without changing catalog identity. */
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { chromium, type Browser, type Page } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
captureStableAria, compareOrRefreshGolden, launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold,
|
||||
} from './scaffold.ts'
|
||||
import { connectFreshWorkspaceZh, ZH_BROWSER_LOCALE } from './support.ts'
|
||||
|
||||
const NAME = 'DeepSeek-V4.1-Flash'
|
||||
const FIXTURE = fileURLToPath(new URL('./fixtures/deepseek-model-label-1017/session.v4.jsonl', import.meta.url))
|
||||
const EXPECTED = fileURLToPath(new URL('./expected/deepseek-model-label-1017/models.expected.md', import.meta.url))
|
||||
const MODE = webSnapshotMode()
|
||||
|
||||
describe.skipIf(MODE === 'record')('web e2e: temporary DeepSeek release badge', () => {
|
||||
let scaffold: WebScaffold
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
let tripwire: ReturnType<typeof watchConsole>
|
||||
|
||||
beforeAll(async () => {
|
||||
scaffold = await launchWebScaffold({
|
||||
extraOverlayPath: fileURLToPath(new URL('./fixtures/deepseek-model-label-1017/account.overlay.yml', import.meta.url)),
|
||||
replayFixture: FIXTURE,
|
||||
replayProvidersOnly: true,
|
||||
replayProviders: [
|
||||
{ id: 'deepseek-account', name: 'DeepSeek Account', models: [{ id: 'deepseek-flash', name: NAME }] },
|
||||
{ id: 'deepseek-official', name: 'DeepSeek', models: [
|
||||
{ id: 'deepseek-flash', name: NAME }, { id: 'deepseek-v4-pro', name: 'DeepSeek-V4-Pro' },
|
||||
] },
|
||||
{ id: 'external', name: 'External', models: [{ id: 'deepseek-flash', name: NAME }] },
|
||||
],
|
||||
})
|
||||
browser = await chromium.launch()
|
||||
page = await browser.newPage({ viewport: { width: 1440, height: 900 }, locale: ZH_BROWSER_LOCALE })
|
||||
tripwire = watchConsole(page)
|
||||
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
|
||||
await connectFreshWorkspaceZh(page, scaffold.workspaceCwd)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
try { await browser?.close() }
|
||||
finally { await scaffold?.close() }
|
||||
})
|
||||
|
||||
it('shows the same badge in all placements while preserving the chosen provider and model id', async () => {
|
||||
const trigger = page.getByRole('button', { name: /^选择模型/ })
|
||||
await trigger.getByText('1017', { exact: true }).waitFor()
|
||||
expect(await trigger.textContent()).toContain(`${NAME} 1017`)
|
||||
expect(await trigger.getAttribute('aria-label')).toBe(`选择模型,当前 ${NAME}`)
|
||||
try {
|
||||
await trigger.click()
|
||||
const current = page.getByRole('menuitem', { name: /^模型/ })
|
||||
expect(await current.getByText('1017', { exact: true }).count()).toBe(1)
|
||||
await current.click()
|
||||
const menu = page.getByRole('menu', { name: '模型', exact: true })
|
||||
await compareOrRefreshGolden(EXPECTED, await captureStableAria(page, '[role="menu"][aria-label="模型"]', scaffold.workspaceCwd), MODE)
|
||||
expect(await menu.getByText('1017', { exact: true }).count()).toBe(2)
|
||||
expect(await menu.getByRole('group', { name: 'External', exact: true }).getByText('1017', { exact: true }).count()).toBe(0)
|
||||
const account = menu.getByRole('group', { name: 'DeepSeek 账号', exact: true })
|
||||
await account.getByRole('menuitemradio', { name: `${NAME} 1017`, exact: true }).click()
|
||||
await expect.poll(() => scaffold.ctx.agentDefaultModel.currentSelection()).toEqual({ provider: 'deepseek-account', model: 'deepseek-flash' })
|
||||
await expect.poll(() => trigger.textContent()).toContain(`${NAME} 1017`)
|
||||
const catalog = await scaffold.ctx.llm.listModels('deepseek-account')
|
||||
expect(catalog.map(model => ({ id: model.id, name: model.name }))).toEqual([{ id: 'deepseek-flash', name: NAME }])
|
||||
} finally {
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('Escape')
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the release badge inside a 390px model menu and uses both theme palettes', async () => {
|
||||
try {
|
||||
await page.setViewportSize({ width: 390, height: 844 })
|
||||
const trigger = page.getByRole('button', { name: /^选择模型/ })
|
||||
await trigger.click()
|
||||
await page.getByRole('menuitem', { name: /^模型/ }).click()
|
||||
const menu = page.getByRole('menu', { name: '模型', exact: true })
|
||||
const badge = menu.getByRole('group', { name: 'DeepSeek 账号', exact: true }).getByText('1017', { exact: true })
|
||||
const box = await badge.boundingBox()
|
||||
expect(box).not.toBeNull()
|
||||
expect(box!.x).toBeGreaterThanOrEqual(12)
|
||||
expect(box!.x + box!.width).toBeLessThanOrEqual(378)
|
||||
const colors = await badge.evaluate((node) => {
|
||||
const body = node.ownerDocument.body
|
||||
const previous = body.getAttribute('data-ds-dark-theme')
|
||||
try {
|
||||
return [false, true].map((dark) => {
|
||||
body.toggleAttribute('data-ds-dark-theme', dark)
|
||||
const style = getComputedStyle(node)
|
||||
return { color: style.color, background: style.backgroundColor }
|
||||
})
|
||||
} finally {
|
||||
if (previous === null) body.removeAttribute('data-ds-dark-theme')
|
||||
else body.setAttribute('data-ds-dark-theme', previous)
|
||||
}
|
||||
})
|
||||
expect(colors[0]).not.toEqual(colors[1])
|
||||
for (const color of colors) expect(color.color).not.toBe(color.background)
|
||||
} finally {
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('Escape')
|
||||
await page.setViewportSize({ width: 1440, height: 900 })
|
||||
}
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
})
|
||||
})
|
||||
+3
-1
@@ -4,8 +4,10 @@
|
||||
- menuitemradio "DeepSeek-V4.1-Flash 1017"
|
||||
- group "DeepSeek":
|
||||
- text: DeepSeek
|
||||
- menuitemradio "DeepSeek-V4.1-Flash 1017" [checked]
|
||||
- menuitemradio "DeepSeek-V4.1-Flash 新" [checked]
|
||||
- menuitemradio "DeepSeek-V4-Pro"
|
||||
- menuitemradio "DeepSeek model with a very long release and capability description that exceeds the selector width Preview"
|
||||
- menuitemradio "Invalid badge model"
|
||||
- group "External":
|
||||
- text: External
|
||||
- menuitemradio "DeepSeek-V4.1-Flash"
|
||||
@@ -0,0 +1,185 @@
|
||||
/** Catalog badges reach every selector placement through the Host catalog without changing catalog identity. */
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { LlmAdapter, type LlmModelBadge, type StreamChunk } from '@deepseek-ai/dsh-llm'
|
||||
import { parseModelListing } from '@deepseek-ai/dsh-llm-deepseek'
|
||||
import { chromium, type Browser, type Page } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
captureStableAria, compareOrRefreshGolden, launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold,
|
||||
} from './scaffold.ts'
|
||||
import { connectFreshWorkspaceZh, ZH_BROWSER_LOCALE } from './support.ts'
|
||||
|
||||
const NAME = 'DeepSeek-V4.1-Flash'
|
||||
const LONG_NAME = 'DeepSeek model with a very long release and capability description that exceeds the selector width'
|
||||
const FIXTURE = fileURLToPath(new URL('./fixtures/model-badge/session.v4.jsonl', import.meta.url))
|
||||
const EXPECTED = fileURLToPath(new URL('./expected/model-badge/models.expected.md', import.meta.url))
|
||||
const MODE = webSnapshotMode()
|
||||
|
||||
/** DeepSeek fixture declarations pass through the production wire decoder before replay. */
|
||||
function listedModel(id: string, name: string, badge: unknown) {
|
||||
const entry = parseModelListing({ data: [{ id, name, client_metadata: { 'deepseek-harness': { badge } } }] }).entries[0]!
|
||||
return { id, name, ...(entry.badge?.kind === 'badge' ? { badge: entry.badge.badge } : {}) }
|
||||
}
|
||||
|
||||
describe.skipIf(MODE === 'record')('web e2e: catalog model badges', () => {
|
||||
let scaffold: WebScaffold
|
||||
let browser: Browser
|
||||
let page: Page
|
||||
let tripwire: ReturnType<typeof watchConsole>
|
||||
|
||||
beforeAll(async () => {
|
||||
scaffold = await launchWebScaffold({
|
||||
extraOverlayPath: fileURLToPath(new URL('./fixtures/model-badge/account.overlay.yml', import.meta.url)),
|
||||
replayFixture: FIXTURE,
|
||||
replayProvidersOnly: true,
|
||||
replayProviders: [
|
||||
{ id: 'deepseek-account', name: 'DeepSeek Account', models: [
|
||||
listedModel('deepseek-flash', NAME, { text: ' 1017 ', tone: 'info' }),
|
||||
] },
|
||||
{ id: 'deepseek-official', name: 'DeepSeek', models: [
|
||||
listedModel('deepseek-flash', NAME, { text: { en: ' New ', zh: ' 新 ' }, tone: 'success' }),
|
||||
{ id: 'deepseek-v4-pro', name: 'DeepSeek-V4-Pro' },
|
||||
listedModel('long', LONG_NAME, { text: 'Preview', tone: 'future-tone' }),
|
||||
listedModel('invalid-badge', 'Invalid badge model', { text: { en: 'New', zh: '\u200b' } }),
|
||||
] },
|
||||
{ id: 'external', name: 'External', models: [{ id: 'deepseek-flash', name: NAME }] },
|
||||
],
|
||||
})
|
||||
browser = await chromium.launch()
|
||||
page = await browser.newPage({ viewport: { width: 1440, height: 900 }, locale: ZH_BROWSER_LOCALE })
|
||||
tripwire = watchConsole(page)
|
||||
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
|
||||
await connectFreshWorkspaceZh(page, scaffold.workspaceCwd)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
try { await browser?.close() }
|
||||
finally { await scaffold?.close() }
|
||||
})
|
||||
|
||||
it('shows each catalog badge in all placements while preserving the chosen provider and model id', async () => {
|
||||
const trigger = page.getByRole('button', { name: /^选择模型/ })
|
||||
await trigger.getByText('新', { exact: true }).waitFor()
|
||||
expect(await trigger.textContent()).toContain(`新 ${NAME}`)
|
||||
expect(await trigger.getAttribute('aria-label')).toBe(`选择模型,当前 ${NAME}`)
|
||||
expect(await trigger.getByText('新', { exact: true }).getAttribute('data-tone')).toBe('success')
|
||||
try {
|
||||
await trigger.click()
|
||||
const current = page.getByRole('menuitem', { name: /^模型/ })
|
||||
expect(await current.getByText('新', { exact: true }).count()).toBe(1)
|
||||
await current.click()
|
||||
const menu = page.getByRole('menu', { name: '模型', exact: true })
|
||||
await compareOrRefreshGolden(EXPECTED, await captureStableAria(page, '[role="menu"][aria-label="模型"]', scaffold.workspaceCwd), MODE)
|
||||
expect(await menu.getByRole('group', { name: 'External', exact: true }).locator('[data-tone]').count()).toBe(0)
|
||||
expect(await menu.getByRole('menuitemradio', { name: 'Invalid badge model', exact: true }).locator('[data-tone]').count()).toBe(0)
|
||||
expect(await menu.getByRole('menuitemradio', { name: `${LONG_NAME} Preview`, exact: true }).locator('[data-tone]').getAttribute('data-tone')).toBe('neutral')
|
||||
const account = menu.getByRole('group', { name: 'DeepSeek 账号', exact: true })
|
||||
expect(await account.getByText('1017', { exact: true }).getAttribute('data-tone')).toBe('info')
|
||||
await account.getByRole('menuitemradio', { name: `${NAME} 1017`, exact: true }).click()
|
||||
await expect.poll(() => scaffold.ctx.agentDefaultModel.currentSelection()).toEqual({ provider: 'deepseek-account', model: 'deepseek-flash' })
|
||||
await expect.poll(() => trigger.textContent()).toContain(`1017 ${NAME}`)
|
||||
const catalog = await scaffold.ctx.llm.listModels('deepseek-account')
|
||||
expect(catalog.map(model => ({ id: model.id, name: model.name }))).toEqual([{ id: 'deepseek-flash', name: NAME }])
|
||||
} finally {
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('Escape')
|
||||
}
|
||||
})
|
||||
|
||||
it('updates and clears badges in an already-open page and model menu', async () => {
|
||||
const provider = 'live-badge'
|
||||
class CatalogAdapter extends LlmAdapter {
|
||||
badge: LlmModelBadge | undefined = { text: 'New', tone: 'info' }
|
||||
override async listModels(route: string) {
|
||||
return [{ provider: route, id: 'live', name: 'Live model', ...(this.badge === undefined ? {} : { badge: this.badge }) }]
|
||||
}
|
||||
override async *stream(): AsyncIterable<StreamChunk> { throw new Error('catalog-only scenario') }
|
||||
}
|
||||
const adapter = new CatalogAdapter()
|
||||
const unregister = scaffold.ctx.llm.registerAdapter([provider], adapter)
|
||||
const trigger = page.getByRole('button', { name: /^选择模型/ })
|
||||
try {
|
||||
await trigger.click()
|
||||
await page.getByRole('menuitem', { name: /^模型/ }).click()
|
||||
await page.getByRole('menuitemradio', { name: 'Live model New', exact: true }).click()
|
||||
await expect.poll(() => trigger.textContent()).toContain('New Live model')
|
||||
await trigger.click()
|
||||
await page.getByRole('menuitem', { name: /^模型/ }).click()
|
||||
adapter.badge = { text: 'Updated', tone: 'success' }
|
||||
scaffold.ctx.emit('llm/models-updated', provider)
|
||||
await expect.poll(() => trigger.textContent()).toContain('Updated Live model')
|
||||
const updated = page.getByRole('menuitemradio', { name: 'Live model Updated', exact: true })
|
||||
await updated.waitFor()
|
||||
expect(await updated.locator('[data-tone]').getAttribute('data-tone')).toBe('success')
|
||||
adapter.badge = undefined
|
||||
scaffold.ctx.emit('llm/models-updated', provider)
|
||||
await expect.poll(() => trigger.textContent()).toBe('Live model')
|
||||
const cleared = page.getByRole('menuitemradio', { name: 'Live model', exact: true })
|
||||
await cleared.waitFor()
|
||||
expect(await cleared.locator('[data-tone]').count()).toBe(0)
|
||||
} finally {
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('Escape')
|
||||
await trigger.click()
|
||||
await page.getByRole('menuitem', { name: /^模型/ }).click()
|
||||
await page.getByRole('menuitemradio', { name: `${NAME} 1017`, exact: true }).click()
|
||||
unregister()
|
||||
}
|
||||
})
|
||||
|
||||
it('ellipsizes long names while keeping badges visible in every placement and both theme palettes', async () => {
|
||||
const trigger = page.getByRole('button', { name: /^选择模型/ })
|
||||
try {
|
||||
await trigger.click()
|
||||
await page.getByRole('menuitem', { name: /^模型/ }).click()
|
||||
await page.getByRole('menuitemradio', { name: `${LONG_NAME} Preview`, exact: true }).click()
|
||||
await expect.poll(() => trigger.textContent()).toBe(`Preview ${LONG_NAME}`)
|
||||
const triggerBadge = trigger.getByText('Preview', { exact: true })
|
||||
const triggerBox = (await trigger.boundingBox())!
|
||||
const triggerBadgeBox = (await triggerBadge.boundingBox())!
|
||||
expect(triggerBadgeBox.x).toBeGreaterThanOrEqual(triggerBox.x)
|
||||
expect(triggerBadgeBox.x + triggerBadgeBox.width).toBeLessThanOrEqual(triggerBox.x + triggerBox.width)
|
||||
const triggerName = trigger.getByText(LONG_NAME, { exact: true })
|
||||
expect(await triggerName.evaluate(node => node.scrollWidth > node.clientWidth && getComputedStyle(node).textOverflow === 'ellipsis')).toBe(true)
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 })
|
||||
await trigger.click()
|
||||
const current = page.getByRole('menuitem', { name: /^模型/ })
|
||||
const rootBadgeBox = (await current.getByText('Preview', { exact: true }).boundingBox())!
|
||||
const rootBox = (await current.boundingBox())!
|
||||
expect(rootBadgeBox.x).toBeGreaterThanOrEqual(rootBox.x)
|
||||
expect(rootBadgeBox.x + rootBadgeBox.width).toBeLessThanOrEqual(rootBox.x + rootBox.width)
|
||||
expect(await current.getByText(LONG_NAME, { exact: true }).evaluate(node => node.scrollWidth > node.clientWidth)).toBe(true)
|
||||
await current.click()
|
||||
const menu = page.getByRole('menu', { name: '模型', exact: true })
|
||||
const option = menu.getByRole('menuitemradio', { name: `${LONG_NAME} Preview`, exact: true })
|
||||
const badge = option.getByText('Preview', { exact: true })
|
||||
const box = await badge.boundingBox()
|
||||
expect(box).not.toBeNull()
|
||||
expect(box!.x).toBeGreaterThanOrEqual(12)
|
||||
expect(box!.x + box!.width).toBeLessThanOrEqual(378)
|
||||
expect(await option.getByText(LONG_NAME, { exact: true }).evaluate(node => node.scrollWidth > node.clientWidth)).toBe(true)
|
||||
const colors = await badge.evaluate((node) => {
|
||||
const body = node.ownerDocument.body
|
||||
const previous = body.getAttribute('data-ds-dark-theme')
|
||||
try {
|
||||
return [false, true].map((dark) => {
|
||||
body.toggleAttribute('data-ds-dark-theme', dark)
|
||||
const style = getComputedStyle(node)
|
||||
return { color: style.color, background: style.backgroundColor }
|
||||
})
|
||||
} finally {
|
||||
if (previous === null) body.removeAttribute('data-ds-dark-theme')
|
||||
else body.setAttribute('data-ds-dark-theme', previous)
|
||||
}
|
||||
})
|
||||
expect(colors[0]).not.toEqual(colors[1])
|
||||
for (const color of colors) expect(color.color).not.toBe(color.background)
|
||||
} finally {
|
||||
await page.keyboard.press('Escape')
|
||||
await page.keyboard.press('Escape')
|
||||
await page.setViewportSize({ width: 1440, height: 900 })
|
||||
}
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -127,7 +127,7 @@
|
||||
"tests/developer-tools-settings.e2e.ts",
|
||||
"tests/models-settings.e2e.ts",
|
||||
"tests/deepseek-messages-settings.e2e.ts",
|
||||
"tests/deepseek-model-label-1017.e2e.ts",
|
||||
"tests/model-badge.e2e.ts",
|
||||
"tests/deepseek-messages-chat.e2e.ts",
|
||||
"tests/models-settings-recovery.e2e.ts",
|
||||
"tests/default-model.e2e.ts",
|
||||
|
||||
@@ -2234,8 +2234,8 @@ export type PiAiThinkingTokenBudgetField = NonNullable<OpenAICompletionsCompat['
|
||||
## `@deepseek-ai/dsh-llm-replay`
|
||||
|
||||
- `inject`: `llm`
|
||||
- `refs`: [`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts) · [`SystemPromptUpdate`](../packages/llm/llm/src/index.ts) · [`ToolUpdate`](../packages/llm/llm/src/index.ts)
|
||||
- `source`: [`packages/test-support/llm-replay/src/index.ts:1129`](../packages/test-support/llm-replay/src/index.ts)
|
||||
- `refs`: [`LlmModelBadge`](../packages/llm/llm/src/index.ts) · [`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts) · [`SystemPromptUpdate`](../packages/llm/llm/src/index.ts) · [`ToolUpdate`](../packages/llm/llm/src/index.ts)
|
||||
- `source`: [`packages/test-support/llm-replay/src/index.ts:1135`](../packages/test-support/llm-replay/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Plugin config: the {@link ReplayConfig} inputs, each defaulting to its `DSH_SNAPSHOT_*` env var in `apply`. */
|
||||
@@ -2305,6 +2305,8 @@ export interface ReplayModelConfig {
|
||||
systemPromptUpdate?: SystemPromptUpdate
|
||||
/** Optional mid-conversation tool declaration mode for a keyless replay route. */
|
||||
toolUpdate?: ToolUpdate
|
||||
/** Optional selector badge the replay catalog advertises, so keyless Web scenarios render one. */
|
||||
badge?: LlmModelBadge
|
||||
}
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-llm-replay -->
|
||||
@@ -4833,7 +4835,6 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
|
||||
| `@deepseek-ai/dsh-client-ui-conversation` | — | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-cordis` | — | [`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-creation-recommendations` | — | [`packages/experimental/client-ui-creation-recommendations/src/index.ts`](../packages/experimental/client-ui-creation-recommendations/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` | — | [`packages/client/ui-deepseek-model-label-1017/src/index.ts`](../packages/client/ui-deepseek-model-label-1017/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-deliverables` | `connection` · `sessionQuery` · `sessionController` · `workspaceFiles` · `fs` · `sandboxPolicy` · `workspaceChanges` | [`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-directory-picker-browse` | — | [`packages/client/ui-directory-picker-browse/src/index.ts`](../packages/client/ui-directory-picker-browse/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-directory-picker-native` | — | [`packages/client/ui-directory-picker-native/src/index.ts`](../packages/client/ui-directory-picker-native/src/index.ts) |
|
||||
|
||||
@@ -2236,8 +2236,8 @@ export type PiAiThinkingTokenBudgetField = NonNullable<OpenAICompletionsCompat['
|
||||
## `@deepseek-ai/dsh-llm-replay`
|
||||
|
||||
- `inject`: `llm`
|
||||
- `refs`: [`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts) · [`SystemPromptUpdate`](../packages/llm/llm/src/index.ts) · [`ToolUpdate`](../packages/llm/llm/src/index.ts)
|
||||
- `source`: [`packages/test-support/llm-replay/src/index.ts:1129`](../packages/test-support/llm-replay/src/index.ts)
|
||||
- `refs`: [`LlmModelBadge`](../packages/llm/llm/src/index.ts) · [`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts) · [`SystemPromptUpdate`](../packages/llm/llm/src/index.ts) · [`ToolUpdate`](../packages/llm/llm/src/index.ts)
|
||||
- `source`: [`packages/test-support/llm-replay/src/index.ts:1135`](../packages/test-support/llm-replay/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Plugin config: the {@link ReplayConfig} inputs, each defaulting to its `DSH_SNAPSHOT_*` env var in `apply`. */
|
||||
@@ -2307,6 +2307,8 @@ export interface ReplayModelConfig {
|
||||
systemPromptUpdate?: SystemPromptUpdate
|
||||
/** Optional mid-conversation tool declaration mode for a keyless replay route. */
|
||||
toolUpdate?: ToolUpdate
|
||||
/** Optional selector badge the replay catalog advertises, so keyless Web scenarios render one. */
|
||||
badge?: LlmModelBadge
|
||||
}
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-llm-replay -->
|
||||
@@ -4835,7 +4837,6 @@ export interface Config {
|
||||
| `@deepseek-ai/dsh-client-ui-conversation` | — | [`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-cordis` | — | [`packages/extensions/ui-cordis/src/index.ts`](../packages/extensions/ui-cordis/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-creation-recommendations` | — | [`packages/experimental/client-ui-creation-recommendations/src/index.ts`](../packages/experimental/client-ui-creation-recommendations/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` | — | [`packages/client/ui-deepseek-model-label-1017/src/index.ts`](../packages/client/ui-deepseek-model-label-1017/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-deliverables` | `connection` · `sessionQuery` · `sessionController` · `workspaceFiles` · `fs` · `sandboxPolicy` · `workspaceChanges` | [`packages/client/ui-deliverables/src/index.ts`](../packages/client/ui-deliverables/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-directory-picker-browse` | — | [`packages/client/ui-directory-picker-browse/src/index.ts`](../packages/client/ui-directory-picker-browse/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-client-ui-directory-picker-native` | — | [`packages/client/ui-directory-picker-native/src/index.ts`](../packages/client/ui-directory-picker-native/src/index.ts) |
|
||||
|
||||
@@ -23,11 +23,11 @@ Application listeners are outside this package matrix. Desktop Host subscribes t
|
||||
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
|
||||
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` |
|
||||
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
|
||||
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:689`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:669`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:696`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:675`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:682`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:691`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:671`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:698`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:677`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:684`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:55`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
|
||||
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes`, `server` |
|
||||
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - |
|
||||
@@ -55,7 +55,8 @@ Application listeners are outside this package matrix. Desktop Host subscribes t
|
||||
| `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:114`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) |
|
||||
| `hmr/change` | `emit` | [`packages/boot/hmr/src/index.ts:35`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - |
|
||||
| `hmr/reload` | `emit` | [`packages/boot/hmr/src/index.ts:40`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - |
|
||||
| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `remotes` |
|
||||
| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:24`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `remotes` |
|
||||
| `llm/models-updated` | `emit` | [`packages/llm/llm/src/types.ts:30`](../packages/llm/llm/src/types.ts) | [`llm-deepseek`](../packages/llm/llm-deepseek) (`emit`) | `remotes` |
|
||||
| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) |
|
||||
| `permission-presets/catalog-changed` | `emit` | [`packages/interaction/permission-presets/src/types.ts:48`](../packages/interaction/permission-presets/src/types.ts) | [`permission-presets`](../packages/interaction/permission-presets) (`events.dispatch`) | `remotes` |
|
||||
| `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:427`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
|
||||
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` |
|
||||
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
|
||||
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:689`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:669`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:696`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:675`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:682`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:691`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:671`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:698`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:677`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:684`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:55`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
|
||||
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes`, `server` |
|
||||
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - |
|
||||
@@ -57,7 +57,8 @@
|
||||
| `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:114`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) |
|
||||
| `hmr/change` | `emit` | [`packages/boot/hmr/src/index.ts:35`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - |
|
||||
| `hmr/reload` | `emit` | [`packages/boot/hmr/src/index.ts:40`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - |
|
||||
| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `remotes` |
|
||||
| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:24`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `remotes` |
|
||||
| `llm/models-updated` | `emit` | [`packages/llm/llm/src/types.ts:30`](../packages/llm/llm/src/types.ts) | [`llm-deepseek`](../packages/llm/llm-deepseek) (`emit`) | `remotes` |
|
||||
| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) |
|
||||
| `permission-presets/catalog-changed` | `emit` | [`packages/interaction/permission-presets/src/types.ts:48`](../packages/interaction/permission-presets/src/types.ts) | [`permission-presets`](../packages/interaction/permission-presets) (`events.dispatch`) | `remotes` |
|
||||
| `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:427`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
|
||||
@@ -166,7 +166,6 @@ flowchart TD
|
||||
pkg_client_ui_chat["client-ui-chat"]
|
||||
pkg_client_ui_commands["client-ui-commands"]
|
||||
pkg_client_ui_conversation["client-ui-conversation"]
|
||||
pkg_client_ui_deepseek_model_label_1017["client-ui-deepseek-model-label-1017"]
|
||||
pkg_client_ui_deliverables["client-ui-deliverables"]
|
||||
pkg_client_ui_directory_picker_browse["client-ui-directory-picker-browse"]
|
||||
pkg_client_ui_directory_picker_native["client-ui-directory-picker-native"]
|
||||
@@ -1545,7 +1544,6 @@ flowchart TD
|
||||
| [`client-ui-chat`](../packages/client/ui-chat) | `client` | — |
|
||||
| [`client-ui-commands`](../packages/client/ui-commands) | `client` | — |
|
||||
| [`client-ui-conversation`](../packages/client/ui-conversation) | `client` | — |
|
||||
| [`client-ui-deepseek-model-label-1017`](../packages/client/ui-deepseek-model-label-1017) | `client` | — |
|
||||
| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | — |
|
||||
| [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse) | `client` | — |
|
||||
| [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native) | `client` | — |
|
||||
|
||||
@@ -168,7 +168,6 @@ flowchart TD
|
||||
pkg_client_ui_chat["client-ui-chat"]
|
||||
pkg_client_ui_commands["client-ui-commands"]
|
||||
pkg_client_ui_conversation["client-ui-conversation"]
|
||||
pkg_client_ui_deepseek_model_label_1017["client-ui-deepseek-model-label-1017"]
|
||||
pkg_client_ui_deliverables["client-ui-deliverables"]
|
||||
pkg_client_ui_directory_picker_browse["client-ui-directory-picker-browse"]
|
||||
pkg_client_ui_directory_picker_native["client-ui-directory-picker-native"]
|
||||
@@ -1547,7 +1546,6 @@ flowchart TD
|
||||
| [`client-ui-chat`](../packages/client/ui-chat) | `client` | — |
|
||||
| [`client-ui-commands`](../packages/client/ui-commands) | `client` | — |
|
||||
| [`client-ui-conversation`](../packages/client/ui-conversation) | `client` | — |
|
||||
| [`client-ui-deepseek-model-label-1017`](../packages/client/ui-deepseek-model-label-1017) | `client` | — |
|
||||
| [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | — |
|
||||
| [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse) | `client` | — |
|
||||
| [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native) | `client` | — |
|
||||
|
||||
@@ -555,8 +555,8 @@
|
||||
en: 028791baa24a6a0a
|
||||
zh: 028791baa24a6a0a
|
||||
/session-persistence-event-catalog/resolved-persistence-types/in-history:
|
||||
en: 3e9695c61a9e30c8
|
||||
zh: cdc765b18de2d665
|
||||
en: ad2f023eedbc2a7e
|
||||
zh: 2124b8dedfbf8383
|
||||
/session-persistence-event-catalog/resolved-persistence-types/in-progress:
|
||||
en: 61de2b0c9de8c15b
|
||||
zh: 61de2b0c9de8c15b
|
||||
@@ -990,11 +990,11 @@
|
||||
en: 87f2aaf2b23a5249
|
||||
zh: 3d6c5ed6603bee31
|
||||
/session-persistence-event-catalog/resolved-persistence-types/contentblock:
|
||||
en: d3eb9a17ab27856f
|
||||
zh: a71b16a1e36a36ac
|
||||
en: 920125405e3f3d40
|
||||
zh: f2b241fc44cd63be
|
||||
/session-persistence-event-catalog/resolved-persistence-types/contentblocktype:
|
||||
en: 27dc8b92bae26f1f
|
||||
zh: e05ace867fb59ae1
|
||||
en: d51d3737a6f1208f
|
||||
zh: 7109487fbfaee359
|
||||
/session-persistence-event-catalog/resolved-persistence-types/contentblock~2:
|
||||
en: 9207aa0489ccf8db
|
||||
zh: 850dd92da6769998
|
||||
@@ -1026,11 +1026,11 @@
|
||||
en: 0bf0caf488d14011
|
||||
zh: 734501852de79a21
|
||||
/session-persistence-event-catalog/resolved-persistence-types/fileblock:
|
||||
en: 4cab6c6ed2abedd8
|
||||
zh: a6cdd344e6c36c24
|
||||
en: cc6e6a199425dde9
|
||||
zh: 8a23127ba156d058
|
||||
/session-persistence-event-catalog/resolved-persistence-types/finishreason:
|
||||
en: 49fd9834247f3cc2
|
||||
zh: 4378ead90069d0d6
|
||||
en: 4a9e91f2673dfed0
|
||||
zh: 89d08b1d16191cfe
|
||||
/session-persistence-event-catalog/resolved-persistence-types/goalblockreason:
|
||||
en: b1589f9578a585dd
|
||||
zh: 3783dbbd8793bb37
|
||||
@@ -1065,8 +1065,8 @@
|
||||
en: 76ee1e266cd651da
|
||||
zh: 55c7c95a223ccbed
|
||||
/session-persistence-event-catalog/resolved-persistence-types/imageblock:
|
||||
en: ccafbcae37570ce2
|
||||
zh: fbf43a8c7ff26154
|
||||
en: 31b19d23dfee0571
|
||||
zh: 3ffa6c6d18cf73d5
|
||||
/session-persistence-event-catalog/resolved-persistence-types/imagemediatype:
|
||||
en: 32d86426192176d5
|
||||
zh: c86291efc19bdc91
|
||||
@@ -1116,8 +1116,8 @@
|
||||
en: 8f669d7819805099
|
||||
zh: 391d51d7835afe02
|
||||
/session-persistence-event-catalog/resolved-persistence-types/llmfailure:
|
||||
en: a18d2091f7e64726
|
||||
zh: 550fc25036bd096e
|
||||
en: 1c1591ce9b3cc186
|
||||
zh: 74ebe1b8c58e87d7
|
||||
/session-persistence-event-catalog/resolved-persistence-types/llmretryeventdata:
|
||||
en: 6dbe099c62f7c226
|
||||
zh: 6fd788f63c038c91
|
||||
@@ -1176,11 +1176,11 @@
|
||||
en: aca35dac4f9d2add
|
||||
zh: 6a4bdb95e3c7b0ce
|
||||
/session-persistence-event-catalog/resolved-persistence-types/reasoningblock:
|
||||
en: 07e04c3e336f880f
|
||||
zh: 6f8986ede9994fa1
|
||||
en: 8d2873c634bf6405
|
||||
zh: 91e92b5730462cf0
|
||||
/session-persistence-event-catalog/resolved-persistence-types/replayenvelope:
|
||||
en: 488050ba0b6438b4
|
||||
zh: 2bf607b5ad51e39d
|
||||
en: 34cbedee7bc95921
|
||||
zh: f7deeddb8d60152a
|
||||
/session-persistence-event-catalog/resolved-persistence-types/requestcontext:
|
||||
en: 544039047a28b6bb
|
||||
zh: c691f0bb1e23db08
|
||||
@@ -1227,8 +1227,8 @@
|
||||
en: ecc15ebadb77afdf
|
||||
zh: 97ac27d6a4175f6f
|
||||
/session-persistence-event-catalog/resolved-persistence-types/streamchunk:
|
||||
en: 6a9cc39c2df1b476
|
||||
zh: 697ea4d14d77f59c
|
||||
en: 76ef1d6de369c7b5
|
||||
zh: e033b6bbaa455e87
|
||||
/session-persistence-event-catalog/resolved-persistence-types/subagentcatalogevent:
|
||||
en: 725539dcd5c335ed
|
||||
zh: 5ca08d3667f84ff9
|
||||
@@ -1266,8 +1266,8 @@
|
||||
en: 9bff57038f472770
|
||||
zh: 4eec0e793ae16d53
|
||||
/session-persistence-event-catalog/resolved-persistence-types/textblock:
|
||||
en: c5fcd2cdd37ea387
|
||||
zh: 9c678fbdea0bf25c
|
||||
en: 7ad0f4915397b511
|
||||
zh: bcaa8bd449927989
|
||||
/session-persistence-event-catalog/resolved-persistence-types/todoitem:
|
||||
en: ee24f9407df530ac
|
||||
zh: 310bedf3ac730d77
|
||||
@@ -1275,20 +1275,20 @@
|
||||
en: 8bf023246cbd0887
|
||||
zh: dbf7113574c1bf59
|
||||
/session-persistence-event-catalog/resolved-persistence-types/tokenusage:
|
||||
en: 0aa983637b619157
|
||||
zh: e0f68258606aae78
|
||||
en: 09833c3cf66cc620
|
||||
zh: b7e9b3c138dae85b
|
||||
/session-persistence-event-catalog/resolved-persistence-types/tooladditionblock:
|
||||
en: 7ba7c600fc529015
|
||||
zh: 94b69e9d5bd1fcfd
|
||||
en: bbce95b556fe670f
|
||||
zh: a1f1cae553a356ab
|
||||
/session-persistence-event-catalog/resolved-persistence-types/toolcallblock:
|
||||
en: 78482b09dc227306
|
||||
zh: fb5852ac93beb20e
|
||||
en: d7478804801b52a4
|
||||
zh: 27287739a5874fdd
|
||||
/session-persistence-event-catalog/resolved-persistence-types/toolmessagesource:
|
||||
en: 036b2595215410bb
|
||||
zh: 41792808ebac1fa3
|
||||
/session-persistence-event-catalog/resolved-persistence-types/toolremovalblock:
|
||||
en: fdac0df45529e14c
|
||||
zh: c5e38de83a0e0a42
|
||||
en: 0080c345c319a4b5
|
||||
zh: de1a8b3ed621ce21
|
||||
/session-persistence-event-catalog/resolved-persistence-types/toolrestriction:
|
||||
en: d70be5d46cb1ef7d
|
||||
zh: d3d5a05d62e443fe
|
||||
@@ -1296,8 +1296,8 @@
|
||||
en: 2258683c3cfa4ce3
|
||||
zh: 5663aa76c911761b
|
||||
/session-persistence-event-catalog/resolved-persistence-types/toolschema:
|
||||
en: 689c56f199c0035d
|
||||
zh: 48bcb9d508d1892c
|
||||
en: afa1e3fd9ccdd95b
|
||||
zh: 7af667e3ebac25e8
|
||||
/session-persistence-event-catalog/resolved-persistence-types/toolschema~2:
|
||||
en: 12c14727bb7834e2
|
||||
zh: b0f3c877873c36db
|
||||
@@ -1350,8 +1350,8 @@
|
||||
en: 2786fef560851bb4
|
||||
zh: 5d787475356b2606
|
||||
/session-persistence-event-catalog/resolved-persistence-types/string:
|
||||
en: 1e12d380c4988d40
|
||||
zh: 14a781104e00f360
|
||||
en: 1e484bd12747db9f
|
||||
zh: 1e6fc6aa9066a702
|
||||
/session-persistence-event-catalog/resolved-persistence-types/string~2:
|
||||
en: 0089620bf4638263
|
||||
zh: 1922bef1713094bb
|
||||
@@ -1497,11 +1497,11 @@
|
||||
en: d019b2909673d2a7
|
||||
zh: cc16e9c055ea603d
|
||||
/session-persistence-event-catalog/resolved-persistence-types/failure-kind-aborted:
|
||||
en: 349cd90f2598fd12
|
||||
zh: 1eda02d520ad5d76
|
||||
en: 7dddcbc923491531
|
||||
zh: bbdaea7d433b6fda
|
||||
/session-persistence-event-catalog/resolved-persistence-types/failure-kind-error:
|
||||
en: 51af619ed8532a43
|
||||
zh: a5ad878c63d0e046
|
||||
en: 4cb1c48095df98dc
|
||||
zh: 5a03e44761af559e
|
||||
/session-persistence-event-catalog/resolved-persistence-types/header-reason-startsseries:
|
||||
en: 27f5b76003ac1ead
|
||||
zh: 2a5a0490cc22512b
|
||||
@@ -1647,8 +1647,8 @@
|
||||
en: 673110c18ee48238
|
||||
zh: e8dd81d68e1a4ee0
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-max-tokens:
|
||||
en: 337ebe760096729a
|
||||
zh: bd35a401103a3071
|
||||
en: 7092c1681af1dacb
|
||||
zh: a0c5505f839592ed
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-model-selection:
|
||||
en: a5165486dca91116
|
||||
zh: b7051ca8167806ef
|
||||
@@ -1764,8 +1764,8 @@
|
||||
en: c99d8a67438771ff
|
||||
zh: 070f6160fc419fa5
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-stop:
|
||||
en: 09ce6f3196938ccf
|
||||
zh: 1b8d5540f47fd3aa
|
||||
en: a54b282e864ff035
|
||||
zh: 973e729590675d85
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-time-context:
|
||||
en: 5733addfc8b12a47
|
||||
zh: aaf60d5396e30971
|
||||
@@ -1809,8 +1809,8 @@
|
||||
en: b05115b92a176f3a
|
||||
zh: 22ba93572bd0209a
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-tool-calls:
|
||||
en: 65fb50e399cddd05
|
||||
zh: 9e713bbcb1b7db35
|
||||
en: b9203133bb7e6d5a
|
||||
zh: b3f32a61983bb2e4
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-tool-goal:
|
||||
en: e11ea3169004ae33
|
||||
zh: 7d6a3a77cb9bf205
|
||||
@@ -1857,8 +1857,8 @@
|
||||
en: 5a0bbf4c76266ac5
|
||||
zh: 0b20854f90f9d74d
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-user:
|
||||
en: e98b716228feb157
|
||||
zh: 2bb593ae9cd0ce0d
|
||||
en: 3d3ae20cba45bae8
|
||||
zh: 04ad200556cd8ced
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-user-approval:
|
||||
en: a52d2dfbaf844127
|
||||
zh: 4c5306ca40f20873
|
||||
@@ -1965,11 +1965,11 @@
|
||||
en: bb507524bcc39c53
|
||||
zh: 8314898562437473
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-block-end:
|
||||
en: f23d45580ba6d17b
|
||||
zh: 4bdf4fc300a5b287
|
||||
en: 7513d6a33f4ad113
|
||||
zh: 652201059a3de858
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-block-start:
|
||||
en: ec1f0e5653a604db
|
||||
zh: f905b8ce4d3730be
|
||||
en: 56230efa41e79869
|
||||
zh: 9ca4b8990a28d4d9
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-chunk:
|
||||
en: 2186ede3e9917aea
|
||||
zh: 0cfef64045dce95b
|
||||
@@ -2010,8 +2010,8 @@
|
||||
en: 14e4b48d856bcf57
|
||||
zh: 72ba176535dc7f3f
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-finish:
|
||||
en: 76f708f530d4fc9a
|
||||
zh: b6f1100c734a5991
|
||||
en: 30f8f49d0755dc2d
|
||||
zh: d8553c9590a0e9bf
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-goal-change:
|
||||
en: aad2516b62d3ee83
|
||||
zh: e7204c4dc4e8e408
|
||||
@@ -2049,8 +2049,8 @@
|
||||
en: 34e5591d75322649
|
||||
zh: a216839f305c9796
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-reasoning-delta:
|
||||
en: d1bf7fbf222b717a
|
||||
zh: e88811c2e0d45fb6
|
||||
en: a3f08ffda3526201
|
||||
zh: e599517be130ff99
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-request-context:
|
||||
en: f8addb1a01da9ca1
|
||||
zh: 4ef11ebddd302c05
|
||||
@@ -2109,8 +2109,8 @@
|
||||
en: 1484738718de5312
|
||||
zh: 3e6fbe9c64068225
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-text-delta:
|
||||
en: 38a0279d2ba3a230
|
||||
zh: 42d7be8102d27995
|
||||
en: 5f30a603fab07135
|
||||
zh: 3b7e2076351a1473
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-todo-write:
|
||||
en: 2fa7ba77598a95b4
|
||||
zh: 50972b51d77e9155
|
||||
@@ -2118,8 +2118,8 @@
|
||||
en: 32f732b97739e4d2
|
||||
zh: f288e48e356969c0
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-tool-call-delta:
|
||||
en: 5822c230b4880a6e
|
||||
zh: e0b86bdee2834b24
|
||||
en: 0e907728f330a8c7
|
||||
zh: d9d1400d4c468ab2
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-tool-workflow-agent-end:
|
||||
en: 8eb5e1eb95722bc7
|
||||
zh: 8dd5980029600648
|
||||
@@ -2151,8 +2151,8 @@
|
||||
en: 1b045de96172be4b
|
||||
zh: 8a15c6f35b576b37
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-usage:
|
||||
en: 4ed36e4498f5f203
|
||||
zh: 7b3fcddec14c306b
|
||||
en: e8b2eedeb83ac7fb
|
||||
zh: 036143c1ff567cf5
|
||||
/session-persistence-event-catalog/resolved-persistence-types/type-user-message:
|
||||
en: 5b8341a673284ec1
|
||||
zh: e3264a827babffeb
|
||||
|
||||
+30
-30
@@ -2007,7 +2007,7 @@ SHA-256: `ebb768d85ec87fb68e42fc14fd7d8535b5ac11791b14d04c6770180f0a719bf8`
|
||||
|
||||
SHA-256: `f311671e07bc7fa645a0b6597de9966b97147956565296ba76a36543801b66c5`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:414`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:440`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
`"in-history"`
|
||||
|
||||
@@ -3266,7 +3266,7 @@ Sources: [`packages/compaction/compaction/src/checkpoint.ts:22`](../packages/com
|
||||
|
||||
SHA-256: `8064c03c87c357fd1575cb9395ce86e6dcbe2d275bf7717864a02cddf01827d5`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:150`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:157`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
One of:
|
||||
|
||||
@@ -3288,7 +3288,7 @@ One of:
|
||||
|
||||
SHA-256: `7d65acf0a3a43a8f445ce5d3320b42eca492739fc931b2a7516bfb8368dc2c68`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:148`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:155`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
One of:
|
||||
|
||||
@@ -3481,7 +3481,7 @@ Sources: [`packages/attachment/attachment/src/types.ts:39`](../packages/attachme
|
||||
|
||||
SHA-256: `f2da0f27c1e6add59982c37b15e4a4912c5b33466859cce3b13d503600e24aef`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:98`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:105`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -3498,7 +3498,7 @@ Sources: [`packages/llm/llm/src/types.ts:98`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
SHA-256: `5808e36c95355f0e745201b5db1b6bcec4ebf5adb75d3e82d7d59cc89635ab80`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:165`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:172`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| kind | Form property | Other required fields | Full definition |
|
||||
|---|---|---|---|
|
||||
@@ -3736,7 +3736,7 @@ Sources: [`packages/attachment/attachment/src/types.ts:11`](../packages/attachme
|
||||
|
||||
SHA-256: `12d5808fac1996b9b3c2afa806e03022252ba2d7b88f835862ae1896591f60c7`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:79`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:86`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -4044,7 +4044,7 @@ Sources: [`packages/llm/llm/src/call-config.ts:54`](../packages/llm/llm/src/call
|
||||
|
||||
SHA-256: `5f2117d1856a5ab43c28371faa978ebfaa48f8c7d734ea3fc94842210df83dca`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:41`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:48`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -4511,7 +4511,7 @@ Sources: [`packages/core/tool-executor-ptc/src/types.ts:12`](../packages/core/to
|
||||
|
||||
SHA-256: `814d9434e1ddf9078612b3b34322f26e9363b469213335007f7e9ba7fee9464e`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:68`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:75`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -4528,7 +4528,7 @@ Sources: [`packages/llm/llm/src/types.ts:68`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
SHA-256: `0acd94fe8794574c9ae72c7855ee16d29c546096d4af760d16d1225385764555`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:448`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:474`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -4824,7 +4824,7 @@ Sources: [`packages/skill/skill/src/index.ts:146`](../packages/skill/skill/src/i
|
||||
|
||||
SHA-256: `47e9095591ae36e7c39ee62553097772f2bfa16304536a72dbbc3f691f605180`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:470`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:496`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
One of:
|
||||
|
||||
@@ -5077,7 +5077,7 @@ One of:
|
||||
|
||||
SHA-256: `4e887768586528565381dadbddee6cef555874148089f4579e0b4b1ad096fc9c`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:62`](../packages/llm/llm/src/types.ts) · [`packages/web/web-search-deepseek/src/provider.ts:66`](../packages/web/web-search-deepseek/src/provider.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:69`](../packages/llm/llm/src/types.ts) · [`packages/web/web-search-deepseek/src/provider.ts:66`](../packages/web/web-search-deepseek/src/provider.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -5119,7 +5119,7 @@ Array of [`TodoItem`](#persistence-type-sha256-a99f76fd149051c363f960bfeeb5b9509
|
||||
|
||||
SHA-256: `fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:175`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:182`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -5140,7 +5140,7 @@ Sources: [`packages/llm/llm/src/types.ts:175`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
SHA-256: `8e611bf46821e5003a1605a73560a6f56388295149d449dfe2142d5ec335f29c`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:115`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:122`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -5158,7 +5158,7 @@ Sources: [`packages/llm/llm/src/types.ts:115`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
SHA-256: `c1f0f17b6493b85db6453bb30e01b208d703731b25bc1e68e5d4fc72eea9cdea`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:105`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:112`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -5194,7 +5194,7 @@ Sources: [`packages/llm/llm/src/message.ts:29`](../packages/llm/llm/src/message.
|
||||
|
||||
SHA-256: `43e311235aa537df1bacdd92d144df8a952337a76e9ac892cdf670f1a797bad8`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:127`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:134`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -5249,7 +5249,7 @@ Sources: [`packages/llm/llm/src/message.ts:173`](../packages/llm/llm/src/message
|
||||
|
||||
SHA-256: `66ba70f6f068da443f969a0b947a013c3addd776b9b60a0a393759c19825e9a2`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:491`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:517`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -5601,7 +5601,7 @@ Array of `number`.
|
||||
|
||||
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
|
||||
|
||||
Sources: [`packages/api/session-controller/src/types.ts:386`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
Sources: [`packages/api/session-controller/src/types.ts:388`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
|
||||
`string`
|
||||
|
||||
@@ -6260,7 +6260,7 @@ Sources: [`packages/core/session/src/types.ts:383`](../packages/core/session/src
|
||||
|
||||
SHA-256: `8e31bb958e9b015e6f8407eaade32217068159e816f3cc99c3ad76eb889732e0`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:160`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:167`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -6273,7 +6273,7 @@ Sources: [`packages/llm/llm/src/types.ts:160`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
SHA-256: `693f4952fb5565da96c06bace8924b098ac2fbc915fde0ff8019701183abc057`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:161`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:168`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -6869,7 +6869,7 @@ Sources: [`packages/core/session/src/types.ts:201`](../packages/core/session/src
|
||||
|
||||
SHA-256: `ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7`
|
||||
|
||||
Sources: [`packages/core/session/src/types.ts:220`](../packages/core/session/src/types.ts) · [`packages/llm/llm/src/types.ts:159`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/core/session/src/types.ts:220`](../packages/core/session/src/types.ts) · [`packages/llm/llm/src/types.ts:166`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -7309,7 +7309,7 @@ SHA-256: `28936c614bd4e7e2774806edcbb3cbfd3ee643ac1cbf0bbdff43d915505a8fe3`
|
||||
|
||||
SHA-256: `1d76406cbb2ddadb0dbbb51e9d8bc6cf678fed455cf8eb512f36f7c86d37a62f`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:157`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:164`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -7477,7 +7477,7 @@ SHA-256: `49dab019d00e8c26f27ad2ccff7b604d69e323ae4d17e0d2a7b6b8a830b1ade2`
|
||||
|
||||
SHA-256: `287211846beea1eccb5f9acee00d20388a38fcb2e95858fa2332d6f58529379a`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:158`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:165`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -7657,7 +7657,7 @@ Sources: [`packages/core/tools/src/index.ts:28`](../packages/core/tools/src/inde
|
||||
|
||||
SHA-256: `6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def`
|
||||
|
||||
Sources: [`packages/api/session-controller/src/types.ts:395`](../packages/api/session-controller/src/types.ts)
|
||||
Sources: [`packages/api/session-controller/src/types.ts:397`](../packages/api/session-controller/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -8149,7 +8149,7 @@ SHA-256: `1033093edd0db80ff410e00830b523405e00bb0c7684948e531ff65095799625`
|
||||
|
||||
SHA-256: `d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:475`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:501`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -8163,7 +8163,7 @@ Sources: [`packages/llm/llm/src/types.ts:475`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
SHA-256: `3bec87f9e6ab3da03346f49923d3c51621debc0159df68605e543c3b2a026f90`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:471`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:497`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -8381,7 +8381,7 @@ SHA-256: `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8`
|
||||
|
||||
SHA-256: `a2b49331716ba0a7078b026d145306a70eae64b7313c3e6204bd931e75ec3bb1`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:477`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:503`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -8587,7 +8587,7 @@ Sources: [`packages/llm/llm/src/assistant-stream.ts:28`](../packages/llm/llm/src
|
||||
|
||||
SHA-256: `2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:473`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:499`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -8907,7 +8907,7 @@ Sources: [`packages/llm/llm/src/assistant-stream.ts:21`](../packages/llm/llm/src
|
||||
|
||||
SHA-256: `95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:472`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:498`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -8955,7 +8955,7 @@ Sources: [`packages/llm/llm/src/assistant-stream.ts:35`](../packages/llm/llm/src
|
||||
|
||||
SHA-256: `88a610b831be6d92612a464be5c7d0b88ec24d97bd299837bf74bf7672183c9a`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:474`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:500`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -9133,7 +9133,7 @@ SHA-256: `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8`
|
||||
|
||||
SHA-256: `f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515`
|
||||
|
||||
Sources: [`packages/llm/llm/src/types.ts:476`](../packages/llm/llm/src/types.ts)
|
||||
Sources: [`packages/llm/llm/src/types.ts:502`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
|
||||
@@ -2009,7 +2009,7 @@ SHA-256: `ebb768d85ec87fb68e42fc14fd7d8535b5ac11791b14d04c6770180f0a719bf8`
|
||||
|
||||
SHA-256: `f311671e07bc7fa645a0b6597de9966b97147956565296ba76a36543801b66c5`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:414`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:440`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
`"in-history"`
|
||||
|
||||
@@ -3268,7 +3268,7 @@ SHA-256: `31907de9563751822af6d9be02bafaf26faa098bbd1cb412524134d577cc1648`
|
||||
|
||||
SHA-256: `8064c03c87c357fd1575cb9395ce86e6dcbe2d275bf7717864a02cddf01827d5`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:150`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:157`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
以下类型之一:
|
||||
|
||||
@@ -3290,7 +3290,7 @@ SHA-256: `8064c03c87c357fd1575cb9395ce86e6dcbe2d275bf7717864a02cddf01827d5`
|
||||
|
||||
SHA-256: `7d65acf0a3a43a8f445ce5d3320b42eca492739fc931b2a7516bfb8368dc2c68`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:148`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:155`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
以下类型之一:
|
||||
|
||||
@@ -3483,7 +3483,7 @@ SHA-256: `f19bee84bf3d20904553cdcd57465232bc1c2a194f54c4d195292ba0487223a6`
|
||||
|
||||
SHA-256: `f2da0f27c1e6add59982c37b15e4a4912c5b33466859cce3b13d503600e24aef`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:98`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:105`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -3500,7 +3500,7 @@ SHA-256: `f2da0f27c1e6add59982c37b15e4a4912c5b33466859cce3b13d503600e24aef`
|
||||
|
||||
SHA-256: `5808e36c95355f0e745201b5db1b6bcec4ebf5adb75d3e82d7d59cc89635ab80`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:165`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:172`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| kind | form 属性 | 其他必需字段 | 完整定义 |
|
||||
|---|---|---|---|
|
||||
@@ -3738,7 +3738,7 @@ SHA-256: `b0b0eb1b16490f6bee135b2377e8f8c39227974c799ad7d49ff418af23ecbc35`
|
||||
|
||||
SHA-256: `12d5808fac1996b9b3c2afa806e03022252ba2d7b88f835862ae1896591f60c7`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:79`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:86`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -4046,7 +4046,7 @@ SHA-256: `326a9bfda27dda5882fb3cbe6e715df41dae2298fdae3d66437457268a665693`
|
||||
|
||||
SHA-256: `5f2117d1856a5ab43c28371faa978ebfaa48f8c7d734ea3fc94842210df83dca`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:41`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:48`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -4513,7 +4513,7 @@ SHA-256: `2fe5026daf14bd4ff2b7bfb7cf9791c302d9cbba06802178b6dfec00b892698f`
|
||||
|
||||
SHA-256: `814d9434e1ddf9078612b3b34322f26e9363b469213335007f7e9ba7fee9464e`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:68`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:75`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -4530,7 +4530,7 @@ SHA-256: `814d9434e1ddf9078612b3b34322f26e9363b469213335007f7e9ba7fee9464e`
|
||||
|
||||
SHA-256: `0acd94fe8794574c9ae72c7855ee16d29c546096d4af760d16d1225385764555`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:448`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:474`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -4826,7 +4826,7 @@ SHA-256: `672c4aa2a9b736e3d6e5da6346d2e2ed5e3573f2d0baafa18e7407c9f3fee3db`
|
||||
|
||||
SHA-256: `47e9095591ae36e7c39ee62553097772f2bfa16304536a72dbbc3f691f605180`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:470`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:496`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
以下类型之一:
|
||||
|
||||
@@ -5079,7 +5079,7 @@ SHA-256: `b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943`
|
||||
|
||||
SHA-256: `4e887768586528565381dadbddee6cef555874148089f4579e0b4b1ad096fc9c`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:62`](../packages/llm/llm/src/types.ts) · [`packages/web/web-search-deepseek/src/provider.ts:66`](../packages/web/web-search-deepseek/src/provider.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:69`](../packages/llm/llm/src/types.ts) · [`packages/web/web-search-deepseek/src/provider.ts:66`](../packages/web/web-search-deepseek/src/provider.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -5121,7 +5121,7 @@ SHA-256: `4b7ddaf3710b88ff9b8df66304f5f97bf03688fd19730eedc2e9535203c820ea`
|
||||
|
||||
SHA-256: `fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:175`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:182`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -5142,7 +5142,7 @@ SHA-256: `fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394`
|
||||
|
||||
SHA-256: `8e611bf46821e5003a1605a73560a6f56388295149d449dfe2142d5ec335f29c`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:115`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:122`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -5160,7 +5160,7 @@ SHA-256: `8e611bf46821e5003a1605a73560a6f56388295149d449dfe2142d5ec335f29c`
|
||||
|
||||
SHA-256: `c1f0f17b6493b85db6453bb30e01b208d703731b25bc1e68e5d4fc72eea9cdea`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:105`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:112`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -5196,7 +5196,7 @@ SHA-256: `d9c462b9f62fdd3efc76a44b9524dc8781f6b91cd9c7e584b17d971defafebab`
|
||||
|
||||
SHA-256: `43e311235aa537df1bacdd92d144df8a952337a76e9ac892cdf670f1a797bad8`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:127`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:134`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -5251,7 +5251,7 @@ SHA-256: `4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731`
|
||||
|
||||
SHA-256: `66ba70f6f068da443f969a0b947a013c3addd776b9b60a0a393759c19825e9a2`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:491`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:517`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -5603,7 +5603,7 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78`
|
||||
|
||||
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
|
||||
|
||||
来源:[`packages/api/session-controller/src/types.ts:386`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
来源:[`packages/api/session-controller/src/types.ts:388`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
|
||||
`string`
|
||||
|
||||
@@ -6262,7 +6262,7 @@ SHA-256: `5302e763151277614b97e77f00e3b326694c6c1247b916a20503c344dc970777`
|
||||
|
||||
SHA-256: `8e31bb958e9b015e6f8407eaade32217068159e816f3cc99c3ad76eb889732e0`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:160`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:167`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -6275,7 +6275,7 @@ SHA-256: `8e31bb958e9b015e6f8407eaade32217068159e816f3cc99c3ad76eb889732e0`
|
||||
|
||||
SHA-256: `693f4952fb5565da96c06bace8924b098ac2fbc915fde0ff8019701183abc057`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:161`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:168`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -6871,7 +6871,7 @@ SHA-256: `eaa71be0fbf0f702df8ea6c11d0aa2f9782263d45a10d0fb14eda662f4bfe04c`
|
||||
|
||||
SHA-256: `ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7`
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:220`](../packages/core/session/src/types.ts) · [`packages/llm/llm/src/types.ts:159`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:220`](../packages/core/session/src/types.ts) · [`packages/llm/llm/src/types.ts:166`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -7311,7 +7311,7 @@ SHA-256: `28936c614bd4e7e2774806edcbb3cbfd3ee643ac1cbf0bbdff43d915505a8fe3`
|
||||
|
||||
SHA-256: `1d76406cbb2ddadb0dbbb51e9d8bc6cf678fed455cf8eb512f36f7c86d37a62f`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:157`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:164`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -7479,7 +7479,7 @@ SHA-256: `49dab019d00e8c26f27ad2ccff7b604d69e323ae4d17e0d2a7b6b8a830b1ade2`
|
||||
|
||||
SHA-256: `287211846beea1eccb5f9acee00d20388a38fcb2e95858fa2332d6f58529379a`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:158`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:165`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -7659,7 +7659,7 @@ SHA-256: `8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543`
|
||||
|
||||
SHA-256: `6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def`
|
||||
|
||||
来源:[`packages/api/session-controller/src/types.ts:395`](../packages/api/session-controller/src/types.ts)
|
||||
来源:[`packages/api/session-controller/src/types.ts:397`](../packages/api/session-controller/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -8151,7 +8151,7 @@ SHA-256: `1033093edd0db80ff410e00830b523405e00bb0c7684948e531ff65095799625`
|
||||
|
||||
SHA-256: `d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:475`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:501`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -8165,7 +8165,7 @@ SHA-256: `d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d`
|
||||
|
||||
SHA-256: `3bec87f9e6ab3da03346f49923d3c51621debc0159df68605e543c3b2a026f90`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:471`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:497`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -8383,7 +8383,7 @@ SHA-256: `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8`
|
||||
|
||||
SHA-256: `a2b49331716ba0a7078b026d145306a70eae64b7313c3e6204bd931e75ec3bb1`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:477`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:503`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -8589,7 +8589,7 @@ SHA-256: `71bb3254ec8f1e491ce63b762a7b5f18709de23aa4e8abcc8b886f4b6ce051dd`
|
||||
|
||||
SHA-256: `2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:473`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:499`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -8909,7 +8909,7 @@ SHA-256: `e72269f931559e901e5e8fe1cfcbd0669fb795f43cc038ff39fba62f60403c33`
|
||||
|
||||
SHA-256: `95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:472`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:498`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -8957,7 +8957,7 @@ SHA-256: `3860b4757d512a2869be833acb0faf834335b1b056bf3f83df5474dce95f8fa6`
|
||||
|
||||
SHA-256: `88a610b831be6d92612a464be5c7d0b88ec24d97bd299837bf74bf7672183c9a`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:474`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:500`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -9135,7 +9135,7 @@ SHA-256: `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8`
|
||||
|
||||
SHA-256: `f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515`
|
||||
|
||||
来源:[`packages/llm/llm/src/types.ts:476`](../packages/llm/llm/src/types.ts)
|
||||
来源:[`packages/llm/llm/src/types.ts:502`](../packages/llm/llm/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
|
||||
Generated
+30
-30
@@ -85,7 +85,7 @@
|
||||
{"digest":"0a30c3be336cdd4f8f34e66543a3d46d611c43986edd9b3ce6cbb9b721d3b5d3","names":[],"sources":["packages/core/session/src/types.ts:209"]},
|
||||
{"digest":"0a8a062bc9c3c0d97fd97742262f2ea53dd32297f834e81114870d38ac3dc6da","names":[],"sources":[]},
|
||||
{"digest":"0aa62ab51b36c0e64b134bd03ba8725d758744180a1a447da49beaad194f219f","names":[],"sources":[]},
|
||||
{"digest":"0acd94fe8794574c9ae72c7855ee16d29c546096d4af760d16d1225385764555","names":["packages/llm/llm/src/types.ts#ReplayEnvelope"],"sources":["packages/llm/llm/src/types.ts:448"]},
|
||||
{"digest":"0acd94fe8794574c9ae72c7855ee16d29c546096d4af760d16d1225385764555","names":["packages/llm/llm/src/types.ts#ReplayEnvelope"],"sources":["packages/llm/llm/src/types.ts:474"]},
|
||||
{"digest":"0af3ecfb8c60d416fc40a7cc4fb2c4851a8e46608b2b38ab9e510670c971aa1f","names":[],"sources":[]},
|
||||
{"digest":"0b6d718c693fdf7f06dcccb0bcbbabe78cc63514eafc62d6b2a3899735279d6f","names":["packages/goal/goal/src/types.ts#GoalRef"],"sources":["packages/goal/goal/src/types.ts:20"]},
|
||||
{"digest":"0b9b8facbe2c19773b3fb0ecc824ccbcc603d41b8f18069faa3807d449572fce","names":[],"sources":[]},
|
||||
@@ -108,7 +108,7 @@
|
||||
{"digest":"11f70b595aa76897fc4e25a352bfa718c659f44b799e5aa14e84e5a38fe72912","names":[],"sources":["packages/interaction/user-approval/src/index.ts:41"]},
|
||||
{"digest":"124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3","names":[],"sources":[]},
|
||||
{"digest":"12a0cfa619ac4a5fc6c3e8e359c8dae15769edc61497a575935c78d5842f8e42","names":[],"sources":[]},
|
||||
{"digest":"12d5808fac1996b9b3c2afa806e03022252ba2d7b88f835862ae1896591f60c7","names":["packages/llm/llm/src/types.ts#ImageBlock"],"sources":["packages/llm/llm/src/types.ts:79"]},
|
||||
{"digest":"12d5808fac1996b9b3c2afa806e03022252ba2d7b88f835862ae1896591f60c7","names":["packages/llm/llm/src/types.ts#ImageBlock"],"sources":["packages/llm/llm/src/types.ts:86"]},
|
||||
{"digest":"12f9a5a7506bca46d6ef8e3c71826c4bf40e1389bfe0236cfffaea0c3fb367d2","names":[],"sources":["packages/core/session/src/types.ts:317"]},
|
||||
{"digest":"1379f0b72275994d20f1ddca2d60a96ab16ec80397ca9f1af21bdc67ba5c269c","names":[],"sources":[]},
|
||||
{"digest":"13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176","names":[],"sources":[]},
|
||||
@@ -139,7 +139,7 @@
|
||||
{"digest":"1ccb56061942f5ccb0d30c9e4f2883b9d115ff906560029de637b61d3cde896d","names":[],"sources":["packages/core/session/src/types.ts:349"]},
|
||||
{"digest":"1cde1bedde79c5e8a56aee9c05c8e0a579cf5bc611deb85b93fa331a16ca978f","names":["packages/compaction/compaction-image-offload/src/projection.ts#ImageOffloadTarget"],"sources":["packages/compaction/compaction-image-offload/src/projection.ts:9"]},
|
||||
{"digest":"1d5f04a03ea75fc8eeece544129eba0f49f0c82046e9c404461920390135c7b2","names":[],"sources":[]},
|
||||
{"digest":"1d76406cbb2ddadb0dbbb51e9d8bc6cf678fed455cf8eb512f36f7c86d37a62f","names":[],"sources":["packages/llm/llm/src/types.ts:157"]},
|
||||
{"digest":"1d76406cbb2ddadb0dbbb51e9d8bc6cf678fed455cf8eb512f36f7c86d37a62f","names":[],"sources":["packages/llm/llm/src/types.ts:164"]},
|
||||
{"digest":"1d8798b1a5e8c545ee4dcd2b4e75c0b4c9ffd985e4744c6d1a1aab992f859721","names":[],"sources":[]},
|
||||
{"digest":"1d9cb3caa96100b18b1911fa3ff8c751ef6992971a03c5f4d9aefc1a0004a345","names":[],"sources":[]},
|
||||
{"digest":"1df34a6c086866ffdc2dd9e8e5c581c3751a010cdf6b8538a7327f7a3237f0b4","names":["packages/goal/goal/src/domain.ts#GoalSnapshotChangeMeta"],"sources":["packages/goal/goal/src/domain.ts:24"]},
|
||||
@@ -169,13 +169,13 @@
|
||||
{"digest":"26c879bdd130e8b8683a6ce588414fad2244783d6573ca399fa0de184a2b0bb1","names":[],"sources":[]},
|
||||
{"digest":"2765e9119edf5816ac1ab4c95c5ee32c90403f3742962eff11544f7752d59d2e","names":[],"sources":[]},
|
||||
{"digest":"277b61f9c6f8170425fa6ffe06cfa14376757a896a4749cb82a687d7364c4fc9","names":[],"sources":[]},
|
||||
{"digest":"287211846beea1eccb5f9acee00d20388a38fcb2e95858fa2332d6f58529379a","names":[],"sources":["packages/llm/llm/src/types.ts:158"]},
|
||||
{"digest":"287211846beea1eccb5f9acee00d20388a38fcb2e95858fa2332d6f58529379a","names":[],"sources":["packages/llm/llm/src/types.ts:165"]},
|
||||
{"digest":"28936c614bd4e7e2774806edcbb3cbfd3ee643ac1cbf0bbdff43d915505a8fe3","names":[],"sources":[]},
|
||||
{"digest":"290f32bfd81ff85a8095886a8066fae06e885942b63a8bcd6a8d66d328445808","names":[],"sources":[]},
|
||||
{"digest":"297fbd8067d7815adae02b89790ec6ff5fb7c2cb0d0eb026e1746c24ec857761","names":[],"sources":[]},
|
||||
{"digest":"2aad1179f957631477b582e57cd8f538cb82618fc3fb816bde1171154235b065","names":[],"sources":[]},
|
||||
{"digest":"2ab12a0e345658f9342816d773fe610f049982bcad82631f43b9205abe43e5ee","names":[],"sources":[]},
|
||||
{"digest":"2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095","names":[],"sources":["packages/llm/llm/src/types.ts:473"]},
|
||||
{"digest":"2abb711460e6238d851c30f3e4bdd87a2aa1887ee70575b3d3b7b25610673095","names":[],"sources":["packages/llm/llm/src/types.ts:499"]},
|
||||
{"digest":"2aed07493b1c65b6d00de5586da1e743cf501684b9ad43a1765fb4007d4ea456","names":[],"sources":[]},
|
||||
{"digest":"2c4c21f19f426a58f2dac914e34313aa43f8408972de857a486d1f086d5adb6c","names":[],"sources":[]},
|
||||
{"digest":"2c64d4bc7264fd3f8bed47e96ab8a593bce05a1e4e1c436dcd244a3b7e0c2a9d","names":[],"sources":[]},
|
||||
@@ -215,7 +215,7 @@
|
||||
{"digest":"3bc287e765d58dc3a22672a0495a6cfd561220dd38fbaecef80aa95b96caed79","names":[],"sources":[]},
|
||||
{"digest":"3bd652ebfa8726b3ce3937a4f1bd2759e02f86b3a08a20f9e41a8513656fbc5f","names":["packages/core/session/src/types.ts#OptionalSessionSeq"],"sources":["packages/core/session/src/types.ts:66"]},
|
||||
{"digest":"3bdb8dc6989655b0d4ec361edafb8e2629bc263e1ea591819414fdafc1a413b3","names":[],"sources":[]},
|
||||
{"digest":"3bec87f9e6ab3da03346f49923d3c51621debc0159df68605e543c3b2a026f90","names":[],"sources":["packages/llm/llm/src/types.ts:471"]},
|
||||
{"digest":"3bec87f9e6ab3da03346f49923d3c51621debc0159df68605e543c3b2a026f90","names":[],"sources":["packages/llm/llm/src/types.ts:497"]},
|
||||
{"digest":"3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1","names":[],"sources":[]},
|
||||
{"digest":"3c2535ca0a2062f172f15344f8040755002b5ac506cf94fc4ebaa822b15feb8c","names":[],"sources":[]},
|
||||
{"digest":"3cfc3a56502da1f8c6153c2c56657bab4b749056968634dd0991d325ec1c919f","names":["packages/core/tools/src/contracts.ts#ToolRestriction"],"sources":["packages/core/tools/src/contracts.ts:376"]},
|
||||
@@ -230,7 +230,7 @@
|
||||
{"digest":"4208123b50df5006b181481ab45fcf1cde807b88d3fd4d340090bc2e202fac41","names":[],"sources":[]},
|
||||
{"digest":"42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f","names":[],"sources":[]},
|
||||
{"digest":"4312134fc6f9cad507c31390bd3d147535492dd77621ec517c5e85f853d53a03","names":[],"sources":[]},
|
||||
{"digest":"43e311235aa537df1bacdd92d144df8a952337a76e9ac892cdf670f1a797bad8","names":["packages/llm/llm/src/types.ts#ToolRemovalBlock"],"sources":["packages/llm/llm/src/types.ts:127"]},
|
||||
{"digest":"43e311235aa537df1bacdd92d144df8a952337a76e9ac892cdf670f1a797bad8","names":["packages/llm/llm/src/types.ts#ToolRemovalBlock"],"sources":["packages/llm/llm/src/types.ts:134"]},
|
||||
{"digest":"44818000e56acb97afdb5045ce235eb55e3fb1ccabc9fc7d0750bb38f36a7f8e","names":[],"sources":[]},
|
||||
{"digest":"44c8c77aa9aa100b0d74d2deee85474b29faa6eee0ac0f662f7abe9931ddd7f2","names":[],"sources":[]},
|
||||
{"digest":"4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986","names":[],"sources":[]},
|
||||
@@ -238,7 +238,7 @@
|
||||
{"digest":"45f2a07ea94573123b6ae14db787a40062dec3834f36d8be92c523d40ba28448","names":[],"sources":["packages/interaction/user-approval/src/types.ts:44"]},
|
||||
{"digest":"477614bca595a9e7adb5a1870be446b9a8684f3ba8950259d6289f2d3b74f028","names":[],"sources":["packages/compaction/compaction-image-offload/src/projection.ts:25"]},
|
||||
{"digest":"47e5d84f1a93450a78c7f3954b43990d0f629af7c034a56313e5deb63e772c1b","names":[],"sources":[]},
|
||||
{"digest":"47e9095591ae36e7c39ee62553097772f2bfa16304536a72dbbc3f691f605180","names":["packages/llm/llm/src/types.ts#StreamChunk"],"sources":["packages/llm/llm/src/types.ts:470"]},
|
||||
{"digest":"47e9095591ae36e7c39ee62553097772f2bfa16304536a72dbbc3f691f605180","names":["packages/llm/llm/src/types.ts#StreamChunk"],"sources":["packages/llm/llm/src/types.ts:496"]},
|
||||
{"digest":"487b65c37bfedb8b72088c4107a0a90a6249701f6b26745f6d98f9b8a22b07c7","names":[],"sources":["packages/llm/llm/src/assistant-stream.ts:44"]},
|
||||
{"digest":"48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3","names":[],"sources":[]},
|
||||
{"digest":"48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb","names":[],"sources":[]},
|
||||
@@ -251,10 +251,10 @@
|
||||
{"digest":"4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731","names":["packages/llm/llm/src/message.ts#ToolResultMessage"],"sources":["packages/llm/llm/src/message.ts:173"]},
|
||||
{"digest":"4b7ddaf3710b88ff9b8df66304f5f97bf03688fd19730eedc2e9535203c820ea","names":[],"sources":[]},
|
||||
{"digest":"4bb451330ff699f36b45539ceb0eb2606ead72084e2c5bb637c36cc80e2252db","names":[],"sources":["packages/core/session/src/types.ts:198"]},
|
||||
{"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/agent-team/src/types.ts#TeamId","packages/experimental/agent-team/src/types.ts#TeamMessageId","packages/experimental/agent-team/src/types.ts#TeamTaskId","packages/experimental/webhook/src/brand.ts#WebhookDeliveryId","packages/experimental/webhook/src/brand.ts#WebhookRuleId","packages/experimental/webhook/src/brand.ts#WebhookSourceId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/jobs/jobs/src/brand.ts#JobId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts:386","packages/attachment/attachment/src/brand.ts:6","packages/compaction/compaction/src/brand.ts:4","packages/core/session/src/types.ts:21","packages/experimental/agent-team/src/types.ts:21","packages/experimental/agent-team/src/types.ts:33","packages/experimental/agent-team/src/types.ts:9","packages/experimental/webhook/src/brand.ts:12","packages/experimental/webhook/src/brand.ts:6","packages/experimental/webhook/src/brand.ts:9","packages/feedback/message-feedback/src/types.ts:14","packages/goal/goal/src/types.ts:17","packages/interaction/commands/src/brand.ts:31","packages/interaction/user-approval/src/types.ts:17","packages/jobs/jobs/src/brand.ts:19","packages/llm/llm-retry/src/brand.ts:4","packages/llm/llm/src/brand.ts:16","packages/llm/llm/src/brand.ts:31","packages/llm/llm/src/brand.ts:43","packages/llm/llm/src/brand.ts:67","packages/schedule/schedule/src/types.ts:14","packages/session/session-title/src/index.ts:42","packages/session/session-title/src/types.ts:17","packages/workflow/workflow/src/types.ts:13"]},
|
||||
{"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/agent-team/src/types.ts#TeamId","packages/experimental/agent-team/src/types.ts#TeamMessageId","packages/experimental/agent-team/src/types.ts#TeamTaskId","packages/experimental/webhook/src/brand.ts#WebhookDeliveryId","packages/experimental/webhook/src/brand.ts#WebhookRuleId","packages/experimental/webhook/src/brand.ts#WebhookSourceId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/jobs/jobs/src/brand.ts#JobId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts:388","packages/attachment/attachment/src/brand.ts:6","packages/compaction/compaction/src/brand.ts:4","packages/core/session/src/types.ts:21","packages/experimental/agent-team/src/types.ts:21","packages/experimental/agent-team/src/types.ts:33","packages/experimental/agent-team/src/types.ts:9","packages/experimental/webhook/src/brand.ts:12","packages/experimental/webhook/src/brand.ts:6","packages/experimental/webhook/src/brand.ts:9","packages/feedback/message-feedback/src/types.ts:14","packages/goal/goal/src/types.ts:17","packages/interaction/commands/src/brand.ts:31","packages/interaction/user-approval/src/types.ts:17","packages/jobs/jobs/src/brand.ts:19","packages/llm/llm-retry/src/brand.ts:4","packages/llm/llm/src/brand.ts:16","packages/llm/llm/src/brand.ts:31","packages/llm/llm/src/brand.ts:43","packages/llm/llm/src/brand.ts:67","packages/schedule/schedule/src/types.ts:14","packages/session/session-title/src/index.ts:42","packages/session/session-title/src/types.ts:17","packages/workflow/workflow/src/types.ts:13"]},
|
||||
{"digest":"4e011753f8263f436efaabfbb3b4db53a98fc55342c5ded2daf7208b217be159","names":[],"sources":[]},
|
||||
{"digest":"4e44c9118152f6829634bfd508e47669ab0497caffa380c94b4be91f9390d35d","names":[],"sources":[]},
|
||||
{"digest":"4e887768586528565381dadbddee6cef555874148089f4579e0b4b1ad096fc9c","names":["packages/llm/llm/src/types.ts#TextBlock"],"sources":["packages/llm/llm/src/types.ts:62","packages/web/web-search-deepseek/src/provider.ts:66"]},
|
||||
{"digest":"4e887768586528565381dadbddee6cef555874148089f4579e0b4b1ad096fc9c","names":["packages/llm/llm/src/types.ts#TextBlock"],"sources":["packages/llm/llm/src/types.ts:69","packages/web/web-search-deepseek/src/provider.ts:66"]},
|
||||
{"digest":"4e97c3d85c0fc817ee58873c38c25a2474abaaa0af4839311b50b68db3b8cf1a","names":["packages/subagent/subagent/src/continuation-messages.ts#SubagentSettledMessageSource"],"sources":["packages/subagent/subagent/src/continuation-messages.ts:29"]},
|
||||
{"digest":"4edec1b770e8ec4ce1220bf42edc5ac46713a1b48d4caa778dcfc98bad1f28a3","names":[],"sources":[]},
|
||||
{"digest":"4f507598cf476214aa7646ce7841a7064a9dcb05d0a1eaaefaf79c2b738f160f","names":[],"sources":[]},
|
||||
@@ -281,7 +281,7 @@
|
||||
{"digest":"5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc","names":[],"sources":[]},
|
||||
{"digest":"57f3f606ff8b6c84d9c443faace9d1dd10cca0354f37a4f515ef075f7c351b90","names":[],"sources":[]},
|
||||
{"digest":"58015da1c1950a66daf66ae24913d6a1fc6d1d9ac441b155f8847f7f8584ba8e","names":[],"sources":[]},
|
||||
{"digest":"5808e36c95355f0e745201b5db1b6bcec4ebf5adb75d3e82d7d59cc89635ab80","names":["packages/llm/llm/src/types.ts#FinishReason"],"sources":["packages/llm/llm/src/types.ts:165"]},
|
||||
{"digest":"5808e36c95355f0e745201b5db1b6bcec4ebf5adb75d3e82d7d59cc89635ab80","names":["packages/llm/llm/src/types.ts#FinishReason"],"sources":["packages/llm/llm/src/types.ts:172"]},
|
||||
{"digest":"587bb11da730e06aa52b62c087d217d6f8f22459b25f31cb9e1e1baf17944e5e","names":[],"sources":[]},
|
||||
{"digest":"589d0d91239681f89ee485459dc8c2f73ea47183b5534289d966637024a2c35d","names":[],"sources":[]},
|
||||
{"digest":"58f94390b982f8a5ac1f4f235ba612557f4a1dc4a995d8b1cec63ce7639decb0","names":[],"sources":[]},
|
||||
@@ -301,7 +301,7 @@
|
||||
{"digest":"5dd766ccd1702a1142ad8b4bd44f2242f8e8a6752669ad3de0a5d224329b1438","names":[],"sources":["packages/compaction/compaction/src/types.ts:82"]},
|
||||
{"digest":"5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e","names":[],"sources":[]},
|
||||
{"digest":"5ebce1317eacd2c148456627a09668e927ee15c204f0643f5ea76b46b54a2647","names":[],"sources":[]},
|
||||
{"digest":"5f2117d1856a5ab43c28371faa978ebfaa48f8c7d734ea3fc94842210df83dca","names":["packages/llm/llm/src/types.ts#LlmFailure"],"sources":["packages/llm/llm/src/types.ts:41"]},
|
||||
{"digest":"5f2117d1856a5ab43c28371faa978ebfaa48f8c7d734ea3fc94842210df83dca","names":["packages/llm/llm/src/types.ts#LlmFailure"],"sources":["packages/llm/llm/src/types.ts:48"]},
|
||||
{"digest":"5f26a6c20b37632f8f57729d171c671def4683d994ac6257a8dffcd855101627","names":[],"sources":[]},
|
||||
{"digest":"5f758f3de5f00c18968a7c6d347083aac5c9ae13f6a79c78f6611b23c4973564","names":["packages/subagent/subagent/src/descriptor.ts#OneShotSubagentDescriptorData"],"sources":["packages/subagent/subagent/src/descriptor.ts:61"]},
|
||||
{"digest":"6085488d223aeffdef51d7ae17655fc26ab22ee13cbed82a50e62e2b6fb5b918","names":[],"sources":[]},
|
||||
@@ -315,14 +315,14 @@
|
||||
{"digest":"6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25","names":["packages/experimental/agent-team/src/types.ts#TeamMemberSnapshot"],"sources":["packages/experimental/agent-team/src/types.ts:48"]},
|
||||
{"digest":"65a8400e451e667e5b6e34df72ceb81fca8c12330330fa72083ad40279b189ad","names":["packages/llm/llm/src/message.ts#SystemPromptMessageSource"],"sources":["packages/llm/llm/src/message.ts:35"]},
|
||||
{"digest":"65d583e66518c5236015e0846470fb32efcf92375ed306aef14e50a9d2cd2099","names":["packages/skill/tool-skill/src/index.ts#SkillCatalogSource"],"sources":["packages/skill/tool-skill/src/index.ts:35"]},
|
||||
{"digest":"66ba70f6f068da443f969a0b947a013c3addd776b9b60a0a393759c19825e9a2","names":["packages/llm/llm/src/types.ts#ToolSchema"],"sources":["packages/llm/llm/src/types.ts:491"]},
|
||||
{"digest":"66ba70f6f068da443f969a0b947a013c3addd776b9b60a0a393759c19825e9a2","names":["packages/llm/llm/src/types.ts#ToolSchema"],"sources":["packages/llm/llm/src/types.ts:517"]},
|
||||
{"digest":"672c4aa2a9b736e3d6e5da6346d2e2ed5e3573f2d0baafa18e7407c9f3fee3db","names":["packages/skill/skill/src/index.ts#SkillInvocationSource"],"sources":["packages/skill/skill/src/index.ts:146"]},
|
||||
{"digest":"67d5f73ceae82f5265258142ad6cf172de2c1fef93ba551b7f0454e0a93bffe0","names":["packages/llm/llm/src/call-config.ts#LlmCallConfig"],"sources":["packages/llm/llm/src/call-config.ts:31"]},
|
||||
{"digest":"68141cbe3994a5ffcd064c07c75996538437fd9dbf77ce8a2927e89ec577fabe","names":[],"sources":[]},
|
||||
{"digest":"688e853dc149f0d30b31765c12ec7e3be74169bdf1a4c6463a10b945d50cc1b8","names":[],"sources":[]},
|
||||
{"digest":"68f44f59e91c41395076cb77265981a601680ec8a381bbbeb6ab754ffdc94573","names":[],"sources":[]},
|
||||
{"digest":"69081694be231d56fd9580ba14645fd5e35373202605d5c5c841a9435b5fa3b1","names":[],"sources":[]},
|
||||
{"digest":"693f4952fb5565da96c06bace8924b098ac2fbc915fde0ff8019701183abc057","names":[],"sources":["packages/llm/llm/src/types.ts:161"]},
|
||||
{"digest":"693f4952fb5565da96c06bace8924b098ac2fbc915fde0ff8019701183abc057","names":[],"sources":["packages/llm/llm/src/types.ts:168"]},
|
||||
{"digest":"69d238a6e9b08d67f601b1825962963d8d3523cb69ebf6208c697dc5d058c199","names":[],"sources":[]},
|
||||
{"digest":"6ab45ac76839722df912f3ba50f1c6b41f2d614bde11f1aabfae04117ccdc80d","names":[],"sources":[]},
|
||||
{"digest":"6b23ed9ce1b397e602788dbe033693bf97642a69e23fcab1cc45bf6f13b6d619","names":[],"sources":[]},
|
||||
@@ -334,7 +334,7 @@
|
||||
{"digest":"6ccb6bc423fb3952458abf9c1b1c3284208b7e660344b612cc55da773d61afe4","names":[],"sources":[]},
|
||||
{"digest":"6cda0d6d3b4336d83998fe580628426df81c2afbab5d768b8813b56d569120e8","names":[],"sources":[]},
|
||||
{"digest":"6cf082d0fb6034dea61636787ff0ddf43b6e59aad71e644435a479bc9b83ad65","names":[],"sources":[]},
|
||||
{"digest":"6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def","names":[],"sources":["packages/api/session-controller/src/types.ts:395"]},
|
||||
{"digest":"6dacb1584f2bfbe209dc6b6f578d51da6d55595f64d0e63c727c98f359278def","names":[],"sources":["packages/api/session-controller/src/types.ts:397"]},
|
||||
{"digest":"6e52dd55a1d48954ed5464ec46fe34d9eb65f709baabc9561e7a279910b0c406","names":[],"sources":["packages/preset/agent-preset-registry/src/session.ts:31"]},
|
||||
{"digest":"6ea608bdc8a7a63d3d23b84c49721325094db88d136482e43a820945295eee3e","names":[],"sources":[]},
|
||||
{"digest":"6ebcc2dbec9f2dabca9f668ae6d39e877899b180740570f453409db339221df3","names":[],"sources":[]},
|
||||
@@ -366,18 +366,18 @@
|
||||
{"digest":"7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397","names":[],"sources":[]},
|
||||
{"digest":"7d226b9fd95b4ba213d4237c22e930a91c3c769c95ca7f92977b9d625ba13438","names":["packages/goal/goal/src/domain.ts#GoalChangeMeta"],"sources":["packages/goal/goal/src/domain.ts:44"]},
|
||||
{"digest":"7d54a5a5e629e2e7783bfac72683bcc5e969e9f43426b9289826aaf294ff6344","names":["packages/feedback/command-feedback/src/types.ts#FeedbackRecord"],"sources":["packages/feedback/command-feedback/src/types.ts:27"]},
|
||||
{"digest":"7d65acf0a3a43a8f445ce5d3320b42eca492739fc931b2a7516bfb8368dc2c68","names":["packages/llm/llm/src/types.ts#ContentBlockType"],"sources":["packages/llm/llm/src/types.ts:148"]},
|
||||
{"digest":"7d65acf0a3a43a8f445ce5d3320b42eca492739fc931b2a7516bfb8368dc2c68","names":["packages/llm/llm/src/types.ts#ContentBlockType"],"sources":["packages/llm/llm/src/types.ts:155"]},
|
||||
{"digest":"7df62978a2b7de8540e714152297743e0a622209a46a5ac9513fc0fad73ca23b","names":[],"sources":[]},
|
||||
{"digest":"7eaef7a763ba5b949b8464bc8add8980a3a5ab566014b69787aeba71e5dfe07b","names":[],"sources":[]},
|
||||
{"digest":"7f13860ee89a2d35ce09f958e85a04566cd59cb1de1d73a9619a9b631961dc71","names":[],"sources":[]},
|
||||
{"digest":"7f7fd5a6b0064f597534b29ff62ef26e786dffccf5e14f654a7d4fcea2c35f04","names":[],"sources":[]},
|
||||
{"digest":"7f9365bae9fcb73e1e34061a1acfe0422c1a5fd351848cace9bebbe806f28462","names":["packages/workflow/tool-workflow/src/types.ts#ToolWorkflowRunEndData"],"sources":["packages/workflow/tool-workflow/src/types.ts:36"]},
|
||||
{"digest":"7f959241c24128b4983b559984811861dc59774b85905c8fa57c6cb0148a3dbc","names":["packages/goal/goal/src/domain.ts#GoalMessageSource"],"sources":["packages/goal/goal/src/domain.ts:47"]},
|
||||
{"digest":"8064c03c87c357fd1575cb9395ce86e6dcbe2d275bf7717864a02cddf01827d5","names":["packages/llm/llm/src/types.ts#ContentBlock"],"sources":["packages/llm/llm/src/types.ts:150"]},
|
||||
{"digest":"8064c03c87c357fd1575cb9395ce86e6dcbe2d275bf7717864a02cddf01827d5","names":["packages/llm/llm/src/types.ts#ContentBlock"],"sources":["packages/llm/llm/src/types.ts:157"]},
|
||||
{"digest":"80677c8c8b804189a0fde8aa70dd4ad7af8bb12dfd03027e8e023c8592c838bc","names":[],"sources":[]},
|
||||
{"digest":"8067b0d232c350f189738e60ee3c9939d46ca3b32b45db5525237b077893f649","names":[],"sources":[]},
|
||||
{"digest":"811229eedcec57979dbadca7dbea832a59f8580cff30cb217fca5e2da16314bd","names":[],"sources":[]},
|
||||
{"digest":"814d9434e1ddf9078612b3b34322f26e9363b469213335007f7e9ba7fee9464e","names":["packages/llm/llm/src/types.ts#ReasoningBlock"],"sources":["packages/llm/llm/src/types.ts:68"]},
|
||||
{"digest":"814d9434e1ddf9078612b3b34322f26e9363b469213335007f7e9ba7fee9464e","names":["packages/llm/llm/src/types.ts#ReasoningBlock"],"sources":["packages/llm/llm/src/types.ts:75"]},
|
||||
{"digest":"82470020b5bfcb1a843e2aa05602af7d37df99e82d01f015c7c589ba054fd89d","names":[],"sources":[]},
|
||||
{"digest":"825ca23416b1ea9d81442afead5cf9229a6fa05842f042135cc3aa9710a6a2d8","names":[],"sources":[]},
|
||||
{"digest":"829a2fb3be6efd4ba36aef8ed2d381a4c6fb2a0aaf50c823a6f3f8290786b809","names":[],"sources":[]},
|
||||
@@ -398,7 +398,7 @@
|
||||
{"digest":"871bfd6f7bb91061246768d3377f3fd58d6b8ea7c31f44c714ae7ab215650d62","names":[],"sources":[]},
|
||||
{"digest":"87b1a33564b9a4d63b00988a524c12259243953aebb9c9a05127d87b27464df9","names":[],"sources":[]},
|
||||
{"digest":"88841202942c7683b3b7e5ea6a796047ec326b6701ac23e58cc236d85613b9fa","names":[],"sources":[]},
|
||||
{"digest":"88a610b831be6d92612a464be5c7d0b88ec24d97bd299837bf74bf7672183c9a","names":[],"sources":["packages/llm/llm/src/types.ts:474"]},
|
||||
{"digest":"88a610b831be6d92612a464be5c7d0b88ec24d97bd299837bf74bf7672183c9a","names":[],"sources":["packages/llm/llm/src/types.ts:500"]},
|
||||
{"digest":"8a046b52331959aa3394680d6acbe09b8a5447c6b61375f263445bfcef19b7f1","names":[],"sources":[]},
|
||||
{"digest":"8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0","names":[],"sources":[]},
|
||||
{"digest":"8a9c990773f798eac10ca8878e6bcc531ee080bc589cac956730e462cc6a09ad","names":[],"sources":["packages/interaction/permission-presets/src/index.ts:53"]},
|
||||
@@ -409,8 +409,8 @@
|
||||
{"digest":"8c1c4f72755a78ea163523a46565316e36e9b644382eb48e6635c71a6f412dd2","names":[],"sources":[]},
|
||||
{"digest":"8c627d7a45fa406cea879a3922105e27ed4c3c7966e264afaf11ea7a330f2dd1","names":[],"sources":[]},
|
||||
{"digest":"8cdd3fdd295cee53035de16a1b23411716f75e9f994fe9510cac5d7fef605c76","names":[],"sources":[]},
|
||||
{"digest":"8e31bb958e9b015e6f8407eaade32217068159e816f3cc99c3ad76eb889732e0","names":[],"sources":["packages/llm/llm/src/types.ts:160"]},
|
||||
{"digest":"8e611bf46821e5003a1605a73560a6f56388295149d449dfe2142d5ec335f29c","names":["packages/llm/llm/src/types.ts#ToolAdditionBlock"],"sources":["packages/llm/llm/src/types.ts:115"]},
|
||||
{"digest":"8e31bb958e9b015e6f8407eaade32217068159e816f3cc99c3ad76eb889732e0","names":[],"sources":["packages/llm/llm/src/types.ts:167"]},
|
||||
{"digest":"8e611bf46821e5003a1605a73560a6f56388295149d449dfe2142d5ec335f29c","names":["packages/llm/llm/src/types.ts#ToolAdditionBlock"],"sources":["packages/llm/llm/src/types.ts:122"]},
|
||||
{"digest":"8e8155594e07812356b67d732196dda894b0f9c91a5969d51612cf490b0a016d","names":[],"sources":[]},
|
||||
{"digest":"8e8e91646a5cae3fc78f7532a0013ff2088ae47077642c2edd507a508209b8af","names":["packages/schedule/schedule/src/types.ts#OneShotScheduleDispatchChange"],"sources":["packages/schedule/schedule/src/types.ts:237"]},
|
||||
{"digest":"8f324cea3916c0517261e88167def8efb3a9994c1135e23d40ed9fd48f8c7bd4","names":[],"sources":[]},
|
||||
@@ -424,7 +424,7 @@
|
||||
{"digest":"93cbbc5a7fd0b1aa1ff1b9a6bb5ec52b073f48e5a5200365c45c0cd54059e1b9","names":[],"sources":["packages/interaction/user-approval/src/types.ts:55"]},
|
||||
{"digest":"93d38436347f67c9022aacbc4472e283d722bcab8eb73fc376aaf7b6a6b9fb35","names":[],"sources":[]},
|
||||
{"digest":"94c56aeb1564ad735eb773d88636b40761d063efa1610f1308004d32923f5378","names":[],"sources":[]},
|
||||
{"digest":"95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65","names":[],"sources":["packages/llm/llm/src/types.ts:472"]},
|
||||
{"digest":"95080295115fbd83544520ba247b68a03790131f6a2eac323d388758cb542f65","names":[],"sources":["packages/llm/llm/src/types.ts:498"]},
|
||||
{"digest":"95a3ffccbf3c8afcf9a2f3b355f724da8e07e98dc47ee741166dde7f9ea058bd","names":[],"sources":[]},
|
||||
{"digest":"95acdbd066cf445d37092e5fdfe2e8717ef6ca1aed50f70e2e6037a6989a1f2f","names":[],"sources":[]},
|
||||
{"digest":"95c0e88fcf0bafb2087f912ad9e2369de6ae6530cc0c4c2772c96205b57f7f60","names":[],"sources":[]},
|
||||
@@ -461,7 +461,7 @@
|
||||
{"digest":"a15a19ef63cf5d1b711db1d249821dab0cedcd3a9ad09137b47352c93841fd20","names":[],"sources":[]},
|
||||
{"digest":"a16e567c8cd2c55cf1e911aed3c200c4e1f6febb1459ff2a9e7adf159afb68cb","names":[],"sources":[]},
|
||||
{"digest":"a2066bd52f61218ba15b758def2641cc6a8bd3d4a594a488ee4c65be356d7e39","names":[],"sources":[]},
|
||||
{"digest":"a2b49331716ba0a7078b026d145306a70eae64b7313c3e6204bd931e75ec3bb1","names":[],"sources":["packages/llm/llm/src/types.ts:477"]},
|
||||
{"digest":"a2b49331716ba0a7078b026d145306a70eae64b7313c3e6204bd931e75ec3bb1","names":[],"sources":["packages/llm/llm/src/types.ts:503"]},
|
||||
{"digest":"a2be0f0cb59f0d4a06557d45847d1938aced238baa77a6da416e590b8b958553","names":[],"sources":[]},
|
||||
{"digest":"a3a259a1aa5401af786ce08c4a8419681d3000d73467991b5fc563389bc4ebd2","names":[],"sources":[]},
|
||||
{"digest":"a3c6f1ea38e9a61d7a9706057ba65aabe02dfe6d9dd90d4736aa62325d874531","names":[],"sources":[]},
|
||||
@@ -486,7 +486,7 @@
|
||||
{"digest":"aacda84a69739238aa782eea902d3073017d9d74b9808de2bf5a83c9738cbdc6","names":[],"sources":[]},
|
||||
{"digest":"aada2771a250136cc3a839b0507e9c6502622a2897716903942297bcb6661292","names":[],"sources":[]},
|
||||
{"digest":"aae343bc3d663c83f2e8b5e1c7eea5d509dc7a5361bc8bd8b5ac3bb8d3b1ed56","names":[],"sources":[]},
|
||||
{"digest":"ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7","names":[],"sources":["packages/core/session/src/types.ts:220","packages/llm/llm/src/types.ts:159"]},
|
||||
{"digest":"ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7","names":[],"sources":["packages/core/session/src/types.ts:220","packages/llm/llm/src/types.ts:166"]},
|
||||
{"digest":"aba175fb20ffc71112c55efe70458ff5d7dbfb83501892b4ba8d077f0ea5d7ba","names":[],"sources":[]},
|
||||
{"digest":"abd6ca62a5ab166553848324a23fef90eed7f59c8021bd63e7ea5bc6fd666b0c","names":["packages/web/web-search-deepseek/src/provider.ts#DeepSeekSearchLlmRequest"],"sources":["packages/web/web-search-deepseek/src/provider.ts:55"]},
|
||||
{"digest":"ad0c38a0adb82a94c925456df41586d9b5f9202fca18593eae2213d4445b4585","names":[],"sources":["packages/core/session/src/types.ts:398"]},
|
||||
@@ -551,7 +551,7 @@
|
||||
{"digest":"bf16d2e98a7f13894ded2cffe5ce857bf473954a0e3c0ed9bc7739e2e1c7b78a","names":[],"sources":[]},
|
||||
{"digest":"c0ea97bc89dfc46fa94eab956f5c3ee6af2f964aa7db6327cb18b451f207bef7","names":["packages/workflow/tool-workflow/src/types.ts#ToolWorkflowAgentEndData"],"sources":["packages/workflow/tool-workflow/src/types.ts:29"]},
|
||||
{"digest":"c170da8ab7c37635b10867b2571811d9e317ac3ec394e8ff8efe53f902fc41c1","names":["packages/llm/llm-retry/src/types.ts#LlmRetryEventData"],"sources":["packages/llm/llm-retry/src/types.ts:16"]},
|
||||
{"digest":"c1f0f17b6493b85db6453bb30e01b208d703731b25bc1e68e5d4fc72eea9cdea","names":["packages/llm/llm/src/types.ts#ToolCallBlock"],"sources":["packages/llm/llm/src/types.ts:105"]},
|
||||
{"digest":"c1f0f17b6493b85db6453bb30e01b208d703731b25bc1e68e5d4fc72eea9cdea","names":["packages/llm/llm/src/types.ts#ToolCallBlock"],"sources":["packages/llm/llm/src/types.ts:112"]},
|
||||
{"digest":"c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058","names":[],"sources":[]},
|
||||
{"digest":"c219efbdfd53b836786202ab4532913d555798f0a8615a4e9629736e3799fb70","names":[],"sources":[]},
|
||||
{"digest":"c2deb7c9183735d4e0fffddf5c623f4e34daa1c8e96b111982e957dfcbbcf896","names":["packages/workflow/workflow/src/types.ts#WorkflowAgentOutcome"],"sources":["packages/workflow/workflow/src/types.ts:110"]},
|
||||
@@ -581,7 +581,7 @@
|
||||
{"digest":"d0fbb0ca1bbba059830826f33f801da1aced35a2504376d61e020bdfec8d50c0","names":[],"sources":[]},
|
||||
{"digest":"d28765f87d6b6ab408e0b2f3e7e297791b004b806d8e302c49903ebae815feb2","names":[],"sources":[]},
|
||||
{"digest":"d2b617d9dfcc4e0a4b60732d8ba1effc632e3b75124b93b064c07814a0a716bc","names":[],"sources":[]},
|
||||
{"digest":"d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d","names":[],"sources":["packages/llm/llm/src/types.ts:475"]},
|
||||
{"digest":"d4b739491689cc6bb13d07ffac1a32239875abda364c0d5edd8f74faf5e9c00d","names":[],"sources":["packages/llm/llm/src/types.ts:501"]},
|
||||
{"digest":"d4c799e3d51cae1040362ba4968efc484739e14416c9c1df20c2f108b4ef2eb5","names":[],"sources":[]},
|
||||
{"digest":"d4e4d575abeb4b72d616dc65c17ac0eaa943cc135609395044c38d5afcb10e31","names":[],"sources":[]},
|
||||
{"digest":"d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a","names":[],"sources":[]},
|
||||
@@ -655,11 +655,11 @@
|
||||
{"digest":"f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402","names":["packages/core/session/src/types.ts#TurnEndReason"],"sources":["packages/core/session/src/types.ts:238"]},
|
||||
{"digest":"f1996af2602e6f62b5ab179ca5c4af00d8753938b74cc6ef102de09e99e74a7e","names":[],"sources":[]},
|
||||
{"digest":"f19bee84bf3d20904553cdcd57465232bc1c2a194f54c4d195292ba0487223a6","names":["packages/attachment/attachment/src/types.ts#FileAttachmentRef"],"sources":["packages/attachment/attachment/src/types.ts:39"]},
|
||||
{"digest":"f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515","names":[],"sources":["packages/llm/llm/src/types.ts:476"]},
|
||||
{"digest":"f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515","names":[],"sources":["packages/llm/llm/src/types.ts:502"]},
|
||||
{"digest":"f230c0b583e23b25ee0a61dbe4c05e95c1e2ae44b27939501eb9e03696f0c278","names":[],"sources":[]},
|
||||
{"digest":"f29d63336e8266d80dd7baeab219436db86341304f6be1cd0edb36f5134c4a47","names":[],"sources":[]},
|
||||
{"digest":"f2da0f27c1e6add59982c37b15e4a4912c5b33466859cce3b13d503600e24aef","names":["packages/llm/llm/src/types.ts#FileBlock"],"sources":["packages/llm/llm/src/types.ts:98"]},
|
||||
{"digest":"f311671e07bc7fa645a0b6597de9966b97147956565296ba76a36543801b66c5","names":["packages/llm/llm/src/types.ts#SystemPromptUpdate"],"sources":["packages/llm/llm/src/types.ts:414"]},
|
||||
{"digest":"f2da0f27c1e6add59982c37b15e4a4912c5b33466859cce3b13d503600e24aef","names":["packages/llm/llm/src/types.ts#FileBlock"],"sources":["packages/llm/llm/src/types.ts:105"]},
|
||||
{"digest":"f311671e07bc7fa645a0b6597de9966b97147956565296ba76a36543801b66c5","names":["packages/llm/llm/src/types.ts#SystemPromptUpdate"],"sources":["packages/llm/llm/src/types.ts:440"]},
|
||||
{"digest":"f4a66f654996c093aae700ea4c434ff2ee3569fea9c0e5813ccf6afdccabd163","names":[],"sources":[]},
|
||||
{"digest":"f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1","names":[],"sources":[]},
|
||||
{"digest":"f56c5b59ca0161310c338d31dd16cf14287ccd602ff4181058df178c1c286d7f","names":["packages/context/session-reference/src/types.ts#SessionReferenceSource"],"sources":["packages/context/session-reference/src/types.ts:13"]},
|
||||
@@ -688,7 +688,7 @@
|
||||
{"digest":"fd0d91a1f9dd2efb8959bdf5e07f8f0a98b1cdfa38ce8d4732ebde0b44c3abd4","names":[],"sources":[]},
|
||||
{"digest":"fe1a7acdeed4d0f5ab483396eaa5b3dc97263b7c10eb4da2f0028cb01728bfc9","names":[],"sources":[]},
|
||||
{"digest":"fe792a6cd430494503837e21e79f91677704ccdd9e870c679aabd7d43ed47ddd","names":[],"sources":[]},
|
||||
{"digest":"fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394","names":["packages/llm/llm/src/types.ts#TokenUsage"],"sources":["packages/llm/llm/src/types.ts:175"]},
|
||||
{"digest":"fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394","names":["packages/llm/llm/src/types.ts#TokenUsage"],"sources":["packages/llm/llm/src/types.ts:182"]},
|
||||
{"digest":"fee1caf8ab6a65c1bc981f5a1730164490f22a2a89ffa3d0b76f1f432c44c8b5","names":[],"sources":[]}
|
||||
],
|
||||
"nodes": {
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
en: 58dec2259b0b1c6f
|
||||
zh: fc99fe965acaabf3
|
||||
/llm-streaming/the-model-request:
|
||||
en: 2b0831aaae8e1cdf
|
||||
zh: f669d93d1b9d6025
|
||||
en: 834d3d8d6b5871ab
|
||||
zh: 393889ec6075f622
|
||||
/llm-streaming/the-model-request/the-request-envelope-llmcallconfig-and-the-logged-header:
|
||||
en: b0eb4620c6953646
|
||||
zh: fabf51c7245a4855
|
||||
|
||||
@@ -525,6 +525,26 @@ interface LlmNativeAuth {
|
||||
}
|
||||
```
|
||||
|
||||
A catalog entry may carry a provider-authored `badge` that selectors show beside the model name. It is display data: `resolveModelInfo` omits it, and no model request carries it.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Emphasis of a model badge: `neutral` states a plain fact, `info` highlights
|
||||
* the model, `success` marks a benefit, and `warning` asks for attention.
|
||||
*/
|
||||
type LlmModelBadgeTone = 'neutral' | 'info' | 'success' | 'warning'
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Short provider-authored label a selector shows beside one advertised model name. */
|
||||
interface LlmModelBadge {
|
||||
/** One literal, or translations keyed by lowercase language id with a required English entry. */
|
||||
text: LocalizedText
|
||||
/** Emphasis of the label. */
|
||||
tone: LlmModelBadgeTone
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** One adapter-discovered model; catalog membership is advisory, not request validation. */
|
||||
interface LlmModelInfo {
|
||||
@@ -538,6 +558,11 @@ interface LlmModelInfo {
|
||||
description?: string
|
||||
/** Accepted request modalities; absent means unknown, while an explicit omission is negative capability. */
|
||||
inputModalities?: readonly ModelModality[]
|
||||
/**
|
||||
* Selector label advertised by catalog discovery; exact-route resolution
|
||||
* omits it, and no model request or Session event carries it.
|
||||
*/
|
||||
badge?: LlmModelBadge
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1170,6 +1195,23 @@ The provider topology changed: an adapter registered or unregistered routes, or
|
||||
|
||||
Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts)
|
||||
|
||||
<a id="llmmodels-updated--emit"></a>
|
||||
|
||||
#### `llm/models-updated` — emit
|
||||
|
||||
A provider's advertised models or display metadata changed; consumers reload `listModels()`.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* A provider's advertised models or display metadata changed; consumers reload `listModels()`.
|
||||
* @mode emit
|
||||
* @param provider - route whose model catalog changed.
|
||||
*/
|
||||
'llm/models-updated'(provider: string): void
|
||||
```
|
||||
|
||||
Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts)
|
||||
|
||||
<a id="llmstream--waterfall"></a>
|
||||
|
||||
#### `llm/stream` — waterfall
|
||||
|
||||
@@ -531,6 +531,26 @@ interface LlmNativeAuth {
|
||||
}
|
||||
```
|
||||
|
||||
目录条目可以携带提供方编写的 `badge`,供选择器显示在模型名称旁。它只是展示数据:`resolveModelInfo` 不返回它,模型请求也不携带它。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Emphasis of a model badge: `neutral` states a plain fact, `info` highlights
|
||||
* the model, `success` marks a benefit, and `warning` asks for attention.
|
||||
*/
|
||||
type LlmModelBadgeTone = 'neutral' | 'info' | 'success' | 'warning'
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Short provider-authored label a selector shows beside one advertised model name. */
|
||||
interface LlmModelBadge {
|
||||
/** One literal, or translations keyed by lowercase language id with a required English entry. */
|
||||
text: LocalizedText
|
||||
/** Emphasis of the label. */
|
||||
tone: LlmModelBadgeTone
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** One adapter-discovered model; catalog membership is advisory, not request validation. */
|
||||
interface LlmModelInfo {
|
||||
@@ -544,6 +564,11 @@ interface LlmModelInfo {
|
||||
description?: string
|
||||
/** Accepted request modalities; absent means unknown, while an explicit omission is negative capability. */
|
||||
inputModalities?: readonly ModelModality[]
|
||||
/**
|
||||
* Selector label advertised by catalog discovery; exact-route resolution
|
||||
* omits it, and no model request or Session event carries it.
|
||||
*/
|
||||
badge?: LlmModelBadge
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1176,6 +1201,23 @@ The provider topology changed: an adapter registered or unregistered routes, or
|
||||
|
||||
Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts)
|
||||
|
||||
<a id="llmmodels-updated--emit"></a>
|
||||
|
||||
#### `llm/models-updated` — emit
|
||||
|
||||
A provider's advertised models or display metadata changed; consumers reload `listModels()`.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* A provider's advertised models or display metadata changed; consumers reload `listModels()`.
|
||||
* @mode emit
|
||||
* @param provider - route whose model catalog changed.
|
||||
*/
|
||||
'llm/models-updated'(provider: string): void
|
||||
```
|
||||
|
||||
Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts)
|
||||
|
||||
<a id="llmstream--waterfall"></a>
|
||||
|
||||
#### `llm/stream` — waterfall
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.2/model-badge-package-removal/guide.md
|
||||
/:
|
||||
en: e3db78299cefc568
|
||||
zh: 0c1892abef438638
|
||||
/the-ui-deepseek-model-label-1017-package-is-removed:
|
||||
en: 772b7e3479b2e52b
|
||||
zh: 9d7a225cf77c2664
|
||||
/the-ui-deepseek-model-label-1017-package-is-removed/change:
|
||||
en: 48543e623bac7b47
|
||||
zh: a22640ced4b587fa
|
||||
/the-ui-deepseek-model-label-1017-package-is-removed/migration:
|
||||
en: 3e366a0f2daf220c
|
||||
zh: 859d6bc103213809
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "The fixed 1017 model badge package is removed; model badges come from the DeepSeek model listing."
|
||||
---
|
||||
# The `ui-deepseek-model-label-1017` package is removed
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
`@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` and its `ui-deepseek-model-label-1017` row in the Web bundle are removed, and the package receives no further releases. The composer model selector shows the badge that the DeepSeek `GET /models` listing declares for a model; without a declared badge, `DeepSeek-V4.1-Flash` shows no `1017`. A profile patch that disables or edits the removed row matches no entry, so boot warns that the entry is not found and skips the patch.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Delete the patch whose `id` is `ui-deepseek-model-label-1017`, with its `disabled` or other fields, from `$DSH_HOME/profiles/<profile>/cordis.patch.yml` and from any overlay passed with `--patch`.
|
||||
2. Remove `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` from the dependencies of any package that lists it.
|
||||
3. To change how model names or badges render, contribute to the `conversation.input.model.label` slot; its owner props carry the catalog `model.badge` ([label extensions](../../../../packages/client/ui-model-selection/README.md#use-this-package)).
|
||||
4. Restart DSH and confirm that boot reports no missing `ui-deepseek-model-label-1017` entry.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "固定的 1017 模型角标包已移除,模型角标改由 DeepSeek 模型列表下发。"
|
||||
---
|
||||
# `ui-deepseek-model-label-1017` 包已移除
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
`@deepseek-ai/dsh-client-ui-deepseek-model-label-1017` 及其在 Web bundle 中的 `ui-deepseek-model-label-1017` 条目已移除,该包不再发布新版本。composer 模型选择器显示 DeepSeek `GET /models` 列表为模型声明的角标;列表没有声明角标时,`DeepSeek-V4.1-Flash` 不再显示 `1017`。停用或修改该条目的 profile patch 不再匹配任何条目,启动时会警告找不到该条目并跳过这条 patch。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 从 `$DSH_HOME/profiles/<profile>/cordis.patch.yml` 以及通过 `--patch` 传入的 overlay 中,删除 `id` 为 `ui-deepseek-model-label-1017` 的 patch,连同其中的 `disabled` 或其他字段。
|
||||
2. 若有包在依赖中列出 `@deepseek-ai/dsh-client-ui-deepseek-model-label-1017`,将其删除。
|
||||
3. 如需改变模型名称或角标的显示方式,向 `conversation.input.model.label` slot 注册组件;其 owner props 携带目录中的 `model.badge`([名称扩展](../../../../packages/client/ui-model-selection/README.zh.md#use-this-package))。
|
||||
4. 重启 DSH,确认启动输出中没有找不到 `ui-deepseek-model-label-1017` 条目的警告。
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.4/model-listing-refresh/guide.md
|
||||
/:
|
||||
en: 87b8371f353630ce
|
||||
zh: 545cd66ec2055405
|
||||
/model-listings-refresh-automatically:
|
||||
en: f4613eb3beef2045
|
||||
zh: 7c1301d90456a6c2
|
||||
/model-listings-refresh-automatically/change:
|
||||
en: 6ed436b9f0a93aef
|
||||
zh: 16a09e92ba786d89
|
||||
/model-listings-refresh-automatically/migration:
|
||||
en: e479027c4c4811df
|
||||
zh: 7cc7cde8aa4e0135
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "DeepSeek model listings refresh automatically after first use, and custom listing-cache owners must supply live settings and change notifications."
|
||||
---
|
||||
# Model listings refresh automatically
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
DeepSeek account and API-key routes refresh their model listing automatically after first use, including while idle. `modelsMaxAgeMs` keeps its ten-minute default and now schedules background requests instead of waiting for another access. Failed refreshes retain the last successful listing, including badges. Successful refreshes replace badge metadata, including omission or `null`, and notify open Web selectors.
|
||||
|
||||
`ModelListingCache` consumers must supply `settings` and `changed` callbacks. Its `dispose()` now returns a promise that settles after active loads and automatic credential lookups finish. `ListedCatalog` also requires a `badges` map, and `ModelSelectInjected` requires a `resolveText` callback.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Keep existing `modelsMaxAgeMs` values to use the same interval for automatic refresh. Use `0` only when each access should refresh and no automatic timer should run.
|
||||
2. Custom `ModelListingCache` owners must provide `settings: () => ListingSettings | undefined` for the latest route configuration and `changed: () => void` to notify catalog consumers. Return `undefined` from `settings` when the route stops following a listing.
|
||||
3. Return or await `cache.dispose()` from the owning plugin's cleanup.
|
||||
4. Code that constructs `ListedCatalog` must include `badges: new Map()` when it has no badges, or a map from model id to `LlmModelBadge`. Prefer `resolveListedCatalog` to construct this result from decoded listing entries.
|
||||
5. Code that constructs `ModelSelectInjected` must provide `resolveText: text => ctx.locale.resolveText(text)` from its owning locale service. Test doubles can use `text => typeof text === 'string' ? text : text.en` when they intentionally render English.
|
||||
6. After the next refresh, confirm that an already-open model selector displays the current badge without reloading the page. Remove the badge field or set it to `null` to clear it.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "DeepSeek 模型列表首次使用后自动刷新,自定义列表缓存的持有者必须提供实时配置与变化通知。"
|
||||
---
|
||||
# 模型列表自动刷新
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
DeepSeek 账号与 API key 路由首次使用模型列表后会自动刷新,空闲时也会刷新。`modelsMaxAgeMs` 保留十分钟默认值,现在会安排后台请求,无需等待下一次访问。刷新失败时保留上次成功加载的列表及其角标。刷新成功时替换角标元数据,省略或 `null` 也会生效,并通知已打开的 Web 选择器。
|
||||
|
||||
`ModelListingCache` 的消费方必须提供 `settings` 与 `changed` 回调。其 `dispose()` 现在返回一个 promise,等待进行中的加载与自动凭据查询结束后才完成。`ListedCatalog` 还要求提供 `badges` 映射,`ModelSelectInjected` 要求提供 `resolveText` 回调。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 保留现有 `modelsMaxAgeMs` 值即可按相同间隔自动刷新。只有需要每次访问都刷新、且不运行自动定时器时,才使用 `0`。
|
||||
2. 自定义 `ModelListingCache` 持有者必须提供 `settings: () => ListingSettings | undefined` 以读取最新路由配置,并通过 `changed: () => void` 通知目录消费方。路由停止跟随列表时,`settings` 返回 `undefined`。
|
||||
3. 在持有该缓存的插件清理函数中返回或等待 `cache.dispose()`。
|
||||
4. 构造 `ListedCatalog` 的代码在没有角标时必须提供 `badges: new Map()`,否则提供模型 id 到 `LlmModelBadge` 的映射。优先使用 `resolveListedCatalog` 从已解码的列表条目构造该结果。
|
||||
5. 构造 `ModelSelectInjected` 的代码必须通过所属的 locale 服务提供 `resolveText: text => ctx.locale.resolveText(text)`。明确使用英语渲染的测试替身可以使用 `text => typeof text === 'string' ? text : text.en`。
|
||||
6. 下一次刷新后,确认已打开的模型选择器无需重新加载页面就显示当前角标。移除角标字段或将其设为 `null` 即可清除角标。
|
||||
@@ -44,6 +44,7 @@ export const API_REMOTE_FORWARDED_EVENTS = [
|
||||
{ event: 'cordis/inspect-query', mode: 'emit' },
|
||||
{ event: 'cordis/inspect-query-resolved', mode: 'emit' },
|
||||
{ event: 'llm/adapters-updated', mode: 'emit' },
|
||||
{ event: 'llm/models-updated', mode: 'emit' },
|
||||
{ event: 'permission-presets/catalog-changed', mode: 'emit' },
|
||||
{ event: 'plugin-manager/changed', mode: 'emit' },
|
||||
{ event: 'plugin-manager/install-log', mode: 'emit' },
|
||||
|
||||
@@ -44,6 +44,7 @@ export async function buildModelCatalog(
|
||||
name: model.name,
|
||||
...(model.description === undefined ? {} : { description: model.description }),
|
||||
...(reasoning === undefined ? {} : { reasoning }),
|
||||
...(model.badge === undefined ? {} : { badge: model.badge }),
|
||||
}
|
||||
}))
|
||||
return {
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
} from '@deepseek-ai/dsh-attachment'
|
||||
import type { Branded } from '@deepseek-ai/dsh-brand'
|
||||
import type { LlmAttemptId, MessageId } from '@deepseek-ai/dsh-llm/brand'
|
||||
import type { TextBlock } from '@deepseek-ai/dsh-llm'
|
||||
import type { LlmModelBadge, TextBlock } from '@deepseek-ai/dsh-llm'
|
||||
import type { SessionId, SessionSeqCursor } from '@deepseek-ai/dsh-session/types'
|
||||
import type { SessionProjectionMap, SessionProjectionSummaryExcluded, SessionProjectionSummaryValues } from '@deepseek-ai/dsh-session-projection/types'
|
||||
import type { Principal } from '@deepseek-ai/dsh-typert-protocol'
|
||||
@@ -110,6 +110,8 @@ export interface ModelCatalogModel {
|
||||
readonly name: string
|
||||
readonly description?: string
|
||||
readonly reasoning?: ModelReasoning
|
||||
/** Provider-authored label shown beside the name; display data only. */
|
||||
readonly badge?: LlmModelBadge
|
||||
}
|
||||
|
||||
/** One provider and its successfully loaded model catalog. */
|
||||
|
||||
@@ -432,6 +432,7 @@ describe('Web session model selection', () => {
|
||||
const { ctx } = await harness()
|
||||
ctx.llm.registerAdapter(['plain'], new CatalogAdapter('Plain', [
|
||||
{ provider: 'plain', id: 'plain-model', name: 'Plain Model' },
|
||||
{ provider: 'plain', id: 'badged-model', name: 'Badged Model', badge: { text: { en: 'New', zh: '新' }, tone: 'info' } },
|
||||
]))
|
||||
ctx.llm.registerAdapter(['described-reasoning'], new CatalogAdapter('Described Reasoning', [
|
||||
{ provider: 'described-reasoning', id: 'reasoning-model', name: 'Reasoning Model' },
|
||||
@@ -451,7 +452,10 @@ describe('Web session model selection', () => {
|
||||
|
||||
const catalog = await buildModelCatalog(ctx)
|
||||
expect(catalog.groups).toEqual(expect.arrayContaining([
|
||||
{ id: 'plain', name: 'Plain', models: [{ id: 'plain-model', name: 'Plain Model' }] },
|
||||
{ id: 'plain', name: 'Plain', models: [
|
||||
{ id: 'plain-model', name: 'Plain Model' },
|
||||
{ id: 'badged-model', name: 'Badged Model', badge: { text: { en: 'New', zh: '新' }, tone: 'info' } },
|
||||
] },
|
||||
{
|
||||
id: 'described-reasoning',
|
||||
name: 'Described Reasoning',
|
||||
|
||||
@@ -413,9 +413,6 @@
|
||||
- id: ui-model-selection
|
||||
name: '@deepseek-ai/dsh-client-ui-model-selection'
|
||||
|
||||
- id: ui-deepseek-model-label-1017
|
||||
name: '@deepseek-ai/dsh-client-ui-deepseek-model-label-1017'
|
||||
|
||||
- id: ui-permission
|
||||
name: '@deepseek-ai/dsh-client-ui-permission-presets'
|
||||
|
||||
|
||||
@@ -91,7 +91,6 @@
|
||||
"@deepseek-ai/dsh-client-ui-layout": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-message-feedback": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-model-selection": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-deepseek-model-label-1017": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-open-in-app": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-permission-presets": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-plan": "workspace:*",
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
en: 1eb35b015a6c423f
|
||||
zh: a8a3dceee2c9e943
|
||||
/client-web-gui-browser-half/packages:
|
||||
en: 3cae9938f3a11040
|
||||
zh: 8ed2edf0ffe6eed0
|
||||
en: faf349da96142e7c
|
||||
zh: 2565c9785b010940
|
||||
/client-web-gui-browser-half/related-documentation:
|
||||
en: d1d506e68fab7333
|
||||
zh: 1a90fe6f3f8f24a9
|
||||
|
||||
@@ -68,7 +68,6 @@ The kernel packages boot and serve the page; the UI feature packages present it.
|
||||
| [`ui-jobs/`](ui-jobs/README.md) | Lists this session's background jobs with on-demand streaming record panels | — |
|
||||
| [`ui-schedule/`](ui-schedule/README.md) | Lists the current Session's active reminders in a read-only header catalog | — |
|
||||
| [`ui-model-selection/`](ui-model-selection/README.md) | Provides model selection in conversation surfaces | — |
|
||||
| [`ui-deepseek-model-label-1017/`](ui-deepseek-model-label-1017/README.md) | Adds the temporary 1017 badge to official DeepSeek Flash model labels | — |
|
||||
| [`ui-permission-presets/`](ui-permission-presets/README.md) | Configures default permissions and switches the current session's access | — |
|
||||
| [`ui-plan/`](ui-plan/README.md) | Presents active plan-mode status and its exit control | — |
|
||||
| [`ui-settings-plugins/`](ui-settings-plugins/README.md) | Owns the Built-in plugins settings section shell and its tab extension point | — |
|
||||
|
||||
@@ -68,7 +68,6 @@ kind: "package-group"
|
||||
| [`ui-jobs/`](ui-jobs/README.zh.md) | 列出本会话的后台任务,并提供按需展开的流式输出面板 | — |
|
||||
| [`ui-schedule/`](ui-schedule/README.zh.md) | 在只读标题栏目录中列出当前会话中生效的提醒 | — |
|
||||
| [`ui-model-selection/`](ui-model-selection/README.zh.md) | 在对话界面中提供模型选择 | — |
|
||||
| [`ui-deepseek-model-label-1017/`](ui-deepseek-model-label-1017/README.zh.md) | 为官方 DeepSeek Flash 模型名称添加临时的 1017 角标 | — |
|
||||
| [`ui-permission-presets/`](ui-permission-presets/README.zh.md) | 配置默认权限并切换当前会话的访问模式 | — |
|
||||
| [`ui-plan/`](ui-plan/README.zh.md) | 展示生效中的 plan mode 状态及其退出控件 | — |
|
||||
| [`ui-settings-plugins/`](ui-settings-plugins/README.zh.md) | 负责「内置插件」设置分区壳及其标签页扩展点 | — |
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-deepseek-model-label-1017/README.md
|
||||
/:
|
||||
en: 21f43d0c704c3668
|
||||
zh: 0497e58bb871e2c4
|
||||
/deepseek-ai-dsh-client-ui-deepseek-model-label-1017:
|
||||
en: aa0cd60e16297d9f
|
||||
zh: 882cee782592c56b
|
||||
/deepseek-ai-dsh-client-ui-deepseek-model-label-1017/summary:
|
||||
en: 708be526d5758c04
|
||||
zh: 136b968ae71c9657
|
||||
/deepseek-ai-dsh-client-ui-deepseek-model-label-1017/table-of-contents:
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
/deepseek-ai-dsh-client-ui-deepseek-model-label-1017/use-this-package:
|
||||
en: a6d5fcecfa93935b
|
||||
zh: 8ae2809a06910c52
|
||||
/deepseek-ai-dsh-client-ui-deepseek-model-label-1017/understand-the-implementation:
|
||||
en: ca1d0c5db1b819a0
|
||||
zh: 960e8db56fa5987e
|
||||
/deepseek-ai-dsh-client-ui-deepseek-model-label-1017/further-exploration:
|
||||
en: 2885099078047d38
|
||||
zh: d7de1db3f1ba0a50
|
||||
/deepseek-ai-dsh-client-ui-deepseek-model-label-1017/model-experience:
|
||||
en: 16ab1406c87419d7
|
||||
zh: 01bce720d29ab2e8
|
||||
/deepseek-ai-dsh-client-ui-deepseek-model-label-1017/model-experience/kv-cache-effect:
|
||||
en: b7599ec8e5b66e58
|
||||
zh: a21537863b9b6e3a
|
||||
/deepseek-ai-dsh-client-ui-deepseek-model-label-1017/known-limitations-and-deferred-work:
|
||||
en: bce774c6d8ae9428
|
||||
zh: b9d7019acc7bbc19
|
||||
/deepseek-ai-dsh-client-ui-deepseek-model-label-1017/known-limitations-and-deferred-work/dev-note:
|
||||
en: e67f633dc6fe7773
|
||||
zh: 8ec3e73528ddb1a6
|
||||
@@ -1,91 +0,0 @@
|
||||
---
|
||||
description: "Show a temporary 1017 badge beside DeepSeek Flash in the composer model selector."
|
||||
kind: "package-reference"
|
||||
---
|
||||
|
||||
# @deepseek-ai/dsh-client-ui-deepseek-model-label-1017
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
This package shows `1017` beside `DeepSeek-V4.1-Flash` in the composer model selector. It covers the official API-key and account routes while preserving their catalog names and request IDs. Disable it to restore plain model names.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Use this package](#use-this-package)
|
||||
- [Understand the implementation](#understand-the-implementation)
|
||||
- [Further Exploration](#further-exploration)
|
||||
- [Model Experience](#model-experience)
|
||||
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
||||
- [Dev Note](#dev-note)
|
||||
|
||||
-----
|
||||
|
||||
<a id="use-this-package"></a>
|
||||
## Use this package
|
||||
|
||||
The Web bundle includes this plugin, including when Desktop uses that bundle. It has no configuration fields.
|
||||
|
||||
The badge appears only when the provider is `deepseek-official` or `deepseek-account`, the model ID is `deepseek-flash`, and the catalog name is exactly `DeepSeek-V4.1-Flash`. Other names and providers keep their catalog text. The input button, current-model menu row, and model options share the badge.
|
||||
|
||||
Disable the following row through a profile patch to restore the catalog name:
|
||||
|
||||
```yaml
|
||||
- id: ui-deepseek-model-label-1017
|
||||
disabled: true
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## Understand the implementation
|
||||
|
||||
<details>
|
||||
<summary>Implementation internals — click to expand</summary>
|
||||
|
||||
The browser entry contributes to the existing `conversation.input.model.label` slot through `ctx.slots.inject()`. Its component derives the label from owner props and uses the shared `Tag` primitive; it owns no cache, subscription, network request, or model-selection state. Registration and dictionaries leave with the plugin. The Host entry only makes the browser package available to the Loader.
|
||||
|
||||
</details>
|
||||
|
||||
-----
|
||||
|
||||
<a id="further-exploration"></a>
|
||||
## Further Exploration
|
||||
|
||||
These packages own the selector and its two directory sources.
|
||||
|
||||
- [Model selection](../ui-model-selection/README.md) — label placement and selection behavior.
|
||||
- [DeepSeek transport](../../llm/llm-deepseek/README.md) — built-in model catalog.
|
||||
- [DeepSeek account](../../llm/llm-deepseek-account/README.md) — account-provided model directory.
|
||||
|
||||
-----
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## Model Experience
|
||||
|
||||
None, as the package only decorates browser labels without changing model requests or Session events.
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
None; provider requests and their cached prefixes are unchanged.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
<a id="known-limitations-and-deferred-work"></a>
|
||||
|
||||
The fixed release marker has these presentation limits:
|
||||
|
||||
- The settings editor, `/model` command, trigger accessibility text, and existing tooltip keep the original catalog name.
|
||||
- This package occupies a `single` slot. Another model-label plugin at the same priority conflicts; the two presentations do not compose automatically.
|
||||
- The `1017` marker is fixed client presentation, not a server capability assertion. Remove this package and its bundle row when structured release labels replace it.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
<details>
|
||||
<summary>Working context for maintainers — click to expand</summary>
|
||||
|
||||
None.
|
||||
|
||||
</details>
|
||||
@@ -1,91 +0,0 @@
|
||||
---
|
||||
description: "在输入框的模型选择器中为 DeepSeek Flash 显示临时的 1017 角标。"
|
||||
kind: "package-reference"
|
||||
---
|
||||
|
||||
# @deepseek-ai/dsh-client-ui-deepseek-model-label-1017
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
## 概述
|
||||
|
||||
本包在输入框模型选择器的 `DeepSeek-V4.1-Flash` 旁显示 `1017`。它覆盖官方 API Key 和账号通道,保留目录名称和请求 ID。停用后恢复纯模型名称。
|
||||
|
||||
## 目录
|
||||
|
||||
- [使用本包](#use-this-package)
|
||||
- [理解实现](#understand-the-implementation)
|
||||
- [进一步探索](#further-exploration)
|
||||
- [模型体验](#model-experience)
|
||||
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
||||
- [开发备注](#dev-note)
|
||||
|
||||
-----
|
||||
|
||||
<a id="use-this-package"></a>
|
||||
## 使用本包
|
||||
|
||||
Web bundle 包含本插件,桌面端使用该 bundle 时也会加载。本包没有配置字段。
|
||||
|
||||
只有提供方为 `deepseek-official` 或 `deepseek-account`、模型 ID 为 `deepseek-flash`,且目录名称恰好为 `DeepSeek-V4.1-Flash` 时才显示角标。其他名称和提供方保留目录文字。输入框按钮、当前模型菜单行和模型选项共用这个角标。
|
||||
|
||||
通过 profile patch 停用以下条目即可恢复目录名称:
|
||||
|
||||
```yaml
|
||||
- id: ui-deepseek-model-label-1017
|
||||
disabled: true
|
||||
```
|
||||
|
||||
-----
|
||||
|
||||
<a id="understand-the-implementation"></a>
|
||||
## 理解实现
|
||||
|
||||
<details>
|
||||
<summary>内部实现 — 点击展开</summary>
|
||||
|
||||
浏览器入口通过 `ctx.slots.inject()` 向已有的 `conversation.input.model.label` slot 注册组件。组件根据 owner props 派生标签,使用共享 `Tag` 基元,不拥有缓存、订阅、网络请求或模型选择状态。注册和字典随插件卸载。Host 入口仅让 Loader 能加载浏览器包。
|
||||
|
||||
</details>
|
||||
|
||||
-----
|
||||
|
||||
<a id="further-exploration"></a>
|
||||
## 进一步探索
|
||||
|
||||
以下包分别拥有选择器和两个目录来源。
|
||||
|
||||
- [模型选择](../ui-model-selection/README.zh.md) — 名称显示位置与选择行为。
|
||||
- [DeepSeek 传输](../../llm/llm-deepseek/README.zh.md) — 内置模型目录。
|
||||
- [DeepSeek 账号](../../llm/llm-deepseek-account/README.zh.md) — 账号提供的模型目录。
|
||||
|
||||
-----
|
||||
|
||||
<a id="model-experience"></a>
|
||||
## 模型体验
|
||||
|
||||
无,本包只装饰浏览器标签,不改变模型请求或 Session 事件。
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
无;提供方请求及其缓存前缀保持不变。
|
||||
|
||||
## 已知限制与延期工作
|
||||
|
||||
<a id="known-limitations-and-deferred-work"></a>
|
||||
|
||||
固定的版本标记有以下显示限制:
|
||||
|
||||
- 设置编辑器、`/model` 命令、触发按钮的无障碍文字及已有提示保留原始目录名称。
|
||||
- 本包占用 `single` slot。其他模型名称插件若使用相同优先级会冲突,两种展示不会自动组合。
|
||||
- `1017` 是固定的客户端展示,不是服务端能力声明。结构化版本标签替代它时,移除本包及其 bundle 条目。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
<details>
|
||||
<summary>维护者工作上下文 — 点击展开</summary>
|
||||
|
||||
无。
|
||||
|
||||
</details>
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "DeepSeek 1017 Modellplakette",
|
||||
"description": "Fügen Sie den offiziellen DeepSeek Flash-Modellnamen ein temporäres 1017-Release-Abzeichen hinzu."
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "DeepSeek 1017 model badge",
|
||||
"description": "Add a temporary 1017 release badge to official DeepSeek Flash model names."
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Insignia del modelo DeepSeek 1017",
|
||||
"description": "Agregue una insignia de lanzamiento temporal 1017 a los nombres oficiales de los modelos Flash DeepSeek."
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "Badge modèle DeepSeek 1017",
|
||||
"description": "Ajoutez un badge de version 1017 temporaire aux noms de modèles Flash DeepSeek officiels."
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "DeepSeek 1017 モデルバッジ",
|
||||
"description": "公式 DeepSeek Flash モデルの名前に、一時的な 1017 リリースバッジを表示します。"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "DeepSeek selo do modelo 1017",
|
||||
"description": "Adicione um selo de versão temporário 1017 aos nomes oficiais dos modelos do DeepSeek Flash."
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "DeepSeek Значок модели 1017",
|
||||
"description": "Добавьте временный значок выпуска 1017 к официальным названиям моделей Flash DeepSeek."
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "DeepSeek 1017 模型标记",
|
||||
"description": "为官方 DeepSeek Flash 模型名称添加临时的 1017 版本标记。"
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-client-ui-deepseek-model-label-1017",
|
||||
"description": "Temporary 1017 release badge for official DeepSeek Flash model labels",
|
||||
"version": "0.2.1-rc.4",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
||||
"directory": "packages/client/ui-deepseek-model-label-1017"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"./client": {
|
||||
"types": "./lib/types/client/index.d.ts",
|
||||
"default": "./lib/client.js"
|
||||
},
|
||||
"./src/*": "./src/*",
|
||||
"./locale/*.json": "./locale/*.json",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dsh": {
|
||||
"client": {
|
||||
"inject": [
|
||||
"@deepseek-ai/dsh-client-ui-model-selection",
|
||||
"@deepseek-ai/dsh-client-ui-renderer",
|
||||
"@deepseek-ai/dsh-client-locale"
|
||||
],
|
||||
"platform": "web"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"bundle": "tsdown",
|
||||
"watch": "tsdown --watch"
|
||||
},
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:~"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-model-selection": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-renderer": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:*",
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:*",
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"files": [
|
||||
"locale/*.json",
|
||||
"lib/index.js",
|
||||
"lib/client.js",
|
||||
"lib/types/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
.label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
vertical-align: baseline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/** Display-only release marking; the catalog and selected route remain unchanged. */
|
||||
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-model-selection/client'
|
||||
import { Tag } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import css from './DeepSeekModelLabel.module.css'
|
||||
|
||||
const MODEL_NAME = 'DeepSeek-V4.1-Flash'
|
||||
|
||||
/**
|
||||
* Render the fixed 1017 release label for recognized official Flash entries.
|
||||
* @param props - Provider and model identity plus the badge dictionary.
|
||||
* @returns The base name with a badge, or the untouched catalog name.
|
||||
*/
|
||||
export function DeepSeekModelLabel({ provider, model, t }:
|
||||
PropsRuntime<'conversation.input.model.label'> & PropsLocale<'deepseek-model-label-1017'>,
|
||||
) {
|
||||
const official = provider.id === 'deepseek-account' || provider.id === 'deepseek-official'
|
||||
if (!official || model.id !== 'deepseek-flash' || model.name !== MODEL_NAME) return model.name
|
||||
return <span className={css.label}>
|
||||
<span>{model.name}</span>{' '}<Tag tone="info">{t('badge.label')}</Tag>
|
||||
</span>
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/** Temporary 1017 badge for the two official DeepSeek model routes. */
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-model-selection/client'
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
|
||||
import { DeepSeekModelLabel } from './DeepSeekModelLabel.tsx'
|
||||
import { en, ja, es, fr, de, ru, pt, zh, type LabelKey } from './locales.ts'
|
||||
|
||||
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
interface LocaleNamespaceMap {
|
||||
/** The temporary model release badge. */
|
||||
'deepseek-model-label-1017': LabelKey
|
||||
}
|
||||
}
|
||||
|
||||
/** Required services: model-label registration and localized badge text. */
|
||||
export const inject = ['slots', 'locale']
|
||||
|
||||
/**
|
||||
* Decorate existing model labels while their declaring slot is mounted.
|
||||
* @param ctx - Client plugin context owning the dictionaries and registration.
|
||||
*/
|
||||
export function apply(ctx: Context): void {
|
||||
const namespace = 'deepseek-model-label-1017'
|
||||
ctx.effect(() => ctx.locale.register(namespace, { zh, en, ja, es, fr, de, ru, pt }), 'deepseek-model-label-1017: dictionaries')
|
||||
ctx.slots.inject('conversation.input.model.label', () => ctx.slots.register({
|
||||
name: 'conversation.input.model.label', locale: namespace,
|
||||
}, DeepSeekModelLabel))
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/** Localized label of the fixed release marker. */
|
||||
|
||||
/** Simplified Chinese badge copy. */
|
||||
export const zh = { 'badge.label': '1017' }
|
||||
|
||||
/** Keys owned by the release-label namespace. */
|
||||
export type LabelKey = keyof typeof zh
|
||||
|
||||
/** English badge copy. */
|
||||
export const en: Record<LabelKey, string> = { 'badge.label': '1017' }
|
||||
|
||||
/** Japanese copy, checked against the English keys. */
|
||||
export const ja = {
|
||||
'badge.label': '1017',
|
||||
} satisfies Record<keyof typeof en, string>
|
||||
|
||||
/** Spanish dictionary, checked against the English keys. */
|
||||
export const es = { 'badge.label': '1017' } satisfies Record<keyof typeof en, string>
|
||||
|
||||
/** French dictionary, checked against the English keys. */
|
||||
export const fr = { 'badge.label': '1017' } satisfies Record<keyof typeof en, string>
|
||||
|
||||
/** German dictionary, checked against the English keys. */
|
||||
export const de = { 'badge.label': '1017' } satisfies Record<keyof typeof en, string>
|
||||
|
||||
/** Russian dictionary, checked against the English keys. */
|
||||
export const ru = { 'badge.label': '1017' } satisfies Record<keyof typeof en, string>
|
||||
|
||||
/** Portuguese dictionary, checked against the English keys. */
|
||||
export const pt = { 'badge.label': '1017' } satisfies Record<keyof typeof en, string>
|
||||
@@ -1,4 +0,0 @@
|
||||
declare module '*.module.css' {
|
||||
const classes: Record<string, string>
|
||||
export default classes
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/** Loader entry for the temporary DeepSeek model-label presentation. */
|
||||
|
||||
/** Mount the browser entry through the package's client declaration. */
|
||||
export function apply(): void {}
|
||||
@@ -1,74 +0,0 @@
|
||||
// @vitest-environment jsdom
|
||||
import { expect, it, onTestFinished } from 'vitest'
|
||||
import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { SlotTestRuntime } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||
import type { ModelLabelOwner } from '@deepseek-ai/dsh-client-ui-model-selection/client'
|
||||
import * as HostPlugin from '../src/index.ts'
|
||||
import * as LabelPlugin from '../src/client/index.ts'
|
||||
|
||||
const SLOT = 'conversation.input.model.label'
|
||||
const NAME = 'DeepSeek-V4.1-Flash'
|
||||
|
||||
function owner(provider: string, name = NAME, id = 'deepseek-flash', placement: ModelLabelOwner['placement'] = 'option'): ModelLabelOwner {
|
||||
return { provider: { id: provider, name: provider }, model: { id, name }, placement, selected: true }
|
||||
}
|
||||
|
||||
async function runtime() {
|
||||
const host = await SlotTestRuntime.create()
|
||||
onTestFinished(() => host.dispose())
|
||||
const locale = new LocaleRuntime(host.ctx)
|
||||
host.ctx.provide('locale', locale)
|
||||
host.slots.installLocale(locale)
|
||||
await host.mount(HostPlugin)
|
||||
await host.sessions.add({ id: SessionId('release-badge') })
|
||||
const session = host.sessions.retainFor(host.ctx, SessionId('release-badge'))
|
||||
return { host, session }
|
||||
}
|
||||
|
||||
const placements = ['trigger', 'menu', 'option'] as const
|
||||
const targets = ['deepseek-official', 'deepseek-account'].flatMap(provider => placements.map(placement => ({ provider, placement })))
|
||||
it.each(targets)('shows the badge for $provider at $placement without rewriting its catalog entry', async ({ provider, placement }) => {
|
||||
const { host, session } = await runtime()
|
||||
await host.declare({ [SLOT]: { kind: 'single', scope: 'session', inline: true } })
|
||||
await host.mount(LabelPlugin)
|
||||
const input = owner(provider, NAME, 'deepseek-flash', placement)
|
||||
const view = host.renderSlot(SLOT, input, { session, fallback: input.model.name })
|
||||
expect(view.container.textContent).toBe(`${NAME} 1017`)
|
||||
expect(view.view.getByText('1017').tagName).toBe('SPAN')
|
||||
expect(input.model).toEqual({ id: 'deepseek-flash', name: NAME })
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ label: 'another provider', value: owner('external') },
|
||||
{ label: 'another model id', value: owner('deepseek-official', NAME, 'other-model') },
|
||||
{ label: 'a custom name', value: owner('deepseek-account', 'My Flash') },
|
||||
{ label: 'a dated name', value: owner('deepseek-account', `${NAME}-1017`) },
|
||||
{ label: 'a later release name', value: owner('deepseek-official', `${NAME}-1024`) },
|
||||
])('preserves $label', async ({ value }) => {
|
||||
const { host, session } = await runtime()
|
||||
await host.declare({ [SLOT]: { kind: 'single', scope: 'session', inline: true } })
|
||||
await host.mount(LabelPlugin)
|
||||
const view = host.renderSlot(SLOT, value, { session, fallback: value.model.name })
|
||||
expect(view.container.textContent).toBe(value.model.name)
|
||||
expect(view.view.queryByText('1017')).toBeNull()
|
||||
})
|
||||
|
||||
it('waits for the label declaration, follows changed model props, and restores the fallback on unload', async () => {
|
||||
const { host, session } = await runtime()
|
||||
const plugin = await host.mount(LabelPlugin)
|
||||
expect(host.slots.entries(SLOT)).toHaveLength(0)
|
||||
await host.declare({ [SLOT]: { kind: 'single', scope: 'session', inline: true } })
|
||||
const input = owner('deepseek-official')
|
||||
const view = host.renderSlot(SLOT, input, { session, fallback: NAME })
|
||||
expect(view.container.textContent).toBe(`${NAME} 1017`)
|
||||
view.update(owner('deepseek-official', 'My Flash'))
|
||||
expect(view.container.textContent).toBe('My Flash')
|
||||
view.update(input)
|
||||
expect(view.container.textContent).toBe(`${NAME} 1017`)
|
||||
await plugin.dispose()
|
||||
expect(host.slots.entries(SLOT)).toHaveLength(0)
|
||||
expect(view.container.textContent).toBe(NAME)
|
||||
await host.mount(LabelPlugin)
|
||||
expect(view.container.textContent).toBe(`${NAME} 1017`)
|
||||
})
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.client.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../locale/tsconfig.client.json"
|
||||
},
|
||||
{
|
||||
"path": "../ui-model-selection"
|
||||
},
|
||||
{
|
||||
"path": "../ui-renderer"
|
||||
},
|
||||
{
|
||||
"path": "../ui-primitives"
|
||||
},
|
||||
{
|
||||
"path": "../ui-slots"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import { clientBundle } from '../tsdown.client.ts'
|
||||
|
||||
export default clientBundle(import.meta.dirname, ['lib/types/index.js'])
|
||||
@@ -21,8 +21,8 @@
|
||||
en: 2e6d9965e01dc0ce
|
||||
zh: 332cc82233eb02f3
|
||||
/deepseek-ai-dsh-client-ui-model-selection/use-this-package/model-label-extensions:
|
||||
en: bc47871cf8b61451
|
||||
zh: 17d21da46604f071
|
||||
en: 23610fb9daf9918a
|
||||
zh: 9f63a97901be100c
|
||||
/deepseek-ai-dsh-client-ui-model-selection/use-this-package/unroutable-sessions:
|
||||
en: 7f20428c747ff052
|
||||
zh: 975e6de728e02fa0
|
||||
@@ -30,8 +30,8 @@
|
||||
en: 261bbf42296243af
|
||||
zh: 72779fb7b8fbc473
|
||||
/deepseek-ai-dsh-client-ui-model-selection/understand-the-implementation:
|
||||
en: f3d38594439eab3d
|
||||
zh: f255974e61696dca
|
||||
en: 56003671ca5acf24
|
||||
zh: 251e02201ea82142
|
||||
/deepseek-ai-dsh-client-ui-model-selection/further-exploration:
|
||||
en: 14acd4692e923480
|
||||
zh: ff89aed852aa0da8
|
||||
@@ -42,8 +42,8 @@
|
||||
en: 68e54c00412a40d1
|
||||
zh: 0ac96f57fd194d96
|
||||
/deepseek-ai-dsh-client-ui-model-selection/known-limitations-and-deferred-work:
|
||||
en: 75b37954ceacd92a
|
||||
zh: a49d0361486e35c4
|
||||
en: 7003aff3aa57e40c
|
||||
zh: 450d88a5e9332f05
|
||||
/deepseek-ai-dsh-client-ui-model-selection/known-limitations-and-deferred-work/dev-note:
|
||||
en: e67f633dc6fe7773
|
||||
zh: 3bb0c20c93f16f98
|
||||
|
||||
@@ -49,7 +49,7 @@ The composer replaces the model and effort text with the Models icon when the ex
|
||||
|
||||
`conversation.input.model.label` is a session-scoped, inline `single` slot for the composer trigger (`placement: 'trigger'`), the root menu's current value (`'menu'`), and each model option (`'option'`). Its owner props provide `provider: { id, name }`, the exact catalog `model`, `placement`, and `selected`. Provider names are localized display names; match channels by `provider.id`, and identify a model by the provider/model ID pair. Connection URLs and credentials are not passed to the slot.
|
||||
|
||||
Contributions must render non-interactive inline content, such as a colored name or badge. The selector owns clicks, search, focus, disabled states, check marks, reasoning effort, and trigger accessibility text. An absent contribution, including after plugin disposal, displays the catalog name. The trigger and root-menu value use the slot only when the selected model is present in the displayed catalog; otherwise they keep the built-in placeholder. During refresh, retained catalog entries continue to use the slot. Open menus track label size changes to stay inside the viewport. `/model` command rows use the command popup's separate rendering API.
|
||||
Contributions must render non-interactive inline content, such as a colored name or badge. The selector owns clicks, search, focus, disabled states, check marks, reasoning effort, and trigger accessibility text. An absent contribution, including after plugin disposal, displays the catalog name with its optional `model.badge` before the name in the trigger and after it in menus: a `Tag` in the badge's tone whose text is resolved for the active locale. Long catalog names ellipsize within the available width while the badge remains visible. A contribution replaces both and receives the badge in its owner props. The trigger and root-menu value use the slot only when the selected model is present in the displayed catalog; otherwise they keep the built-in placeholder. During refresh, retained catalog entries continue to use the slot. Open menus track label size changes to stay inside the viewport. `/model` command rows use the command popup's separate rendering API.
|
||||
|
||||
Import this package's `/client` declarations type-only, then register through `ctx.slots.inject('conversation.input.model.label', () => ctx.slots.register({ name: 'conversation.input.model.label' }, ModelLabel))`. Derive `ModelLabel` props from `PropsRuntime<'conversation.input.model.label'>`; use provider IDs to choose CSS Module classes and retain `model.name` as the text. Registration follows the declaration and contributing plugin lifetimes. Without a component replacement, styles can target the enclosing label's `data-provider-id` and `data-model-id` attributes in all three placements.
|
||||
|
||||
@@ -73,7 +73,7 @@ Menus use the shared `MenuSurface` material, including the macOS backing for bac
|
||||
|
||||
The composer `ModelSelect` and `/model` option builder share [provider ordering](src/client/provider-order.ts), while both searches use `rankByName` within each provider. The command supplies optional groups and `searchMode: 'fuzzy-label'` through the [popupSelect API](../ui-commands/README.md#use-this-package); both entries use `MenuGroup` and rebuild its sticky observer when the rendered groups change. The command popup fills the composer overlay, while the button retains its compact menu.
|
||||
|
||||
Two entries over ONE per-session directory owned by `ModelDirectoryResolver` (`ctx.modelDirectories`): the `/model` popupSelect contribution (registered through `ctx.commandUi`) and the composer's named `conversation.input.model` seat both load the session's available directory through `session.models` and submit through `session.selectModel` via the same `ModelDirectory` instance, so a switch made in either entry is what the other shows next. Directory loads and selections share a generation counter so an older response never overwrites a newer one. The directory publishes the latest submitted selection as `pending` until it settles or a connection reset invalidates it; a connection reset drops every resident projection and repulls the Host-restored selection before display. Directories are per-session, resolved lazily, and disposed with the session scope; addressed subagent sessions expose neither entry. Every resident directory refetches directly on forwarded `llm/adapters-updated`, `settings/document-updated`, and credential update events.
|
||||
Two entries over ONE per-session directory owned by `ModelDirectoryResolver` (`ctx.modelDirectories`): the `/model` popupSelect contribution (registered through `ctx.commandUi`) and the composer's named `conversation.input.model` seat both load the session's available directory through `session.models` and submit through `session.selectModel` via the same `ModelDirectory` instance, so a switch made in either entry is what the other shows next. Directory loads and selections share a generation counter so an older response never overwrites a newer one. The directory publishes the latest submitted selection as `pending` until it settles or a connection reset invalidates it; a connection reset drops every resident projection and repulls the Host-restored selection before display. Directories are per-session, resolved lazily, and disposed with the session scope; addressed subagent sessions expose neither entry. Every resident directory refetches directly on forwarded `llm/adapters-updated`, `llm/models-updated`, `settings/document-updated`, and credential update events.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -109,6 +109,7 @@ These limits define the current model surface. They are current package constrai
|
||||
- **No create-time or addressed-subagent selection** — both entries require an existing ordinary session's Agent; there is no draft-phase model choice to fold into session creation, and subagent continuation deliberately exposes no independent model-selection contract.
|
||||
- **Directory names are presentation-only** — selection and persistence use provider/model/effort ids; a provider whose catalog or exact-model metadata lookup fails lists as an unselectable failure row until reload.
|
||||
- **No arbitrary effort input** — the composer offers only the exact model's adapter-advertised levels; an adapter without reasoning metadata leaves the Effort row absent.
|
||||
- **Badges label the composer seat only** — `/model` popup rows, the trigger's accessible name, and its tooltip keep the plain catalog name.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
@@ -49,7 +49,7 @@ composer 菜单中的模型名称和推理等级均使用 400(regular)字重
|
||||
|
||||
`conversation.input.model.label` 是会话作用域的内联 `single` slot,覆盖 composer 触发按钮(`placement: 'trigger'`)、根菜单中的当前值(`'menu'`)和各模型选项(`'option'`)。owner props 提供 `provider: { id, name }`、准确的目录 `model`、`placement` 和 `selected`。提供方名称是本地化显示名称;按 `provider.id` 匹配渠道,按提供方与模型 ID 的组合识别模型。slot 不接收连接 URL 或凭据。
|
||||
|
||||
扩展必须渲染非交互的内联内容,例如带颜色的名称或徽标。选择器负责点击、搜索、焦点、禁用状态、勾选标记、推理强度和触发按钮的无障碍文字。没有扩展(包括插件卸载后)时显示目录名称。触发按钮和根菜单当前值仅在所选模型存在于显示目录中时调用 slot,否则保留内置占位文字。刷新期间保留的目录条目仍调用 slot。菜单打开时会跟随名称内容的尺寸变化调整位置,保持在视口内。`/model` 命令行使用命令弹窗独立的渲染 API。
|
||||
扩展必须渲染非交互的内联内容,例如带颜色的名称或徽标。选择器负责点击、搜索、焦点、禁用状态、勾选标记、推理强度和触发按钮的无障碍文字。没有扩展(包括插件卸载后)时显示目录名称;模型带有目录角标 `model.badge` 时,触发按钮在名称前显示一个按角标语调着色的 `Tag`,菜单仍放在名称后,文字按当前语言解析。较长的目录名称会在可用宽度内省略,角标保持可见。扩展会同时替换两者,并从 owner props 收到角标。触发按钮和根菜单当前值仅在所选模型存在于显示目录中时调用 slot,否则保留内置占位文字。刷新期间保留的目录条目仍调用 slot。菜单打开时会跟随名称内容的尺寸变化调整位置,保持在视口内。`/model` 命令行使用命令弹窗独立的渲染 API。
|
||||
|
||||
以 type-only 方式导入本包的 `/client` 声明,再通过 `ctx.slots.inject('conversation.input.model.label', () => ctx.slots.register({ name: 'conversation.input.model.label' }, ModelLabel))` 注册。`ModelLabel` 的 props 从 `PropsRuntime<'conversation.input.model.label'>` 派生;用提供方 ID 选择 CSS Module 类,并保留 `model.name` 作为文字。注册随声明和贡献插件的生命周期清理。不替换组件时,样式也可以在三个显示位置通过名称外层的 `data-provider-id` 和 `data-model-id` 属性定位。
|
||||
|
||||
@@ -73,7 +73,7 @@ composer 菜单中的模型名称和推理等级均使用 400(regular)字重
|
||||
|
||||
composer 的 `ModelSelect` 与 `/model` 选项构建器共用[提供方排序](src/client/provider-order.ts),两个搜索框均在每个提供方内使用 `rankByName`。命令通过 [popupSelect API](../ui-commands/README.zh.md#use-this-package) 提供可选分组与 `searchMode: 'fuzzy-label'`;两个入口均使用 `MenuGroup`,并在渲染分组变化时重建其吸顶观察器。命令弹窗撑满 composer 浮层,按钮则保留紧凑菜单。
|
||||
|
||||
两个入口共用一份由 `ModelDirectoryResolver`(`ctx.modelDirectories`)持有的会话级目录:`/model` popupSelect 贡献项(经 `ctx.commandUi` 注册)与 composer 的具名 `conversation.input.model` 位都经 `session.models` 加载会话的可用目录、经 `session.selectModel` 通过同一个 `ModelDirectory` 实例提交,因此任一入口所做的切换正是另一个入口接下来显示的。目录加载与选择共享一个代次计数器,旧响应不会覆盖新结果。目录把最近一次提交的选择发布为 `pending`,直到它完成或被连接重置作废;连接重置丢弃所有常驻投影,并在显示前重新拉取 Host 恢复的选择。目录按会话惰性解析,随会话作用域一并 dispose(资源释放);已寻址 subagent 会话不公开任一入口。每份常驻目录都会直接在转发的 `llm/adapters-updated`、`settings/document-updated` 与凭据更新事件上重拉。
|
||||
两个入口共用一份由 `ModelDirectoryResolver`(`ctx.modelDirectories`)持有的会话级目录:`/model` popupSelect 贡献项(经 `ctx.commandUi` 注册)与 composer 的具名 `conversation.input.model` 位都经 `session.models` 加载会话的可用目录、经 `session.selectModel` 通过同一个 `ModelDirectory` 实例提交,因此任一入口所做的切换正是另一个入口接下来显示的。目录加载与选择共享一个代次计数器,旧响应不会覆盖新结果。目录把最近一次提交的选择发布为 `pending`,直到它完成或被连接重置作废;连接重置丢弃所有常驻投影,并在显示前重新拉取 Host 恢复的选择。目录按会话惰性解析,随会话作用域一并 dispose(资源释放);已寻址 subagent 会话不公开任一入口。每份常驻目录都会直接在转发的 `llm/adapters-updated`、`llm/models-updated`、`settings/document-updated` 与凭据更新事件上重拉。
|
||||
|
||||
</details>
|
||||
|
||||
@@ -109,6 +109,7 @@ composer 的 `ModelSelect` 与 `/model` 选项构建器共用[提供方排序](s
|
||||
- **无创建期或已寻址 subagent 选择**——两个入口都要求既有普通会话的 agent(智能体);没有可纳入会话创建的草稿阶段模型选择,subagent 继续执行也有意不公开独立的模型选择约定。
|
||||
- **目录名仅供呈现**——选择与持久化使用提供方/模型/推理强度 id;目录查询或确切模型元数据查询失败的提供方以不可选失败行列出,重新加载前保持原样。
|
||||
- **不能任意输入推理强度**——composer 仅提供确切模型由适配器公布的推理强度;适配器没有推理元数据时不显示 Effort 行。
|
||||
- **角标只出现在 composer 模型位**——`/model` 弹窗各行、触发按钮的无障碍名称及其提示仍只显示目录名称。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
@@ -250,6 +250,10 @@
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
}
|
||||
|
||||
.modelOption {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.option:not(.modelOption):hover:not(:disabled) {
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
@@ -284,6 +288,25 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badgedName {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
gap: 4px;
|
||||
vertical-align: baseline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badgedNameText {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.badge {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.check {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
@@ -37,7 +37,7 @@ import clsx from 'clsx'
|
||||
import type { ModelReasoningEffort, ModelSelection } from '@deepseek-ai/dsh-api-remotes/client'
|
||||
import {
|
||||
IconCheckOutlineRegular, IconChevronDownOutlineRegular, IconChevronRightOutlineRegular, IconCloseFillRegular,
|
||||
IconDataOutlineRegular, IconWarningOutlineRegular, Input, isMobileDevice, rankByName, StateDot, Toast,
|
||||
IconDataOutlineRegular, IconWarningOutlineRegular, Input, isMobileDevice, rankByName, StateDot, Tag, Toast,
|
||||
} from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { PropsLocale, PropsRenderSlots, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import type { ModelLabelOwner, ModelSelectInjected } from './slots.ts'
|
||||
@@ -64,7 +64,7 @@ const MEASURE_STYLE: CSSProperties = { visibility: 'hidden', left: 0, top: 0 }
|
||||
* @returns the trigger and, while open, the two-level menu.
|
||||
*/
|
||||
export function ModelSelect(
|
||||
{ locked, available, directory, load, select, t, renderSlot }:
|
||||
{ locked, available, directory, load, select, resolveText, t, renderSlot }:
|
||||
ModelSelectInjected & Pick<PropsRuntime<'conversation.input.model'>, 'locked'> & PropsLocale<'model'>
|
||||
& Omit<PropsRenderSlots<'conversation.input.model.label'>, 'SessionProvider'>,
|
||||
) {
|
||||
@@ -441,7 +441,15 @@ export function ModelSelect(
|
||||
<span data-provider-id={provider.id} data-model-id={model.id}>
|
||||
{renderSlot('conversation.input.model.label', {
|
||||
provider, model, placement, selected,
|
||||
}, { fallback: model.name })}
|
||||
}, {
|
||||
fallback: model.badge === undefined ? model.name : (
|
||||
<span className={css.badgedName}>
|
||||
{placement === 'trigger' ? <><Tag className={css.badge} tone={model.badge.tone}>{resolveText(model.badge.text)}</Tag>{' '}</> : null}
|
||||
<span className={css.badgedNameText}>{model.name}</span>
|
||||
{placement === 'trigger' ? null : <>{' '}<Tag className={css.badge} tone={model.badge.tone}>{resolveText(model.badge.text)}</Tag></>}
|
||||
</span>
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
)
|
||||
let itemIndex = 0
|
||||
|
||||
@@ -184,6 +184,7 @@ export function apply(ctx: ClientContext): void {
|
||||
load: () => {
|
||||
if (available) directory.load().catch(() => { /* surfaced on the store */ })
|
||||
},
|
||||
resolveText: text => ctx.locale.resolveText(text),
|
||||
select: (selection: ModelSelection) => available
|
||||
? directory.select(selection)
|
||||
: Promise.resolve(undefined),
|
||||
|
||||
@@ -52,6 +52,7 @@ export class ModelDirectoryResolver extends Service {
|
||||
for (const directory of this.live.directories.values) directory.resetConnected()
|
||||
})
|
||||
ctx.remote.$on('llm/adapters-updated', () => { this.catalog.refresh() })
|
||||
ctx.remote.$on('llm/models-updated', () => { this.catalog.refresh() })
|
||||
ctx.remote.$on('settings/document-updated', () => { this.catalog.refresh() })
|
||||
ctx.remote.$on('credentials/record-updated', () => { this.catalog.refresh() })
|
||||
ctx.remote.$on('credentials/reference-updated', () => { this.catalog.refresh() })
|
||||
|
||||
@@ -21,7 +21,8 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
interface SlotMap {
|
||||
/**
|
||||
* Non-interactive inline model label; selection controls remain owner-managed.
|
||||
* Without a contribution, the composer displays the catalog name.
|
||||
* Without a contribution, the badge precedes the catalog name in the trigger
|
||||
* and follows it in menus.
|
||||
* @example
|
||||
* return {
|
||||
* inject: ['slots'],
|
||||
@@ -50,6 +51,12 @@ export interface ModelSelectInjected {
|
||||
directory: SnapshotStore<ModelDirectoryState>
|
||||
/** Ensure the shared advisory catalog is loaded (errors land on the store). */
|
||||
load: () => void
|
||||
/**
|
||||
* Choose a catalog badge's provider-authored text for the active locale.
|
||||
* @param text - the badge's literal text or translations.
|
||||
* @returns the text to display.
|
||||
*/
|
||||
resolveText: (text: NonNullable<ModelCatalogModel['badge']>['text']) => string
|
||||
/**
|
||||
* Select a complete provider/model/reasoning selection.
|
||||
* @param selection - model selection and optional adapter-owned effort.
|
||||
|
||||
@@ -227,6 +227,14 @@ describe('ui-model-selection dual entry', () => {
|
||||
expect(b.seat().locale).toBe('model')
|
||||
})
|
||||
|
||||
it.each([['zh', '新'], ['en', 'New']] as const)('resolves seat badge text for the active %s locale', async (locale, expected) => {
|
||||
const b = await bench(locale)
|
||||
b.mint('s1')
|
||||
const face = b.seat().inject!(sid('s1'))
|
||||
expect(face.resolveText({ en: 'New', zh: '新' })).toBe(expected)
|
||||
expect(face.resolveText('1017')).toBe('1017')
|
||||
})
|
||||
|
||||
it.each(['zh', 'en'] as const)('shows names and providers without catalog descriptions (%s)', async (locale) => {
|
||||
const b = await bench(locale)
|
||||
b.mint('s1')
|
||||
@@ -384,6 +392,27 @@ describe('ui-model-selection dual entry', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('replaces and removes badges in an open directory on model catalog notifications', async () => {
|
||||
const b = await bench()
|
||||
try {
|
||||
b.mint('s1')
|
||||
const face = b.seat().inject!(sid('s1'))
|
||||
for (const badge of [{ text: 'New', tone: 'info' as const }, { text: 'Updated', tone: 'success' as const }, undefined]) {
|
||||
b.setGroups(GROUPS.map(group => ({
|
||||
...group, models: group.models.map(model => ({ ...model, ...(badge === undefined ? {} : { badge }) })),
|
||||
})))
|
||||
b.remote.emit('llm/models-updated', ['deepseek-official'])
|
||||
await vi.waitFor(() => {
|
||||
expect(face.directory.getSnapshot().status).toBe('ready')
|
||||
expect(face.directory.getSnapshot().groups[0]?.models[0]?.badge).toEqual(badge)
|
||||
})
|
||||
}
|
||||
expect(face.directory.getSnapshot().current?.model).toBe('deepseek-v4-flash')
|
||||
} finally {
|
||||
await b.ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('retains the selected model while refreshing its catalog', async () => {
|
||||
const b = await bench()
|
||||
b.mint('s1')
|
||||
|
||||
@@ -25,6 +25,8 @@ const t: ComponentProps<typeof ModelSelect>['t'] = (key, params) => {
|
||||
|
||||
const renderLabel: ComponentProps<typeof ModelSelect>['renderSlot'] = (_name, _owner, options) => options?.fallback ?? null
|
||||
|
||||
const resolveText: ComponentProps<typeof ModelSelect>['resolveText'] = text => typeof text === 'string' ? text : text.en
|
||||
|
||||
const reasoning = {
|
||||
efforts: [
|
||||
{ id: 'off', name: 'Off' },
|
||||
@@ -97,7 +99,8 @@ describe('ModelSelect label slot', () => {
|
||||
await runtime.root.declare({
|
||||
'conversation.input.model.label': { kind: 'single', scope: 'session', inline: true },
|
||||
}, ({ renderSlot, SessionProvider }) => <SessionProvider session={session}>
|
||||
<ModelSelect locked={false} available directory={directory} load={vi.fn()} select={select} t={t} renderSlot={renderSlot} />
|
||||
<ModelSelect locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={select} t={t} renderSlot={renderSlot} />
|
||||
</SessionProvider>)
|
||||
runtime.renderRoot()
|
||||
const trigger = screen.getByRole('button', { name: '选择模型,当前 Same model' })
|
||||
@@ -140,7 +143,8 @@ describe('ModelSelect label slot', () => {
|
||||
it('does not decorate loading or missing selections with a stale provider', () => {
|
||||
const directory = createSnapshotStore(state({ groups: [], status: 'loading' }))
|
||||
const renderSlot = vi.fn(() => null)
|
||||
render(<ModelSelect locked={false} available directory={directory} load={vi.fn()} select={vi.fn()} t={t} renderSlot={renderSlot} />)
|
||||
render(<ModelSelect locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={vi.fn()} t={t} renderSlot={renderSlot} />)
|
||||
expect(renderSlot).not.toHaveBeenCalled()
|
||||
expect(document.querySelector('[data-provider-id]')).toBeNull()
|
||||
act(() => { directory.set(state({ groups: [] })) })
|
||||
@@ -149,6 +153,57 @@ describe('ModelSelect label slot', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('ModelSelect catalog badges', () => {
|
||||
const BADGE = { text: { en: 'New', zh: '新' }, tone: 'info' } as const
|
||||
const badged = () => state({
|
||||
current: { provider: 'deepseek-official', model: 'deepseek-flash' },
|
||||
groups: [{
|
||||
id: 'deepseek-official',
|
||||
name: 'DeepSeek',
|
||||
models: [
|
||||
{ id: 'deepseek-flash', name: 'DeepSeek-V4.1-Flash', badge: BADGE },
|
||||
{ id: 'deepseek-v4-pro', name: 'DeepSeek-V4-Pro' },
|
||||
],
|
||||
}],
|
||||
})
|
||||
const chinese: ComponentProps<typeof ModelSelect>['resolveText'] = text => typeof text === 'string' ? text : text.zh ?? text.en
|
||||
|
||||
it('places the badge before the trigger name and after names in the menus', () => {
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={createSnapshotStore(badged())}
|
||||
load={vi.fn()} resolveText={chinese} select={vi.fn()} t={t} />)
|
||||
const trigger = screen.getByRole('button', { name: '选择模型,当前 DeepSeek-V4.1-Flash' })
|
||||
expect(trigger.textContent).toBe('新 DeepSeek-V4.1-Flash')
|
||||
expect(screen.getByText('新').getAttribute('data-tone')).toBe('info')
|
||||
fireEvent.click(trigger)
|
||||
const current = screen.getByRole('menuitem', { name: /^模型/ })
|
||||
expect(current.textContent).toContain('DeepSeek-V4.1-Flash 新')
|
||||
fireEvent.click(current)
|
||||
expect(screen.getAllByRole('menuitemradio').map(row => row.textContent)).toEqual(['DeepSeek-V4.1-Flash 新', 'DeepSeek-V4-Pro'])
|
||||
})
|
||||
|
||||
it('hands the badge to a label contribution, which replaces the default rendering', async () => {
|
||||
const runtime = await SlotTestRuntime.create()
|
||||
onTestFinished(() => runtime.dispose())
|
||||
const sessionId = SessionId('model-badge')
|
||||
await runtime.sessions.add({ id: sessionId })
|
||||
const session = runtime.sessions.retainFor(runtime.ctx, sessionId)
|
||||
await runtime.root.declare({
|
||||
'conversation.input.model.label': { kind: 'single', scope: 'session', inline: true },
|
||||
}, ({ renderSlot, SessionProvider }) => <SessionProvider session={session}>
|
||||
<ModelSelect locked={false} available directory={createSnapshotStore(badged())} load={vi.fn()} resolveText={chinese}
|
||||
select={vi.fn()} t={t} renderSlot={renderSlot} />
|
||||
</SessionProvider>)
|
||||
runtime.renderRoot()
|
||||
await runtime.mount({ inject: ['slots'], apply(ctx) {
|
||||
ctx.slots.inject('conversation.input.model.label', () => ctx.slots.register({
|
||||
name: 'conversation.input.model.label',
|
||||
}, ({ model }) => <span>{model.name} [{model.badge?.tone ?? 'none'}]</span>))
|
||||
} })
|
||||
expect(screen.getByRole('button', { name: '选择模型,当前 DeepSeek-V4.1-Flash' }).textContent).toBe('DeepSeek-V4.1-Flash [info]')
|
||||
expect(screen.queryByText('新')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('ModelSelect reasoning effort', () => {
|
||||
it('renders effort names without descriptions and submits the effort as part of the session selection', async () => {
|
||||
const directory = createSnapshotStore<ModelDirectoryState>(state())
|
||||
@@ -160,7 +215,7 @@ describe('ModelSelect reasoning effort', () => {
|
||||
locked={false}
|
||||
available
|
||||
directory={directory}
|
||||
load={vi.fn()}
|
||||
load={vi.fn()} resolveText={resolveText}
|
||||
select={select}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -203,7 +258,7 @@ describe('ModelSelect reasoning effort', () => {
|
||||
locked={false}
|
||||
available
|
||||
directory={directory}
|
||||
load={vi.fn()}
|
||||
load={vi.fn()} resolveText={resolveText}
|
||||
select={vi.fn().mockResolvedValue({ ok: true, value: undefined })}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -225,7 +280,7 @@ describe('ModelSelect reasoning effort', () => {
|
||||
locked={false}
|
||||
available
|
||||
directory={directory}
|
||||
load={vi.fn()}
|
||||
load={vi.fn()} resolveText={resolveText}
|
||||
select={select}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -246,7 +301,8 @@ describe('ModelSelect reasoning effort', () => {
|
||||
|
||||
it.each(['model', 'provider'])('hides the saved id and effort when the selected %s disappears', (removed) => {
|
||||
const directory = createSnapshotStore(state({ retainedEffort: 'High' }))
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()} select={vi.fn()} t={t} />)
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={vi.fn()} t={t} />)
|
||||
expect(screen.getByRole('button', { name: /选择模型,当前/ }).textContent).toContain('DeepSeek-V4-Flash')
|
||||
act(() => { directory.update((snapshot) => {
|
||||
snapshot.groups = removed === 'provider' ? [] : snapshot.groups.map(group => ({ ...group, models: [] }))
|
||||
@@ -272,7 +328,7 @@ describe('ModelSelect reasoning effort', () => {
|
||||
locked={false}
|
||||
available
|
||||
directory={directory}
|
||||
load={vi.fn()}
|
||||
load={vi.fn()} resolveText={resolveText}
|
||||
select={vi.fn().mockResolvedValue({ ok: true, value: undefined })}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -308,7 +364,7 @@ describe('ModelSelect reasoning effort', () => {
|
||||
locked={false}
|
||||
available
|
||||
directory={directory}
|
||||
load={vi.fn()}
|
||||
load={vi.fn()} resolveText={resolveText}
|
||||
select={select}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -351,7 +407,8 @@ describe('ModelSelect reasoning effort', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()} select={select} t={t} />)
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={select} t={t} />)
|
||||
const spinners = () => document.querySelectorAll('[data-state="ongoing"]')
|
||||
|
||||
const trigger = screen.getByRole('button', { name: /选择模型|当前/ })
|
||||
@@ -380,7 +437,8 @@ describe('ModelSelect reasoning effort', () => {
|
||||
directory.set(state({ status: 'selecting', pending: selection }))
|
||||
return new Promise<undefined>(() => {})
|
||||
})
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()} select={select} t={t} />)
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={select} t={t} />)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /选择模型|当前/ }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: /推理等级/ }))
|
||||
@@ -400,7 +458,7 @@ describe('ModelSelect reasoning effort', () => {
|
||||
locked={false}
|
||||
available
|
||||
directory={createSnapshotStore(state())}
|
||||
load={vi.fn()}
|
||||
load={vi.fn()} resolveText={resolveText}
|
||||
select={vi.fn().mockResolvedValue({ ok: true, value: undefined })}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -433,7 +491,7 @@ describe('ModelSelect reasoning effort', () => {
|
||||
locked={false}
|
||||
available={false}
|
||||
directory={createSnapshotStore(state())}
|
||||
load={load}
|
||||
load={load} resolveText={resolveText}
|
||||
select={vi.fn().mockResolvedValue(undefined)}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -450,7 +508,7 @@ describe('ModelSelect keyboard walk', () => {
|
||||
locked={false}
|
||||
available
|
||||
directory={createSnapshotStore(state())}
|
||||
load={vi.fn()}
|
||||
load={vi.fn()} resolveText={resolveText}
|
||||
select={select}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -479,7 +537,7 @@ describe('ModelSelect keyboard walk', () => {
|
||||
const select = vi.fn()
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available
|
||||
directory={createSnapshotStore(state({ groups: modelGroups(modelCount) }))}
|
||||
load={vi.fn()} select={select} t={t}
|
||||
load={vi.fn()} resolveText={resolveText} select={select} t={t}
|
||||
/>)
|
||||
const trigger = screen.getByRole('button', { name: /选择模型/ })
|
||||
fireEvent.click(trigger)
|
||||
@@ -588,7 +646,7 @@ describe('ModelSelect keyboard walk', () => {
|
||||
locked={false}
|
||||
available
|
||||
directory={createSnapshotStore(state())}
|
||||
load={vi.fn()}
|
||||
load={vi.fn()} resolveText={resolveText}
|
||||
select={vi.fn().mockResolvedValue({ ok: true, value: undefined })}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -634,7 +692,7 @@ describe('ModelSelect keyboard walk', () => {
|
||||
locked={false}
|
||||
available
|
||||
directory={directory}
|
||||
load={load}
|
||||
load={load} resolveText={resolveText}
|
||||
select={vi.fn().mockResolvedValue({ ok: true, value: undefined })}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -693,7 +751,7 @@ describe('ModelSelect keyboard walk', () => {
|
||||
locked={false}
|
||||
available
|
||||
directory={createSnapshotStore(state({ current: { provider: 'gone', model: 'gone' } }))}
|
||||
load={vi.fn()}
|
||||
load={vi.fn()} resolveText={resolveText}
|
||||
select={vi.fn().mockResolvedValue({ ok: true, value: undefined })}
|
||||
t={t}
|
||||
/>)
|
||||
@@ -710,7 +768,7 @@ describe('ModelSelect catalog size', () => {
|
||||
const groups = modelGroups(count)
|
||||
const current = { provider: 'deepseek-official', model: count > 1 ? 'model-2' : 'deepseek-v4-flash' }
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={createSnapshotStore(state({ groups, current }))}
|
||||
load={vi.fn()} select={vi.fn()} t={t} />)
|
||||
load={vi.fn()} resolveText={resolveText} select={vi.fn()} t={t} />)
|
||||
const trigger = screen.getByRole('button', { name: /选择模型/ })
|
||||
fireEvent.click(trigger)
|
||||
if (count > 0) fireEvent.click(screen.getByRole('menuitem', { name: /^模型/ }))
|
||||
@@ -733,7 +791,7 @@ describe('ModelSelect catalog size', () => {
|
||||
it.each([0, 1, 4, 5])('opens an unselected %i-model catalog on search or its first row', (count) => {
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available
|
||||
directory={createSnapshotStore(state({ groups: modelGroups(count), current: null }))}
|
||||
load={vi.fn()} select={vi.fn()} t={t} />)
|
||||
load={vi.fn()} resolveText={resolveText} select={vi.fn()} t={t} />)
|
||||
const trigger = screen.getByRole('button', { name: '请选择模型' })
|
||||
fireEvent.click(trigger)
|
||||
const rows = screen.queryAllByRole('menuitemradio')
|
||||
@@ -746,7 +804,8 @@ describe('ModelSelect catalog size', () => {
|
||||
it.each([true, false])('clears search and restores focus across 5 → 4 → 5 models (checked: %s)', (checked) => {
|
||||
const current = { provider: 'deepseek-official', model: checked ? 'model-3' : 'removed' }
|
||||
const directory = createSnapshotStore(state({ groups: modelGroups(5), current }))
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()} select={vi.fn()} t={t} />)
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={vi.fn()} t={t} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: /选择模型/ }))
|
||||
if (checked) fireEvent.click(screen.getByRole('menuitem', { name: /^模型/ }))
|
||||
const search = screen.getByRole('searchbox')
|
||||
@@ -775,7 +834,7 @@ describe('ModelSelect catalog size', () => {
|
||||
const select = vi.fn().mockResolvedValue({ ok: true, value: undefined })
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available
|
||||
directory={createSnapshotStore(state({ groups: modelGroups(4), current: { provider: 'deepseek-official', model: 'model-2' } }))}
|
||||
load={vi.fn()} select={select} t={t} />)
|
||||
load={vi.fn()} resolveText={resolveText} select={select} t={t} />)
|
||||
const trigger = screen.getByRole('button', { name: /选择模型/ })
|
||||
fireEvent.click(trigger)
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: /^模型/ }))
|
||||
@@ -806,7 +865,7 @@ describe('ModelSelect catalog size', () => {
|
||||
it('keeps Tab selection available after hovering a small-catalog row', async () => {
|
||||
const select = vi.fn().mockResolvedValue({ ok: true, value: undefined })
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={createSnapshotStore(state({ groups: modelGroups(4) }))}
|
||||
load={vi.fn()} select={select} t={t} />)
|
||||
load={vi.fn()} resolveText={resolveText} select={select} t={t} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: /选择模型/ }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: /^模型/ }))
|
||||
const row = screen.getByRole('menuitemradio', { name: 'Model 3' })
|
||||
@@ -821,7 +880,7 @@ describe('ModelSelect catalog size', () => {
|
||||
it.each(['Escape', 'Tab'])('leaves a small model pane with %s and returns to its root cell', (key) => {
|
||||
const select = vi.fn()
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={createSnapshotStore(state({ groups: modelGroups(4) }))}
|
||||
load={vi.fn()} select={select} t={t} />)
|
||||
load={vi.fn()} resolveText={resolveText} select={select} t={t} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: /选择模型/ }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: /^模型/ }))
|
||||
const row = screen.getAllByRole('menuitemradio')[0]!
|
||||
@@ -838,7 +897,7 @@ describe('ModelSelect search', () => {
|
||||
it('clears search when reopening an unselected large catalog', () => {
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available
|
||||
directory={createSnapshotStore(state({ groups: modelGroups(5), current: null }))}
|
||||
load={vi.fn()} select={vi.fn()} t={t} />)
|
||||
load={vi.fn()} resolveText={resolveText} select={vi.fn()} t={t} />)
|
||||
const trigger = screen.getByRole('button', { name: '请选择模型' })
|
||||
fireEvent.click(trigger)
|
||||
const search = screen.getByRole('searchbox')
|
||||
@@ -856,7 +915,8 @@ describe('ModelSelect search', () => {
|
||||
const directory = createSnapshotStore(state({ groups: [
|
||||
...state().groups, { id: 'empty', name: 'Empty Provider', models: [] },
|
||||
] }))
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()} select={vi.fn()} t={t} />)
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={vi.fn()} t={t} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: /选择模型/ }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: /模型/ }))
|
||||
expect(screen.queryByRole('group', { name: 'Empty Provider' })).toBeNull()
|
||||
@@ -870,7 +930,7 @@ describe('ModelSelect search', () => {
|
||||
render(<>
|
||||
<button type="button">Outside</button>
|
||||
<ModelSelect renderSlot={renderLabel} locked={false} available directory={createSnapshotStore(state({ groups: modelGroups(5) }))}
|
||||
load={vi.fn()} select={select} t={t} />
|
||||
load={vi.fn()} resolveText={resolveText} select={select} t={t} />
|
||||
</>)
|
||||
const trigger = screen.getByRole('button', { name: /选择模型/ })
|
||||
fireEvent.click(trigger)
|
||||
@@ -896,7 +956,8 @@ describe('ModelSelect search', () => {
|
||||
] },
|
||||
],
|
||||
}))
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()} select={select} t={t} />)
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={select} t={t} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: /选择模型/ }))
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: /模型/ }))
|
||||
const search = screen.getByRole('searchbox')
|
||||
@@ -948,7 +1009,8 @@ describe('ModelSelect search', () => {
|
||||
{ id: 'other', name: 'Other', models: [{ id: 'gemini', name: 'Gemini Flash' }] },
|
||||
] }))
|
||||
const select = vi.fn().mockResolvedValue({ ok: true, value: undefined })
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()} select={select} t={t} />)
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={select} t={t} />)
|
||||
const trigger = screen.getByRole('button', { name: /选择模型/ })
|
||||
fireEvent.click(trigger)
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: /模型/ }))
|
||||
@@ -994,7 +1056,8 @@ describe('ModelSelect search', () => {
|
||||
|
||||
it('hides the inherited effort until a listed model is selected', async () => {
|
||||
const directory = createSnapshotStore<ModelDirectoryState>(state({ current: null, routable: false, retainedEffort: 'High' }))
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()} select={vi.fn()} t={t} />)
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={vi.fn()} t={t} />)
|
||||
const trigger = screen.getByRole('button', { name: '请选择模型' })
|
||||
expect(trigger.hasAttribute('disabled')).toBe(false)
|
||||
await expect(`${trigger.textContent}\n`).toMatchFileSnapshot('./expected/unselected-model.txt')
|
||||
@@ -1018,7 +1081,8 @@ it('places account and official models before third-party models', async () => {
|
||||
id, name: id, models: [1, 2].map(index => ({ id: `${id}-${index}`, name: `${id}-${index}` })),
|
||||
}))
|
||||
const directory = createSnapshotStore<ModelDirectoryState>(state({ current: null, groups }))
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()} select={vi.fn()} t={t} />)
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={vi.fn()} t={t} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: '请选择模型' }))
|
||||
const names = screen.getAllByRole('menuitemradio').map(row => row.textContent)
|
||||
expect(names).toEqual([
|
||||
@@ -1036,7 +1100,7 @@ it.each([en, zh])('localizes the account group while preserving external names',
|
||||
}))
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available
|
||||
directory={createSnapshotStore(state({ current: null, groups }))}
|
||||
load={vi.fn()} select={vi.fn()} t={key => key in copy ? copy[key as keyof typeof copy] : key} />)
|
||||
load={vi.fn()} resolveText={resolveText} select={vi.fn()} t={key => key in copy ? copy[key as keyof typeof copy] : key} />)
|
||||
fireEvent.click(screen.getByRole('button', { name: copy['trigger.selectAria'] }))
|
||||
expect(screen.getByRole('group', { name: copy['provider.account'] })).toBeTruthy()
|
||||
expect(screen.getByRole('group', { name: 'My Gateway' })).toBeTruthy()
|
||||
@@ -1048,7 +1112,8 @@ it('restores the account model name after login without changing the saved route
|
||||
] }]
|
||||
const selected = { provider: 'deepseek-account', model: 'deepseek-flash', reasoningEffort: 'high' }
|
||||
const directory = createSnapshotStore(state({ current: selected, groups, retainedEffort: 'High' }))
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()} select={vi.fn()} t={t} />)
|
||||
render(<ModelSelect renderSlot={renderLabel} locked={false} available directory={directory} load={vi.fn()}
|
||||
resolveText={resolveText} select={vi.fn()} t={t} />)
|
||||
expect(screen.getByRole('button', { name: /选择模型,当前/ }).textContent).toBe('DeepSeek FlashHigh')
|
||||
act(() => { directory.update((snapshot) => { snapshot.groups = []; snapshot.routable = false }) })
|
||||
expect(screen.getByRole('button', { name: '请选择模型' }).textContent).toBe('请选择模型')
|
||||
|
||||
@@ -1292,7 +1292,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
|
||||
kind: 'single',
|
||||
scope: 'session',
|
||||
summary: 'Non-interactive inline model label; selection controls remain owner-managed.',
|
||||
doc: 'Non-interactive inline model label; selection controls remain owner-managed.\nWithout a contribution, the composer displays the catalog name.',
|
||||
doc: 'Non-interactive inline model label; selection controls remain owner-managed.\nWithout a contribution, the badge precedes the catalog name in the trigger\nand follows it in menus.',
|
||||
registerOptions: [],
|
||||
ownerProps: [
|
||||
'/** Catalog data for a model name inside the composer\'s existing controls. */\nexport interface ModelLabelOwner {\n /** Provider identity and localized display name; contains no connection configuration. */\n provider: Pick<ModelProviderGroup, \'id\' | \'name\'>\n /** The exact provider\'s model catalog entry. */\n model: ModelCatalogModel\n /** Trigger caption, root-menu current value, or selectable model row. */\n placement: \'trigger\' | \'menu\' | \'option\'\n /** Whether this provider/model pair is the current selection. */\n selected: boolean\n}',
|
||||
@@ -1325,12 +1325,10 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
|
||||
hookContext: '',
|
||||
slotInject: '',
|
||||
declaredBy: 'an entry in \'conversation.input.model\' (client-ui-model-selection), so it exists while that entry is mounted',
|
||||
occupants: [
|
||||
'client-ui-deepseek-model-label-1017 DeepSeekModelLabel',
|
||||
],
|
||||
replaceRisk: 'shadows-shipped-ui',
|
||||
occupants: [],
|
||||
replaceRisk: 'none',
|
||||
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.model.label\', () => ctx.slots.register(\n { name: \'conversation.input.model.label\' },\n ({ provider, model }) => React.createElement(\'span\', { title: provider.name }, model.name),\n ))\n },\n}',
|
||||
source: 'packages/client/ui-model-selection/src/client/slots.ts:36',
|
||||
source: 'packages/client/ui-model-selection/src/client/slots.ts:37',
|
||||
},
|
||||
{
|
||||
key: 'conversation.input.overlay',
|
||||
|
||||
@@ -4888,6 +4888,14 @@ export const EVENT_API: readonly EventApiEntry[] = [
|
||||
description: 'The provider topology changed: an adapter registered or unregistered routes, or the configurable-provider directory gained or lost entries. This payload-free registry notification fires at each commit point (including registration disposal); consumers re-read `listProviders()`, `listModels()`, or `listConfigurableProviders()` for the new state. Observer failures are contained and cannot veto the registry mutation.',
|
||||
parameters: [],
|
||||
},
|
||||
{
|
||||
name: 'llm/models-updated',
|
||||
mode: 'emit',
|
||||
signature: '\'llm/models-updated\'(provider: string): void',
|
||||
summary: 'A provider\'s advertised models or display metadata changed; consumers reload `listModels()`.',
|
||||
description: 'A provider\'s advertised models or display metadata changed; consumers reload `listModels()`.',
|
||||
parameters: [{ name: 'provider', description: 'route whose model catalog changed.' }],
|
||||
},
|
||||
{
|
||||
name: 'llm/stream',
|
||||
mode: 'waterfall',
|
||||
@@ -6720,6 +6728,14 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'LlmImageRequestPricing',
|
||||
declaration: 'export interface LlmImageRequestPricing {\n priceImages(images: readonly ImageBlock[]): readonly LlmImageRequestPrice[];\n}',
|
||||
},
|
||||
{
|
||||
name: 'LlmModelBadge',
|
||||
declaration: 'export interface LlmModelBadge {\n text: LocalizedText;\n tone: LlmModelBadgeTone;\n}',
|
||||
},
|
||||
{
|
||||
name: 'LlmModelBadgeTone',
|
||||
declaration: 'export type LlmModelBadgeTone = \'neutral\' | \'info\' | \'success\' | \'warning\';',
|
||||
},
|
||||
{
|
||||
name: 'LlmModelContext',
|
||||
declaration: 'export interface LlmModelContext {\n contextWindow: number;\n}',
|
||||
@@ -6730,7 +6746,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'LlmModelInfo',
|
||||
declaration: 'export interface LlmModelInfo {\n provider: string;\n id: string;\n name: string;\n description?: string;\n inputModalities?: readonly ModelModality[];\n}',
|
||||
declaration: 'export interface LlmModelInfo {\n provider: string;\n id: string;\n name: string;\n description?: string;\n inputModalities?: readonly ModelModality[];\n badge?: LlmModelBadge;\n}',
|
||||
},
|
||||
{
|
||||
name: 'LlmModelReasoningInfo',
|
||||
@@ -7022,7 +7038,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'ModelCatalogModel',
|
||||
declaration: 'export interface ModelCatalogModel {\n readonly id: string;\n readonly name: string;\n readonly description?: string;\n readonly reasoning?: ModelReasoning;\n}',
|
||||
declaration: 'export interface ModelCatalogModel {\n readonly id: string;\n readonly name: string;\n readonly description?: string;\n readonly reasoning?: ModelReasoning;\n readonly badge?: LlmModelBadge;\n}',
|
||||
},
|
||||
{
|
||||
name: 'ModelMessageSource',
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
en: 23386452a66b2fbd
|
||||
zh: 4cf93c4bf17b3da2
|
||||
/deepseek-ai-dsh-llm-deepseek-account/use-this-package:
|
||||
en: b92099c600203029
|
||||
zh: 5d8e48261d873793
|
||||
en: b1a9453e08198023
|
||||
zh: 630e7f566ad4d9db
|
||||
/deepseek-ai-dsh-llm-deepseek-account/understand-the-implementation:
|
||||
en: 534b344a98142d91
|
||||
zh: 1291b721c8fead84
|
||||
|
||||
@@ -29,12 +29,14 @@ Only `deepseekAccount.resolveToken(url)` supplies the token; the account service
|
||||
|
||||
The route's catalog comes from the account's `GET /models` listing at the origin of the Messages `baseURL` (`https://api.deepseek.com/models` for the official endpoint); the listing endpoint is not configurable. Listing requests carry the account token in `x-dsh-auth-token` and the harness `User-Agent`, and reject redirects.
|
||||
|
||||
The first access of a credential waits for the listing up to `modelsTimeoutMs`. Later accesses use the loaded listing and refresh it in the background once it is older than `modelsMaxAgeMs`. A failed or empty listing keeps the previous one; before any listing loads, the route serves the built-in catalog of the current version (`deepseek-flash` and `deepseek-v4-pro`). A listing HTTP 401 does not remove the stored login. Each loaded listing, the first failure of a failing streak with the catalog it falls back to, and each hidden listing entry are written to stderr with the `[llm-deepseek-account]` tag; stdout remains available for protocol messages, and secrets are never logged. Each account token has its own listing, so the service can roll a model out to some accounts first.
|
||||
The first access of a credential waits for the listing up to `modelsTimeoutMs`. After first use, the Host refreshes automatically every `modelsMaxAgeMs` (ten minutes by default), including while the page is idle. Later accesses return the cached listing while a refresh runs. A failed or empty listing retains the previous listing, including badges; before any listing loads, the route serves the built-in catalog of the current version (`deepseek-flash` and `deepseek-v4-pro`). A listing HTTP 401 does not remove the stored login. Each loaded listing, the first failure of a failing streak with the catalog it falls back to, each hidden listing entry, and each dropped badge are written to stderr with the `[llm-deepseek-account]` tag; stdout remains available for protocol messages, and secrets are never logged. Each account token has its own listing, so the service can roll a model out to some accounts first. Changed listings emit `llm/models-updated`, so open selectors reload without a page refresh. Omitted or `null` badge metadata clears a previous badge; no separate clear operation is needed. Plugin disposal stops refreshes and waits for active loads.
|
||||
|
||||
A listed model is offered only when the current DeepSeek Harness version can serve it. A model that declares `client_requirements["deepseek-harness"].min_version` is offered when the current version is at least that version in semantic-version order, including prerelease tags such as `0.2.0-alpha.1` and `0.2.0-rc.1`. A prerelease orders below its release: `min_version: 0.2.1` refuses `0.2.1-alpha.2`, so a listing that admits prereleases declares the earliest one it admits, such as `0.2.1-alpha.0`. A model without that declaration is offered only when the built-in catalog knows its id; other listed models stay hidden. A request for a listed model the current version cannot serve fails before network I/O with `CLIENT_UPGRADE_REQUIRED`, which names the required and current versions, or with `MODEL_UNSUPPORTED`. Model ids the listing does not name pass through unchanged.
|
||||
|
||||
Offered models take the listing's name, `context_window`, input modalities, and Messages system prompt and tool update modes (`api_capabilities.anthropic_messages.system_prompt_update` and `tool_update`) over the bundled entry for the same id, which keeps its image budgets and description. A mode the listing omits or sets to `null` keeps the bundled one; any value other than the modes this build reads (`in-history` for system prompts; `in-history` or `addition-only` for tools) reads as `leading-only`, which removes the bundled mode. `max_output_tokens` bounds the request default: a request that names no cap sends the smaller of `maxTokens` and that capability. The listing's effort levels are not read; every model offers `off`, `low`, `high`, and `max`.
|
||||
|
||||
An offered model shows the badge its listing entry declares under `client_metadata["deepseek-harness"].badge` beside its name in the Web model selector. `client_metadata` holds client-specific data that any build may ignore; unlike `client_requirements`, nothing under it hides a model. `badge.text` is a string, or an object keyed by lowercase language ids such as `en`, `zh`, and `ja` that includes `en`; the selector shows the active language's text and otherwise English. Each text is trimmed and must be 1 to 12 characters on one line; an emoji or a letter with combining marks counts as one character. `badge.tone` is `neutral`, `info`, `success`, or `warning`; an absent or other value shows `neutral`. An absent or `null` value at any level of that path shows no badge, because this build has no badge of its own to fall back on. When a level is not an object or the badge is unreadable, the badge is dropped while its model stays offered. Badges are display data: model requests, Session events, and model-facing tools never carry them.
|
||||
|
||||
`modelOverrides` adjusts single models without replacing the catalog. Keys are model ids; values take the fields of a [Messages `models` entry](../llm-deepseek/README.md#minimal-configuration), and unset fields inherit. Overrides apply over the listing and the bundled catalog; they cannot add models or make a refused model available. Listed capabilities do not bound an override: an override `maxTokens` above the listed `max_output_tokens` is sent as written, and the service rejects requests beyond its limits. An override that names a model neither the listing nor the built-in catalog describes, or that the listed model rejects, becomes the provider diagnostic on the Models page, which has no editor for this route. An override for a refused model that the built-in catalog does not describe is checked only once the current version can serve that model. The route has no `models` field; a configuration that still sets it fails at load and names `modelOverrides`. Edit `$DSH_HOME/profiles/<profile>/cordis.patch.yml`; the next request uses the change.
|
||||
|
||||
```yaml
|
||||
@@ -51,7 +53,7 @@ Offered models take the listing's name, `context_window`, input modalities, and
|
||||
| Field | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `modelsTimeoutMs` | `5,000` | Deadline of one listing request and of a first-access wait |
|
||||
| `modelsMaxAgeMs` | `600,000` | Listing age after which the next access refreshes it; a failed load retries after the same age |
|
||||
| `modelsMaxAgeMs` | `600,000` | Automatic refresh interval after first use, also used after failure; `0` refreshes on access only |
|
||||
| `modelOverrides` | `{}` | Field overrides by model id |
|
||||
|
||||
An account request HTTP 401 maps to `ACCOUNT_TOKEN_INVALID` independently of the response body. The account service removes the credential only if it still matches the request token. HTTP 403 and other failures do not clear the login. The account module owns task cancellation after sign-out; this plugin does not inspect Agents.
|
||||
|
||||
@@ -29,12 +29,14 @@ kind: "package-reference"
|
||||
|
||||
路由的目录来自账号的 `GET /models` 列表,地址为 Messages `baseURL` 的 origin 加 `/models`(官方端点即 `https://api.deepseek.com/models`),不可配置。列表请求用 `x-dsh-auth-token` 携带账号 token,带上 harness 的 `User-Agent`,并拒绝重定向。
|
||||
|
||||
同一凭据的第一次访问会等待列表,最长 `modelsTimeoutMs`。之后的访问直接使用已加载的列表,列表超过 `modelsMaxAgeMs` 后在后台刷新。获取失败或得到空列表时保留上一份;还没有加载到任何列表时,路由提供当前版本内置的目录(`deepseek-flash` 与 `deepseek-v4-pro`)。列表请求返回 HTTP 401 不会清除已保存的登录。每次加载成功、连续失败中的第一次失败及其退回的目录、以及每个被隐藏的列表条目,都会以 `[llm-deepseek-account]` 标签写到 stderr;stdout 保留给协议消息,且不记录任何密钥。每个账号 token 有自己的列表,因此服务端可以先对部分账号开放某个模型。
|
||||
同一凭据的第一次访问会等待列表,最长 `modelsTimeoutMs`。首次使用后,Host 每隔 `modelsMaxAgeMs`(默认十分钟)自动刷新,页面空闲时也会刷新。刷新过程中,后续访问直接返回缓存列表。获取失败或得到空列表时保留上一份列表及其角标;还没有加载到任何列表时,路由提供当前版本内置的目录(`deepseek-flash` 与 `deepseek-v4-pro`)。列表请求返回 HTTP 401 不会清除已保存的登录。每次加载成功、连续失败中的第一次失败及其退回的目录、每个被隐藏的列表条目,以及每个被丢弃的角标,都会以 `[llm-deepseek-account]` 标签写到 stderr;stdout 保留给协议消息,且不记录任何密钥。每个账号 token 有自己的列表,因此服务端可以先对部分账号开放某个模型。 列表变化时发出 `llm/models-updated`,已打开的选择器会重新加载,无需刷新页面。省略角标元数据或将其设为 `null` 会清除旧角标,无需单独的清除操作。插件卸载会停止刷新并等待进行中的加载结束。
|
||||
|
||||
只有当前版本的 DeepSeek Harness 能用的模型才会出现在目录里。声明了 `client_requirements["deepseek-harness"].min_version` 的模型,在当前版本号按 semver 顺序不低于该版本时提供,预发布号(如 `0.2.0-alpha.1`、`0.2.0-rc.1`)同样参与比较。预发布版本低于同号的正式版本:`min_version: 0.2.1` 会拒绝 `0.2.1-alpha.2`,因此要放行预发布版本的列表应声明它允许的最早一个,如 `0.2.1-alpha.0`。没有这个声明的模型,只有内置目录认识它的 id 时才提供;其余列出的模型保持隐藏。请求一个当前版本不能用的已列出模型时,会在网络请求之前失败:`CLIENT_UPGRADE_REQUIRED` 会写明所需版本和当前版本,否则为 `MODEL_UNSUPPORTED`。列表没有列出的模型 id 照常透传。
|
||||
|
||||
提供的模型以列表中的名称、`context_window`、输入模态,以及 Messages 的 system prompt 模式和工具更新模式(`api_capabilities.anthropic_messages.system_prompt_update` 与 `tool_update`)覆盖同 id 的内置条目,内置条目保留图片预算和描述。列表没有给出或设为 `null` 的模式沿用内置值;本版本能读的模式之外的任何值(system prompt 能读 `in-history`,工具能读 `in-history` 或 `addition-only`)都按 `leading-only` 处理,即去掉内置的模式。`max_output_tokens` 限制请求默认值:请求没有指定上限时,发送 `maxTokens` 与该能力上限中较小的一个。列表中的推理档位不被读取;每个模型都提供 `off`、`low`、`high`、`max`。
|
||||
|
||||
提供的模型会在 Web 模型选择器中,于名称旁显示列表条目在 `client_metadata["deepseek-harness"].badge` 下声明的角标。`client_metadata` 存放任何版本都可以忽略的客户端专属数据;与 `client_requirements` 不同,其中的内容不会隐藏模型。`badge.text` 是字符串,或以小写语言 id(如 `en`、`zh`、`ja`)为键且包含 `en` 的对象;选择器显示当前语言的文字,没有时显示英文。每段文字去掉首尾空白后须为单行,长度为 1 到 12 个字符;一个 emoji 或带组合符号的字母算作一个字符。`badge.tone` 取 `neutral`、`info`、`success` 或 `warning`;缺省或其他取值按 `neutral` 显示。这条路径上任何一层缺省或为 `null` 时不显示角标,因为本版本没有可回退的内置角标。某一层不是对象或角标无法读取时,角标被丢弃,所属模型照常提供。角标只用于展示:模型请求、Session 事件和面向模型的工具都不携带它。
|
||||
|
||||
`modelOverrides` 调整单个模型而不替换整个目录。键是模型 id;值的字段与 [Messages `models` 条目](../llm-deepseek/README.zh.md#minimal-configuration)相同,未设置的字段沿用下层的值。覆盖作用在列表和内置目录之上;不能新增模型,也不能让被拒绝的模型变得可用。列表声明的能力不约束覆盖:覆盖的 `maxTokens` 高于列表的 `max_output_tokens` 时按覆盖值发送,超出服务端限制的请求由服务端拒绝。覆盖指向的模型既不在列表里、内置目录也不认识,或者被列出的模型拒绝时,会成为模型页上该提供方的诊断信息;模型页不为此路由提供编辑器。被拒绝且内置目录不认识的模型,其覆盖要等当前版本能用该模型时才校验。此路由没有 `models` 字段;配置里仍写了它时,加载即失败并提示改用 `modelOverrides`。修改 `$DSH_HOME/profiles/<profile>/cordis.patch.yml`,下一次请求即生效。
|
||||
|
||||
```yaml
|
||||
@@ -51,7 +53,7 @@ kind: "package-reference"
|
||||
| 字段 | 默认值 | 含义 |
|
||||
|---|---|---|
|
||||
| `modelsTimeoutMs` | `5,000` | 单次列表请求以及首次访问等待的上限 |
|
||||
| `modelsMaxAgeMs` | `600,000` | 列表超过这个时长后,下一次访问时刷新;获取失败后同样在这个时长后重试 |
|
||||
| `modelsMaxAgeMs` | `600,000` | 首次使用后的自动刷新间隔,失败后也按此间隔重试;`0` 表示只在访问时刷新 |
|
||||
| `modelOverrides` | `{}` | 按模型 id 的字段覆盖 |
|
||||
|
||||
账号请求的 HTTP 401 与响应正文无关:映射为 `ACCOUNT_TOKEN_INVALID`,并要求账号服务仅在当前 token 仍与请求所用 token 相同时清除凭据。403 和其他错误不会触发清除。账号模块负责退登后打断任务,本插件不遍历 Agent。
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ACCOUNT_QUOTA_EXCEEDED_CODE, APP_IDENTITY, LlmError, QUOTA_EXCEEDED_COD
|
||||
import type {} from '@deepseek-ai/dsh-deepseek-account'
|
||||
import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
|
||||
import {
|
||||
catalogModelInfo, ModelListingCache, refuseListedModel, registerDeepSeekProvider, requestModelListing, resolveAdapterOptions,
|
||||
catalogModelInfo, createModelListingCache, refuseListedModel, registerDeepSeekProvider, requestModelListing, resolveAdapterOptions,
|
||||
resolveListedCatalog,
|
||||
} from '@deepseek-ai/dsh-llm-deepseek'
|
||||
import type { DeepSeekRequestAuth, ListedCatalog, ListingView, ModelListing, ResolvedDeepSeekOptions } from '@deepseek-ai/dsh-llm-deepseek'
|
||||
@@ -50,16 +50,13 @@ export function apply(ctx: Context, config: Config): void {
|
||||
},
|
||||
}
|
||||
}
|
||||
const catalog = new ModelListingCache({
|
||||
const catalog = createModelListingCache(ctx, name, PROVIDER, {
|
||||
settings: () => settings().catalog,
|
||||
credential: async url => account()?.resolveToken(url),
|
||||
load: (url, token, signal) => requestModelListing(url, { [TOKEN_HEADER]: token }, signal),
|
||||
now: () => Date.now(),
|
||||
// ACP reserves stdout for protocol messages, including during successful catalog loads.
|
||||
info: (message) => { console.error(LOG_TAG, message) },
|
||||
warn: (message) => { console.warn(LOG_TAG, message) },
|
||||
})
|
||||
let loggedListing: ModelListing | undefined
|
||||
/** Resolve the catalog for one view, logging the listing's hidden entries once per listing. */
|
||||
/** Resolve the catalog, reporting each listing's hidden entries and dropped badges once. */
|
||||
const connectionOf = (view: ListingView): AccountConnection => {
|
||||
const resolved = settings()
|
||||
const result = resolveListedCatalog({
|
||||
@@ -96,8 +93,9 @@ export function apply(ctx: Context, config: Config): void {
|
||||
admit: (connection, model) => { refuseListedModel(model, connection.catalog.refusals.get(model)) },
|
||||
discoverModels: async (provider) => {
|
||||
const view = await catalog.view(settings().catalog)
|
||||
return view.authenticated ? discovered(view).catalog.offered.map(model => catalogModelInfo(provider, model)) : []
|
||||
if (!view.authenticated) return []
|
||||
const { offered, badges } = discovered(view).catalog
|
||||
return offered.map(model => catalogModelInfo(provider, model, badges.get(model.id)))
|
||||
},
|
||||
})
|
||||
ctx.effect(() => () => { catalog.dispose() }, 'llm-deepseek-account: model catalog')
|
||||
}
|
||||
|
||||
@@ -98,6 +98,44 @@ it('resolves listed capabilities with this build\'s model knowledge', async () =
|
||||
expect(server.requests[0]?.headers['user-agent']).toMatch(/^deepseek-harness\//)
|
||||
})
|
||||
|
||||
it('advertises the badges its listing declares', async () => {
|
||||
const badge = { text: { en: 'New', zh: '新' }, tone: 'success' }
|
||||
const server = await listing(json({ data: [{ ...FLASH, client_metadata: { 'deepseek-harness': { badge } } }, PRO] }))
|
||||
const ctx = await host(server.origin)
|
||||
await ctx.plugin(Account, { baseURL: server.baseURL })
|
||||
expect((await ctx.llm.listModels('deepseek-account')).map(row => [row.id, row.badge])).toEqual([
|
||||
['deepseek-flash', badge], ['deepseek-v4-pro', undefined],
|
||||
])
|
||||
})
|
||||
|
||||
it('publishes an account badge removal after the default idle refresh interval', async () => {
|
||||
let badge: { text: string } | undefined = { text: 'New' }
|
||||
const server = await listing((request, response) => {
|
||||
json({ data: [{ ...FLASH, client_metadata: { 'deepseek-harness': { badge } } }] })(request, response)
|
||||
})
|
||||
const ctx = await host(server.origin)
|
||||
const refreshed = Promise.withResolvers<undefined>()
|
||||
const changes: string[] = []
|
||||
ctx.on('llm/models-updated', (provider) => {
|
||||
changes.push(provider)
|
||||
if (changes.length === 2) refreshed.resolve(undefined)
|
||||
})
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] })
|
||||
try {
|
||||
await ctx.plugin(Account, { baseURL: server.baseURL })
|
||||
expect((await ctx.llm.listModels('deepseek-account'))[0]?.badge?.text).toBe('New')
|
||||
badge = undefined
|
||||
await vi.advanceTimersByTimeAsync(600_000)
|
||||
await refreshed.promise
|
||||
expect(changes).toEqual(['deepseek-account', 'deepseek-account'])
|
||||
expect((await ctx.llm.listModels('deepseek-account'))[0]).not.toHaveProperty('badge')
|
||||
expect(server.requests).toHaveLength(2)
|
||||
} finally {
|
||||
try { await ctx.fiber.dispose() }
|
||||
finally { vi.useRealTimers() }
|
||||
}
|
||||
})
|
||||
|
||||
it('falls back to the bundled catalog when the listing fails', async () => {
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const server = await listing((_request, response) => { response.writeHead(503).end('down') })
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
en: 23386452a66b2fbd
|
||||
zh: 4cf93c4bf17b3da2
|
||||
/deepseek-ai-dsh-llm-deepseek-api-key/use-this-package:
|
||||
en: 2aad8a660b90561d
|
||||
zh: 3f425b3d1ea023e1
|
||||
en: 007427bed9bd469b
|
||||
zh: 5089b5781746357f
|
||||
/deepseek-ai-dsh-llm-deepseek-api-key/understand-the-implementation:
|
||||
en: d614c1ea3a9eb382
|
||||
zh: 5ca070349bb22d56
|
||||
|
||||
@@ -39,7 +39,7 @@ The endpoint and credential reference come from one configuration resolution. In
|
||||
|
||||
`models` is an independently configurable catalog for this provider; defaults and protocol capabilities come from the shared transport. While `models` equals the built-in catalog and the Messages origin is the official API, `https://api.deepseek.com`, the route serves the official `GET https://api.deepseek.com/models` listing instead. Listing requests carry the API key in `x-api-key` and the harness `User-Agent`, and reject redirects; without an API key the route requests nothing. Any other endpoint, including one named by `baseURL` or `$DEEPSEEK_BASE_URL`, serves `models` and requests no listing. Discovery does not probe inference endpoints.
|
||||
|
||||
The first access waits for the listing up to `modelsTimeoutMs`. Later accesses use the loaded listing and refresh it in the background once it is older than `modelsMaxAgeMs`; an idle route sends no requests. A failed or empty listing keeps the previous one; before any listing loads, the route serves the built-in catalog of the current version (`deepseek-flash` and `deepseek-v4-pro`). A listing HTTP 401 fails only that load. Each loaded listing, the first failure of a failing streak with the catalog it falls back to, and each listing entry hidden because its declarations are unreadable or unusable are written to stderr with the `[llm-deepseek-api-key]` tag; stdout remains available for protocol messages, and secrets are never logged.
|
||||
The first access waits for the listing up to `modelsTimeoutMs`. After first use, the Host refreshes automatically every `modelsMaxAgeMs` (ten minutes by default), including while the page is idle. Later accesses return the cached listing while a refresh runs. A failed or empty listing retains the previous listing, including badges; before any listing loads, the route serves the built-in catalog of the current version (`deepseek-flash` and `deepseek-v4-pro`). A listing HTTP 401 fails only that load. Each loaded listing, the first failure of a failing streak with the catalog it falls back to, each listing entry hidden because its declarations are unreadable or unusable, and each dropped badge are written to stderr with the `[llm-deepseek-api-key]` tag; stdout remains available for protocol messages, and secrets are never logged. Changed listings emit `llm/models-updated`, so open selectors reload without a page refresh. Omitted or `null` badge metadata clears a previous badge; no separate clear operation is needed. Plugin disposal stops refreshes and waits for active loads.
|
||||
|
||||
Listed models are offered, merged with this build's entries, and refused before network I/O with `CLIENT_UPGRADE_REQUIRED` or `MODEL_UNSUPPORTED` by the [account route's rules](../llm-deepseek-account/README.md#use-this-package); model ids the listing does not name pass through unchanged.
|
||||
|
||||
@@ -48,7 +48,7 @@ A `models` list that differs from the built-in catalog replaces the catalog and
|
||||
| Field | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `modelsTimeoutMs` | `5,000` | Deadline of one listing request and of a first-access wait |
|
||||
| `modelsMaxAgeMs` | `600,000` | Listing age after which the next access refreshes it; a failed load retries after the same age |
|
||||
| `modelsMaxAgeMs` | `600,000` | Automatic refresh interval after first use, also used after failure; `0` refreshes on access only |
|
||||
|
||||
The settings namespace is the Cordis entry id, or the plugin name without an entry. Product profiles retain the official entry id `llm-deepseek` and use `llm-deepseek-account` for the account route.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ kind: "package-reference"
|
||||
|
||||
`models` 是该 provider 独立的可配置目录;默认值和协议能力来自共享传输包。当 `models` 等于内置目录、且 Messages 的 origin 是官方 API `https://api.deepseek.com` 时,路由改为提供官方 `GET https://api.deepseek.com/models` 列表。列表请求用 `x-api-key` 携带 API key,带上 harness 的 `User-Agent`,并拒绝重定向;没有 API key 时不发任何请求。其他端点,包括由 `baseURL` 或 `$DEEPSEEK_BASE_URL` 指定的端点,提供 `models`,不请求列表。目录判断不探测推理端点。
|
||||
|
||||
第一次访问会等待列表,最长 `modelsTimeoutMs`。之后的访问直接使用已加载的列表,列表超过 `modelsMaxAgeMs` 后在后台刷新;路由空闲时不发请求。获取失败或得到空列表时保留上一份;还没有加载到任何列表时,路由提供当前版本内置的目录(`deepseek-flash` 与 `deepseek-v4-pro`)。列表请求返回 HTTP 401 只让这一次加载失败。每次加载成功、连续失败中的第一次失败及其退回的目录、以及每个因声明无法读取或不可用而被隐藏的列表条目,都会以 `[llm-deepseek-api-key]` 标签写到 stderr;stdout 保留给协议消息,且不记录任何密钥。
|
||||
第一次访问会等待列表,最长 `modelsTimeoutMs`。首次使用后,Host 每隔 `modelsMaxAgeMs`(默认十分钟)自动刷新,页面空闲时也会刷新。刷新过程中,后续访问直接返回缓存列表。获取失败或得到空列表时保留上一份列表及其角标;还没有加载到任何列表时,路由提供当前版本内置的目录(`deepseek-flash` 与 `deepseek-v4-pro`)。列表请求返回 HTTP 401 只让这一次加载失败。每次加载成功、连续失败中的第一次失败及其退回的目录、每个因声明无法读取或不可用而被隐藏的列表条目,以及每个被丢弃的角标,都会以 `[llm-deepseek-api-key]` 标签写到 stderr;stdout 保留给协议消息,且不记录任何密钥。 列表变化时发出 `llm/models-updated`,已打开的选择器会重新加载,无需刷新页面。省略角标元数据或将其设为 `null` 会清除旧角标,无需单独的清除操作。插件卸载会停止刷新并等待进行中的加载结束。
|
||||
|
||||
列出的模型按[账号路由的规则](../llm-deepseek-account/README.zh.md#use-this-package)提供、与本版本的内置条目合并,并在网络请求之前以 `CLIENT_UPGRADE_REQUIRED` 或 `MODEL_UNSUPPORTED` 拒绝;列表没有列出的模型 id 照常透传。
|
||||
|
||||
@@ -48,7 +48,7 @@ kind: "package-reference"
|
||||
| 字段 | 默认值 | 含义 |
|
||||
|---|---|---|
|
||||
| `modelsTimeoutMs` | `5,000` | 单次列表请求以及首次访问等待的上限 |
|
||||
| `modelsMaxAgeMs` | `600,000` | 列表超过这个时长后,下一次访问时刷新;获取失败后同样在这个时长后重试 |
|
||||
| `modelsMaxAgeMs` | `600,000` | 首次使用后的自动刷新间隔,失败后也按此间隔重试;`0` 表示只在访问时刷新 |
|
||||
|
||||
设置命名空间采用 Cordis entry id,没有 entry 时采用插件名。产品保留 official 的 `llm-deepseek` entry id,账号使用 `llm-deepseek-account`。
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/** API-key authentication, credential-gated discovery, and the official model listing for the official DeepSeek route. */
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import { APP_IDENTITY, assertUsableApiKey, LlmError } from '@deepseek-ai/dsh-llm'
|
||||
import type { LlmModelBadge } from '@deepseek-ai/dsh-llm'
|
||||
import type {} from '@deepseek-ai/cordis-plugin-loader'
|
||||
import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
|
||||
import {
|
||||
catalogModelInfo, ModelListingCache, refuseListedModel, registerDeepSeekProvider, requestModelListing, resolveListedCatalog,
|
||||
catalogModelInfo, createModelListingCache, refuseListedModel, registerDeepSeekProvider, requestModelListing, resolveListedCatalog,
|
||||
} from '@deepseek-ai/dsh-llm-deepseek'
|
||||
import type { CatalogRefusal, DeepSeekCatalogModel, ListingView, ModelListing } from '@deepseek-ai/dsh-llm-deepseek'
|
||||
import { Config, plainOptions, resolveAdapterOptions } from './config.ts'
|
||||
@@ -25,6 +26,8 @@ interface OfficialConnection extends ResolvedDeepSeekOptions {
|
||||
readonly offered: readonly DeepSeekCatalogModel[]
|
||||
/** Listed models refused before network I/O, by id. */
|
||||
readonly refusals: ReadonlyMap<string, CatalogRefusal>
|
||||
/** Listed badges of offered models, by id; empty without a followed listing. */
|
||||
readonly badges: ReadonlyMap<string, LlmModelBadge>
|
||||
}
|
||||
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
@@ -54,18 +57,15 @@ export function apply(ctx: Context, config: Config): void {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
const listings = new ModelListingCache({
|
||||
const listings = createModelListingCache(ctx, name, PROVIDER, {
|
||||
settings: () => options().listing,
|
||||
credential: async () => presentApiKey(options()),
|
||||
load: (url, apiKey, signal) => requestModelListing(url, { 'x-api-key': apiKey }, signal),
|
||||
now: () => Date.now(),
|
||||
// ACP reserves stdout for protocol messages, including during successful catalog loads.
|
||||
info: (message) => { console.error(LOG_TAG, message) },
|
||||
warn: (message) => { console.warn(LOG_TAG, message) },
|
||||
})
|
||||
let reportedListing: ModelListing | undefined
|
||||
/** Resolve the catalog of one operation, reporting each listing's hidden entries once. */
|
||||
/** Resolve the catalog, reporting each listing's hidden entries and dropped badges once. */
|
||||
const connectionOf = (connection: ResolvedDeepSeekOptions, view: ListingView | undefined): OfficialConnection => {
|
||||
if (view === undefined) return { ...connection, offered: connection.models, refusals: new Map() }
|
||||
if (view === undefined) return { ...connection, offered: connection.models, refusals: new Map(), badges: new Map() }
|
||||
const catalog = resolveListedCatalog({
|
||||
listing: view.listing?.entries,
|
||||
supported: connection.models,
|
||||
@@ -77,7 +77,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
reportedListing = view.listing
|
||||
for (const problem of catalog.problems) console.warn(LOG_TAG, problem)
|
||||
}
|
||||
return { ...connection, models: catalog.resolution, offered: catalog.offered, refusals: catalog.refusals }
|
||||
return { ...connection, models: catalog.resolution, offered: catalog.offered, refusals: catalog.refusals, badges: catalog.badges }
|
||||
}
|
||||
ctx.llm.registerConfigurableProviders([
|
||||
{ provider: PROVIDER, displayName: 'DeepSeek', settingsNs: ctx.fiber.entry?.options.id ?? name, settingsPath: [] },
|
||||
@@ -110,7 +110,9 @@ export function apply(ctx: Context, config: Config): void {
|
||||
return connection.models.map(model => catalogModelInfo(provider, model))
|
||||
}
|
||||
const view = await listings.view(connection.listing)
|
||||
return view.authenticated ? connectionOf(connection, view).offered.map(model => catalogModelInfo(provider, model)) : []
|
||||
if (!view.authenticated) return []
|
||||
const { offered, badges } = connectionOf(connection, view)
|
||||
return offered.map(model => catalogModelInfo(provider, model, badges.get(model.id)))
|
||||
},
|
||||
})
|
||||
// Starting or stopping to follow the listing discards it, so restoring the built-in
|
||||
@@ -124,5 +126,4 @@ export function apply(ctx: Context, config: Config): void {
|
||||
if (next !== following) listings.invalidate()
|
||||
following = next
|
||||
})
|
||||
ctx.effect(() => () => { listings.dispose() }, 'llm-deepseek-api-key: model listing')
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ const BUILT_IN = ['deepseek-flash', 'deepseek-v4-pro']
|
||||
const contexts: Context[] = []
|
||||
afterEach(async () => {
|
||||
for (const ctx of contexts.splice(0)) await ctx.fiber.dispose()
|
||||
vi.useRealTimers()
|
||||
vi.unstubAllEnvs()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
@@ -59,6 +60,20 @@ it('follows the official listing while the catalog is the built-in one', async (
|
||||
expect(diagnostics).toHaveBeenCalledExactlyOnceWith('[llm-deepseek-api-key]', expect.stringContaining('loaded 3 model(s), skipped 0 row(s)') as string)
|
||||
})
|
||||
|
||||
it('advertises listed badges only while it follows the listing', async () => {
|
||||
const badge = { text: '1017', tone: 'info' }
|
||||
officialListing(listing({ ...FLASH, client_metadata: { 'deepseek-harness': { badge } } }, PRO))
|
||||
const { ctx } = await host()
|
||||
await ctx.plugin(ApiKey, {})
|
||||
const listed = await ctx.llm.listModels('deepseek-official')
|
||||
expect(listed.map(model => [model.id, model.badge])).toEqual([['deepseek-flash', badge], ['deepseek-v4-pro', undefined]])
|
||||
await expect(ctx.llm.resolveModelInfo('deepseek-official', 'deepseek-flash')).resolves.not.toHaveProperty('badge')
|
||||
|
||||
const other = await host()
|
||||
await other.ctx.plugin(ApiKey, { baseURL: 'http://127.0.0.1:9/anthropic' })
|
||||
expect((await other.ctx.llm.listModels('deepseek-official')).some(model => model.badge !== undefined)).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps a customized catalog without requesting the listing', async () => {
|
||||
const requests = officialListing(listing(FLASH))
|
||||
const { ctx } = await host()
|
||||
@@ -159,3 +174,23 @@ it('keeps following the listing across an update the adapter rejects', async ()
|
||||
expect(await ids(ctx)).toEqual(BUILT_IN)
|
||||
expect(requests).toHaveLength(1)
|
||||
})
|
||||
|
||||
it.each([undefined, null])('notifies an idle client when a badge is replaced and cleared with %s', async (clear) => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] })
|
||||
let badge: { text: string; tone: string } | null | undefined = { text: 'New', tone: 'info' }
|
||||
officialListing(() => listing({ ...FLASH, client_metadata: { 'deepseek-harness': { badge } } })())
|
||||
const { ctx } = await host()
|
||||
await ctx.plugin(ApiKey, {})
|
||||
const changed = vi.fn()
|
||||
ctx.on('llm/models-updated', changed)
|
||||
expect((await ctx.llm.listModels('deepseek-official'))[0]?.badge?.text).toBe('New')
|
||||
badge = { text: 'Updated', tone: 'success' }
|
||||
await vi.advanceTimersByTimeAsync(600_000)
|
||||
expect(changed).toHaveBeenCalledTimes(2)
|
||||
expect(changed).toHaveBeenLastCalledWith('deepseek-official')
|
||||
expect((await ctx.llm.listModels('deepseek-official'))[0]?.badge).toEqual(badge)
|
||||
badge = clear
|
||||
await vi.advanceTimersByTimeAsync(600_000)
|
||||
expect(changed).toHaveBeenCalledTimes(3)
|
||||
expect((await ctx.llm.listModels('deepseek-official'))[0]).not.toHaveProperty('badge')
|
||||
})
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
en: f13f24b504dcfb50
|
||||
zh: 69b0ed9e2adf3394
|
||||
/deepseek-ai-dsh-llm-deepseek/use-this-package/failures-and-recovery:
|
||||
en: 40ab925291edc6ac
|
||||
zh: 62c42ea3521920dc
|
||||
en: 63b17e204a764a7d
|
||||
zh: dafcd75c80796d35
|
||||
/deepseek-ai-dsh-llm-deepseek/understand-the-implementation:
|
||||
en: 1598ac8a8bb9b6bf
|
||||
zh: fdd089c5ae7722cd
|
||||
|
||||
@@ -118,7 +118,7 @@ Successful Files responses must contain valid JSON. JSON decoding failures from
|
||||
|
||||
Non-2xx responses fail with stable codes: `AUTH` (401/403), `QUOTA`, `RATE_LIMIT`, `CONTEXT_WINDOW_EXCEEDED`, `INVALID_REQUEST`, `SERVER`, and `HTTP_<status>` otherwise; pre-response transport failures throw `TRANSPORT`, caller aborts throw `ABORTED`, and stream-idle expiry throws `TIMEOUT`. A request-extension field collision, or a rejected preparation from a directly supplied `prepareExtensions`, fails with `REQUEST_EXTENSION` and appends the cause chain to the message; a post-2xx acceptance failure is logged and the response still streams. A normalized-image rejection names every plausible attachment and its durable position when the provider does not identify a file id. Stale-file rejection removes every named mapping (or every mapping used by the attempt) in one upload-index update and permits one replacement model request. Protocol violations throw `STREAM_CLOSED` or `MALFORMED_RESPONSE`, and a terminal `stop` with no content blocks becomes `EMPTY_RESPONSE`, which the default retry policy retries. A request on the official route without an API key fails with `MISSING_CREDENTIAL`, and a malformed credential fails with `INVALID_CREDENTIAL` naming the reference to fix — never any part of the key.
|
||||
|
||||
Provider plugins own catalog availability. While its API key resolves, the API-key route serves its configured `models`, or the official model listing while `models` is the built-in catalog on the official endpoint; the account route serves its account listing while signed in (see the [API-key](../llm-deepseek-api-key/README.md) and [account](../llm-deepseek-account/README.md) providers). The transport supplies the bundled catalog, the catalog-entry field schemas, and capability resolution, plus the listing machinery both routes share: `requestModelListing`, the credential-scoped `ModelListingCache`, and `resolveListedCatalog` with its client-version gate and `refuseListedModel`. A provider may also supply `connect(signal)`, which resolves the connection for exact-model lookups and prepared calls after provider-owned work such as a catalog load, and `admit(connection, model)`, which refuses a model before network I/O.
|
||||
Provider plugins own catalog availability. While its API key resolves, the API-key route serves its configured `models`, or the official model listing while `models` is the built-in catalog on the official endpoint; the account route serves its account listing while signed in (see the [API-key](../llm-deepseek-api-key/README.md) and [account](../llm-deepseek-account/README.md) providers). The transport supplies the bundled catalog, the catalog-entry field schemas, and capability resolution, plus the listing machinery both routes share: `requestModelListing`, the credential-scoped `ModelListingCache` (owned by `createModelListingCache` for automatic refresh, notifications, and plugin cleanup), and `resolveListedCatalog` with its client-version gate, listed badges, and `refuseListedModel`; `catalogModelInfo` attaches a listed badge to the advertised entry. Badge text rejects controls, Unicode line separators, bidi controls, and strings made only of whitespace or default-ignorable characters; joined emoji remain valid. A provider may also supply `connect(signal)`, which resolves the connection for exact-model lookups and prepared calls after provider-owned work such as a catalog load, and `admit(connection, model)`, which refuses a model before network I/O.
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ Files 模式通过 `maxRequestFilesBytes` 与 `maxImagesPerRequest` 限制保留
|
||||
|
||||
非 2xx 响应以稳定 code 失败:`AUTH`(401/403)、`QUOTA`、`RATE_LIMIT`、`CONTEXT_WINDOW_EXCEEDED`、`INVALID_REQUEST`、`SERVER` 以及其他情况的 `HTTP_<status>`;响应前传输失败抛出 `TRANSPORT`,调用方中止抛出 `ABORTED`,流空闲超时抛出 `TIMEOUT`。请求扩展字段冲突,或直接提供的 `prepareExtensions` 拒绝准备时,使用 `REQUEST_EXTENSION` 失败,并在消息中附上原因链;2xx 后接受失败只记录告警,响应仍会继续流式返回。当提供方未指出 file id 时,规范化图片拒绝会列出所有可能附件及其持久位置。陈旧文件拒绝会在一次上传索引更新中删除全部点名映射(或该次尝试使用的全部映射),并允许一次替换模型请求。协议违规抛出 `STREAM_CLOSED` 或 `MALFORMED_RESPONSE`;不带内容块的终止 `stop` 变成 `EMPTY_RESPONSE`,默认重试策略会重试它。官方路由缺少 API Key 的请求以 `MISSING_CREDENTIAL` 失败;格式错误的凭据以 `INVALID_CREDENTIAL` 失败,并点名需要修复的引用——绝不包含密钥的任何部分。
|
||||
|
||||
提供方插件负责目录可用性:API key 路由在能解析到 API key 时提供配置中的 `models`,在官方端点上且 `models` 为内置目录时改为提供官方模型列表;账号路由在登录时提供账号的模型列表(见 [API key](../llm-deepseek-api-key/README.zh.md) 与[账号](../llm-deepseek-account/README.zh.md)提供方)。传输层提供内置目录、目录条目字段的 schema 和能力解析,以及两条路由共用的列表机制:`requestModelListing`、按凭据区分的 `ModelListingCache`,以及带客户端版本门控的 `resolveListedCatalog` 与 `refuseListedModel`。提供方还可以提供 `connect(signal)`:在提供方自己的工作(例如加载目录)完成后,为精确模型查询和预备调用解析连接;以及 `admit(connection, model)`:在网络请求之前拒绝某个模型。
|
||||
提供方插件负责目录可用性:API key 路由在能解析到 API key 时提供配置中的 `models`,在官方端点上且 `models` 为内置目录时改为提供官方模型列表;账号路由在登录时提供账号的模型列表(见 [API key](../llm-deepseek-api-key/README.zh.md) 与[账号](../llm-deepseek-account/README.zh.md)提供方)。传输层提供内置目录、目录条目字段的 schema 和能力解析,以及两条路由共用的列表机制:`requestModelListing`、按凭据区分的 `ModelListingCache`(由 `createModelListingCache` 持有,负责自动刷新、通知与插件清理),以及带客户端版本门控和列表角标的 `resolveListedCatalog` 与 `refuseListedModel`;`catalogModelInfo` 把列表角标附到公布的条目上。角标文字拒绝控制字符、Unicode 行分隔符、双向控制符及仅由空白或默认可忽略字符组成的字符串,组合 emoji 仍有效。提供方还可以提供 `connect(signal)`:在提供方自己的工作(例如加载目录)完成后,为精确模型查询和预备调用解析连接;以及 `admit(connection, model)`:在网络请求之前拒绝某个模型。
|
||||
|
||||
-----
|
||||
|
||||
|
||||
@@ -8,6 +8,32 @@ import { getOrCreateAnonymousUserId, type AnonymousUserId } from '@deepseek-ai/d
|
||||
import { deepEqualJson } from '@deepseek-ai/dsh-util-values'
|
||||
import { DeepSeekAdapter } from './adapter.ts'
|
||||
import type { DeepSeekAdapterOptions, DeepSeekConnectionOptions } from './types.ts'
|
||||
import { ModelListingCache, type ListingCacheDependencies } from './listing-cache.ts'
|
||||
|
||||
/**
|
||||
* Own a route's listing refreshes, stderr diagnostics, and catalog-change notifications.
|
||||
* @param ctx - provider plugin lifetime.
|
||||
* @param owner - plugin name used in diagnostics.
|
||||
* @param provider - route whose catalog changes.
|
||||
* @param dependencies - current configuration, credentials, and listing transport.
|
||||
* @returns the cache, disposed with the provider after its active work settles.
|
||||
*/
|
||||
export function createModelListingCache(
|
||||
ctx: Context, owner: string, provider: string,
|
||||
dependencies: Pick<ListingCacheDependencies, 'settings' | 'credential' | 'load'>,
|
||||
): ModelListingCache {
|
||||
const tag = `[${owner}]`
|
||||
const cache = new ModelListingCache({
|
||||
...dependencies,
|
||||
now: () => Date.now(),
|
||||
// ACP reserves stdout for protocol messages, including during successful catalog loads.
|
||||
info: (message) => { console.error(tag, message) },
|
||||
warn: (message) => { console.warn(tag, message) },
|
||||
changed: () => { ctx.emit('llm/models-updated', provider) },
|
||||
})
|
||||
ctx.effect(() => () => cache.dispose(), `${owner}: model listing`)
|
||||
return cache
|
||||
}
|
||||
|
||||
/**
|
||||
* Register one provider with request-local transport services and live retry policy.
|
||||
|
||||
@@ -38,9 +38,9 @@ export type { DeepSeekUploadRecord } from './upload-index.ts'
|
||||
export type { RequestDefaults } from './types.ts'
|
||||
|
||||
export { catalogModelInfo } from './model-info.ts'
|
||||
export { registerDeepSeekProvider } from './host.ts'
|
||||
export { createModelListingCache, registerDeepSeekProvider } from './host.ts'
|
||||
export { LISTING_TIMEOUT, modelListingURL, parseModelListing, requestModelListing } from './listing.ts'
|
||||
export type { ClientRequirement, ListedCapabilities, ListedFacts, ListingEntry, ModelListing } from './listing.ts'
|
||||
export type { ClientRequirement, ListedBadge, ListedCapabilities, ListedFacts, ListingEntry, ModelListing } from './listing.ts'
|
||||
export { DEFAULT_MODELS_MAX_AGE_MS, DEFAULT_MODELS_TIMEOUT_MS, modelListingConfigFields, resolveListingTiming } from './listing-config.ts'
|
||||
export type { ListingSettings, ListingTiming, ModelListingConfig, ModelListingOptions } from './listing-config.ts'
|
||||
export { ModelListingCache } from './listing-cache.ts'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** A listed catalog: version gating, this build's model knowledge, and deployment overrides. */
|
||||
import semver from 'semver'
|
||||
import { APP_IDENTITY, LlmError } from '@deepseek-ai/dsh-llm'
|
||||
import type { LlmModelBadge } from '@deepseek-ai/dsh-llm'
|
||||
import { assertNever } from '@deepseek-ai/dsh-util-values'
|
||||
import { resolveAdapterOptions } from './config.ts'
|
||||
import type { ListedFacts, ListingEntry } from './listing.ts'
|
||||
@@ -36,7 +37,9 @@ export interface ListedCatalog {
|
||||
readonly resolution: readonly DeepSeekCatalogModel[]
|
||||
/** Listed models refused before network I/O, by id. */
|
||||
readonly refusals: ReadonlyMap<string, CatalogRefusal>
|
||||
/** Listing entries hidden because their declarations are unreadable or unusable. */
|
||||
/** Listed badges of offered models, by id. */
|
||||
readonly badges: ReadonlyMap<string, LlmModelBadge>
|
||||
/** Listing entries hidden because their declarations are unreadable or unusable, and offered models whose badge is dropped. */
|
||||
readonly problems: readonly string[]
|
||||
/** Configuration diagnostics for `modelOverrides`. */
|
||||
readonly overrideErrors: readonly string[]
|
||||
@@ -115,10 +118,11 @@ export function resolveListedCatalog(input: ListedCatalogInput): ListedCatalog {
|
||||
}
|
||||
if (input.listing === undefined) {
|
||||
const resolution = resolveAdapterOptions({ models: input.supported.map(withOverride) }).models
|
||||
return { offered: resolution, resolution, refusals: new Map(), problems, overrideErrors }
|
||||
return { offered: resolution, resolution, refusals: new Map(), badges: new Map(), problems, overrideErrors }
|
||||
}
|
||||
const offered: DeepSeekCatalogModel[] = []
|
||||
const refusals = new Map<string, CatalogRefusal>()
|
||||
const badges = new Map<string, LlmModelBadge>()
|
||||
for (const entry of input.listing) {
|
||||
const refusal = gateOf(entry, supported.has(entry.id), input.clientVersion, problems)
|
||||
if (refusal !== undefined) {
|
||||
@@ -131,6 +135,20 @@ export function resolveListedCatalog(input: ListedCatalogInput): ListedCatalog {
|
||||
continue
|
||||
}
|
||||
offered.push(withOverride(listed(supported.get(entry.id) ?? { id: entry.id }, entry, entry.capabilities.facts, input.routeMaxTokens)))
|
||||
const badge = entry.badge
|
||||
switch (badge?.kind) {
|
||||
case undefined:
|
||||
break
|
||||
case 'badge':
|
||||
badges.set(entry.id, badge.badge)
|
||||
break
|
||||
case 'invalid':
|
||||
problems.push(`model "${entry.id}" shows no badge: ${badge.reason}`)
|
||||
break
|
||||
/* v8 ignore next 2 -- listing decoding produces only the closed badge union */
|
||||
default:
|
||||
assertNever(badge, 'listed badge')
|
||||
}
|
||||
}
|
||||
const offeredIds = new Set(offered.map(model => model.id))
|
||||
const listedIds = new Set(input.listing.map(entry => entry.id))
|
||||
@@ -142,7 +160,7 @@ export function resolveListedCatalog(input: ListedCatalogInput): ListedCatalog {
|
||||
const resolution = resolveAdapterOptions({
|
||||
models: [...offered, ...input.supported.filter(model => !offeredIds.has(model.id)).map(withOverride)],
|
||||
}).models
|
||||
return { offered: resolution.slice(0, offered.length), resolution, refusals, problems, overrideErrors }
|
||||
return { offered: resolution.slice(0, offered.length), resolution, refusals, badges, problems, overrideErrors }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
/** Credential-scoped `/models` cache with single-flight loads, background refresh, and last-known-good retention. */
|
||||
import { createHash } from 'node:crypto'
|
||||
import { deadline } from '@deepseek-ai/dsh-timeout'
|
||||
import { deadline, MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
|
||||
import { deepEqualJson } from '@deepseek-ai/dsh-util-values'
|
||||
import type { ListingSettings } from './listing-config.ts'
|
||||
import { LISTING_TIMEOUT } from './listing.ts'
|
||||
import type { ModelListing } from './listing.ts'
|
||||
|
||||
/** Host-owned inputs of one cache instance. */
|
||||
export interface ListingCacheDependencies {
|
||||
/** Latest route configuration for automatic refresh; `undefined` stops following the listing. */
|
||||
readonly settings: () => ListingSettings | undefined
|
||||
/** The credential the route sends to `url`, or `undefined` when it has none. */
|
||||
readonly credential: (url: string) => Promise<string | undefined>
|
||||
/** Perform one listing request authenticated with `credential`; `signal` carries the {@link LISTING_TIMEOUT} request deadline. */
|
||||
@@ -17,13 +20,15 @@ export interface ListingCacheDependencies {
|
||||
readonly info: (message: string) => void
|
||||
/** Report the first failure of a failing streak. */
|
||||
readonly warn: (message: string) => void
|
||||
/** Publish a changed view; readers can immediately read `current()`. */
|
||||
readonly changed: () => void
|
||||
}
|
||||
|
||||
/** What request resolution reads. */
|
||||
export interface ListingView {
|
||||
/** Whether a credential is available for the listing endpoint. */
|
||||
readonly authenticated: boolean
|
||||
/** Last successful listing for this credential and endpoint; `undefined` until one succeeds. */
|
||||
/** Last successful listing for this scope; `undefined` until one succeeds. */
|
||||
readonly listing: ModelListing | undefined
|
||||
}
|
||||
|
||||
@@ -51,6 +56,8 @@ export class ModelListingCache {
|
||||
private generation = 0
|
||||
private failing = false
|
||||
private closed = false
|
||||
private timer: ReturnType<typeof setTimeout> | undefined
|
||||
private readonly pending = new Set<Promise<void>>()
|
||||
|
||||
/** @param dependencies - credential, transport, clock, and diagnostic callbacks. */
|
||||
constructor(private readonly dependencies: ListingCacheDependencies) {}
|
||||
@@ -66,7 +73,8 @@ export class ModelListingCache {
|
||||
/**
|
||||
* Resolve the view for the current credential. The first access of a scope
|
||||
* waits for its load, which the request deadline bounds; later accesses
|
||||
* return at once and refresh a stale listing in the background.
|
||||
* return at once and refresh a stale listing in the background. After a load settles,
|
||||
* automatic refresh uses the latest route settings; failures retain the last successful listing, including badges.
|
||||
* @param settings - endpoint and timing of the caller's configuration generation.
|
||||
* @param signal - abandons this wait only; the shared load continues.
|
||||
* @returns the view after any first load of the scope settles.
|
||||
@@ -89,14 +97,20 @@ export class ModelListingCache {
|
||||
this.rescope(this.scope)
|
||||
}
|
||||
|
||||
/** Abort in-flight loads; later results and accesses change nothing. */
|
||||
dispose(): void {
|
||||
/**
|
||||
* Stop automatic refresh and abort loads; later results and accesses change nothing.
|
||||
* @returns completion of all admitted loads and automatic credential lookups.
|
||||
*/
|
||||
async dispose(): Promise<void> {
|
||||
this.closed = true
|
||||
this.generation += 1
|
||||
clearTimeout(this.timer)
|
||||
this.request?.abort()
|
||||
await Promise.allSettled(this.pending)
|
||||
}
|
||||
|
||||
private rescope(scope: string | undefined): void {
|
||||
clearTimeout(this.timer)
|
||||
this.request?.abort()
|
||||
this.scope = scope
|
||||
this.listing = undefined
|
||||
@@ -108,6 +122,7 @@ export class ModelListingCache {
|
||||
|
||||
private start(settings: ListingSettings, credential: string): Promise<void> {
|
||||
if (this.inflight !== undefined) return this.inflight
|
||||
clearTimeout(this.timer)
|
||||
this.generation += 1
|
||||
const generation = this.generation
|
||||
const request = new AbortController()
|
||||
@@ -116,10 +131,12 @@ export class ModelListingCache {
|
||||
const run = this.dependencies.load(settings.url, credential, limit.signal)
|
||||
.then((listing) => {
|
||||
if (generation !== this.generation) return
|
||||
const changed = !deepEqualJson(this.listing, listing)
|
||||
this.listing = listing
|
||||
this.settledAt = this.dependencies.now()
|
||||
this.failing = false
|
||||
this.dependencies.info(`model listing from ${settings.url} loaded ${listing.entries.length} model(s), skipped ${listing.skipped} row(s)`)
|
||||
if (changed) this.notify()
|
||||
}, (error: unknown) => {
|
||||
if (generation !== this.generation) return
|
||||
if (!this.failing) {
|
||||
@@ -131,9 +148,55 @@ export class ModelListingCache {
|
||||
})
|
||||
.finally(() => {
|
||||
limit[Symbol.dispose]()
|
||||
this.pending.delete(run)
|
||||
if (this.inflight === run) this.inflight = undefined
|
||||
if (generation === this.generation) this.schedule(settings)
|
||||
})
|
||||
this.inflight = run
|
||||
this.pending.add(run)
|
||||
return run
|
||||
}
|
||||
|
||||
private notify(): void {
|
||||
try { this.dependencies.changed() }
|
||||
catch (error) { this.dependencies.warn(`model listing change notification failed (${String(error)}); reload the model catalog`) }
|
||||
}
|
||||
|
||||
private schedule(settings: ListingSettings): void {
|
||||
clearTimeout(this.timer)
|
||||
if (this.closed || settings.maxAgeMs === 0) return
|
||||
const age = this.settledAt === undefined ? 0 : this.dependencies.now() - this.settledAt
|
||||
this.timer = setTimeout(() => {
|
||||
const operation = this.refresh(settings).finally(() => {
|
||||
this.pending.delete(operation)
|
||||
})
|
||||
this.pending.add(operation)
|
||||
}, Math.min(MAX_TIMER_DELAY_MS, Math.max(1, settings.maxAgeMs - age)))
|
||||
this.timer.unref()
|
||||
}
|
||||
|
||||
private async refresh(previous: ListingSettings): Promise<void> {
|
||||
let settings = previous
|
||||
try {
|
||||
const next = this.dependencies.settings()
|
||||
if (next === undefined) {
|
||||
this.invalidate()
|
||||
this.notify()
|
||||
return
|
||||
}
|
||||
settings = next
|
||||
const wasAuthenticated = this.current().authenticated
|
||||
await this.view(settings)
|
||||
if (this.closed) return
|
||||
if (wasAuthenticated && !this.current().authenticated) this.notify()
|
||||
} catch (error) {
|
||||
if (this.closed) return
|
||||
if (!this.failing) {
|
||||
this.dependencies.warn(`model listing refresh failed (${String(error)}); keeping the previous catalog; check listing configuration and credentials`)
|
||||
}
|
||||
this.failing = true
|
||||
this.settledAt = this.dependencies.now()
|
||||
}
|
||||
if (this.inflight === undefined) this.schedule(settings)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
|
||||
|
||||
/** Default deadline of one model-listing request and of a first-use wait. */
|
||||
export const DEFAULT_MODELS_TIMEOUT_MS = 5_000
|
||||
/** Default listing age after which the next access refreshes it in the background. */
|
||||
/** Default interval between automatic listing refreshes after first use. */
|
||||
export const DEFAULT_MODELS_MAX_AGE_MS = 600_000
|
||||
|
||||
/** Listing fields of a route Config. */
|
||||
@@ -13,8 +13,8 @@ export interface ModelListingConfig {
|
||||
/** Deadline of one listing request, which also bounds a first-use wait (default five seconds). */
|
||||
modelsTimeoutMs: Volatile<number>
|
||||
/**
|
||||
* Listing age after which the next access refreshes it in the background;
|
||||
* a failed load retries after the same age (default ten minutes).
|
||||
* Listing refresh interval after first use; failures retry at the same interval (default ten minutes).
|
||||
* Zero refreshes on each access and disables automatic refresh.
|
||||
*/
|
||||
modelsMaxAgeMs: Volatile<number>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
/** DeepSeek `GET /models` listing: one authenticated request and per-entry decoding. */
|
||||
import { APP_IDENTITY, attributionHeaders, LlmError } from '@deepseek-ai/dsh-llm'
|
||||
import type { ModelModality } from '@deepseek-ai/dsh-llm'
|
||||
import type { LlmModelBadge, LlmModelBadgeTone, ModelModality } from '@deepseek-ai/dsh-llm'
|
||||
import { timeoutOf } from '@deepseek-ai/dsh-timeout'
|
||||
|
||||
const LISTED_MODALITIES = ['text', 'image'] as const satisfies readonly ModelModality[]
|
||||
const BADGE_TONES = ['neutral', 'info', 'success', 'warning'] as const satisfies readonly LlmModelBadgeTone[]
|
||||
const LANGUAGE_ID = /^[a-z]{2,8}(?:-[a-z0-9]{1,8})*$/u
|
||||
const UNREADABLE_BADGE_CHARACTER = /[\p{Cc}\p{Zl}\p{Zp}\p{Bidi_Control}]/u
|
||||
const VISIBLE_BADGE_CHARACTER = /[^\p{White_Space}\p{Default_Ignorable_Code_Point}]/u
|
||||
const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: 'grapheme' })
|
||||
|
||||
/** Longest badge text this build displays, in user-perceived characters, for every translation. */
|
||||
export const MAX_BADGE_TEXT_LENGTH = 12
|
||||
|
||||
/** Code of a listing deadline's timeout reason; {@link requestModelListing} reports it as `TIMEOUT`. */
|
||||
export const LISTING_TIMEOUT = 'MODEL_LISTING_TIMEOUT'
|
||||
@@ -37,6 +45,11 @@ export interface ListedFacts {
|
||||
readonly toolUpdate?: 'in-history' | 'addition-only' | 'leading-only'
|
||||
}
|
||||
|
||||
/** A badge one listing entry declares, or why this build cannot display it. */
|
||||
export type ListedBadge =
|
||||
| { readonly kind: 'badge'; readonly badge: LlmModelBadge }
|
||||
| { readonly kind: 'invalid'; readonly reason: string }
|
||||
|
||||
/** Declared capabilities, or why the Messages transport cannot drive the model. */
|
||||
export type ListedCapabilities =
|
||||
| { readonly usable: true; readonly facts: ListedFacts }
|
||||
@@ -52,6 +65,11 @@ export interface ListingEntry {
|
||||
readonly requirement: ClientRequirement
|
||||
/** Declared capabilities. */
|
||||
readonly capabilities: ListedCapabilities
|
||||
/**
|
||||
* Selector badge declared under `client_metadata[APP_IDENTITY.product].badge`; absent when the
|
||||
* entry declares none, and never a reason to hide the entry.
|
||||
*/
|
||||
readonly badge?: ListedBadge
|
||||
}
|
||||
|
||||
/** A decoded listing with at least one entry. */
|
||||
@@ -96,6 +114,61 @@ function requirementOf(entry: Fields): ClientRequirement {
|
||||
: { kind: 'invalid' }
|
||||
}
|
||||
|
||||
/** One trimmed, visible badge text on one line, without control characters and at most {@link MAX_BADGE_TEXT_LENGTH} graphemes. */
|
||||
function badgeText(value: unknown): string | undefined {
|
||||
if (typeof value !== 'string') return undefined
|
||||
const text = value.trim()
|
||||
const length = Array.from(GRAPHEMES.segment(text)).length
|
||||
return length <= MAX_BADGE_TEXT_LENGTH && VISIBLE_BADGE_CHARACTER.test(text) && !UNREADABLE_BADGE_CHARACTER.test(value)
|
||||
? text : undefined
|
||||
}
|
||||
|
||||
/** A literal badge text, or translations keyed by lowercase language id that include English. */
|
||||
function localizedBadgeText(value: unknown): LlmModelBadge['text'] | undefined {
|
||||
if (typeof value === 'string') return badgeText(value)
|
||||
const variants = fieldsOf(value)
|
||||
if (variants === undefined) return undefined
|
||||
const texts: [string, string][] = []
|
||||
for (const [language, variant] of Object.entries(variants)) {
|
||||
const text = badgeText(variant)
|
||||
if (!LANGUAGE_ID.test(language) || text === undefined) return undefined
|
||||
texts.push([language, text])
|
||||
}
|
||||
const translations = Object.fromEntries(texts)
|
||||
const en = translations.en
|
||||
return en === undefined ? undefined : { ...translations, en }
|
||||
}
|
||||
|
||||
/** This client's entry in a listing entry's `client_metadata`, as diagnostics name it. */
|
||||
const OWN_METADATA = `client_metadata["${APP_IDENTITY.product}"]`
|
||||
|
||||
/** An optional object field: `undefined` when absent or `null`, `false` when it is not an object. */
|
||||
function optionalFields(value: unknown): Fields | undefined | false {
|
||||
return value === undefined || value === null ? undefined : fieldsOf(value) ?? false
|
||||
}
|
||||
|
||||
function badgeOf(entry: Fields): ListedBadge | undefined {
|
||||
// `null` at any level declares no badge, like an absent field; this build ships none to fall back on.
|
||||
const metadata = optionalFields(entry.client_metadata)
|
||||
if (metadata === false) return { kind: 'invalid', reason: 'client_metadata is not an object' }
|
||||
const own = optionalFields(metadata?.[APP_IDENTITY.product])
|
||||
if (own === false) return { kind: 'invalid', reason: `${OWN_METADATA} is not an object` }
|
||||
const declared = optionalFields(own?.badge)
|
||||
if (declared === false) return { kind: 'invalid', reason: `${OWN_METADATA}.badge is not an object` }
|
||||
if (declared === undefined) return undefined
|
||||
const text = localizedBadgeText(declared.text)
|
||||
if (text === undefined) {
|
||||
return {
|
||||
kind: 'invalid',
|
||||
reason: `${OWN_METADATA}.badge.text is not a string or an object of lowercase language ids including "en", `
|
||||
+ `each text 1 to ${MAX_BADGE_TEXT_LENGTH} characters on one line`,
|
||||
}
|
||||
}
|
||||
// An absent or unrecognized tone keeps the text at the least emphasis.
|
||||
const tone = BADGE_TONES.find(candidate => candidate === declared.tone) ?? 'neutral'
|
||||
return { kind: 'badge', badge: { text, tone } }
|
||||
}
|
||||
|
||||
function capabilitiesOf(entry: Fields): ListedCapabilities {
|
||||
const messages = fieldsOf(fieldsOf(entry.api_capabilities)?.anthropic_messages)
|
||||
if (messages === undefined) return { usable: false, reason: 'declares no api_capabilities.anthropic_messages' }
|
||||
@@ -142,11 +215,13 @@ export function parseModelListing(body: unknown): ModelListing {
|
||||
const id = entry?.id
|
||||
if (entry === undefined || typeof id !== 'string' || id.length === 0 || seen.has(id)) continue
|
||||
seen.add(id)
|
||||
const badge = badgeOf(entry)
|
||||
entries.push({
|
||||
id,
|
||||
...typeof entry.name === 'string' && entry.name.length > 0 ? { name: entry.name } : {},
|
||||
requirement: requirementOf(entry),
|
||||
capabilities: capabilitiesOf(entry),
|
||||
...badge === undefined ? {} : { badge },
|
||||
})
|
||||
}
|
||||
if (entries.length === 0) throw new LlmError('DeepSeek model listing contains no model entry', 'INVALID_RESPONSE')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Protocol-independent model capabilities and reasoning choices. */
|
||||
import { ReasoningEffortId } from '@deepseek-ai/dsh-llm'
|
||||
import type { LlmModelInfo, LlmResolvedModelInfo } from '@deepseek-ai/dsh-llm'
|
||||
import type { LlmModelBadge, LlmModelInfo, LlmResolvedModelInfo } from '@deepseek-ai/dsh-llm'
|
||||
import type { DeepSeekCatalogModel, DeepSeekConnectionOptions } from './types.ts'
|
||||
|
||||
const OFF_REASONING_EFFORT = ReasoningEffortId('off')
|
||||
@@ -40,15 +40,17 @@ const OFF_ONLY_REASONING_EFFORTS = [
|
||||
/** Advertise one catalog entry.
|
||||
* @param provider - registered provider id.
|
||||
* @param model - advisory catalog entry.
|
||||
* @param badge - selector badge a model listing declares for the entry.
|
||||
* @returns selector metadata.
|
||||
*/
|
||||
export function catalogModelInfo(provider: string, model: DeepSeekCatalogModel): LlmModelInfo {
|
||||
export function catalogModelInfo(provider: string, model: DeepSeekCatalogModel, badge?: LlmModelBadge): LlmModelInfo {
|
||||
return {
|
||||
provider,
|
||||
id: model.id,
|
||||
name: model.name ?? model.id,
|
||||
...model.description === undefined ? {} : { description: model.description },
|
||||
inputModalities: model.inputModalities ?? ['text'],
|
||||
...badge === undefined ? {} : { badge },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ const PRO = {
|
||||
api_capabilities: { anthropic_messages: { system_prompt_update: 'leading-only' } },
|
||||
}
|
||||
const requires = (version: unknown) => ({ client_requirements: { 'deepseek-harness': { min_version: version } } })
|
||||
const shows = (badge: unknown) => ({ client_metadata: { 'deepseek-harness': { badge } } })
|
||||
const resolve = (rows: object[] | undefined, overrides: Record<string, ModelOverride> = {}) => resolveListedCatalog({
|
||||
listing: rows === undefined ? undefined : parseModelListing({ data: rows }).entries,
|
||||
supported: resolveAdapterOptions({}).models,
|
||||
@@ -33,6 +34,7 @@ describe('without a listing', () => {
|
||||
])
|
||||
expect(catalog.resolution).toEqual(catalog.offered)
|
||||
expect(catalog.refusals.size).toBe(0)
|
||||
expect(catalog.badges.size).toBe(0)
|
||||
})
|
||||
|
||||
it('applies overrides and does not judge override ids it cannot see', () => {
|
||||
@@ -138,6 +140,21 @@ describe('with a listing', () => {
|
||||
expect(catalog.refusals.get('deepseek-v4-pro')).toEqual({ kind: 'unsupported' })
|
||||
})
|
||||
|
||||
it('collects badges of offered models only and reports a dropped badge', () => {
|
||||
const badge = { text: { en: 'New', zh: '新' }, tone: 'info' }
|
||||
const catalog = resolve([
|
||||
{ ...FLASH, ...shows(badge) },
|
||||
{ ...PRO, ...shows({ text: '' }) },
|
||||
{ ...FLASH, id: 'deepseek-v5', ...shows(badge) },
|
||||
])
|
||||
expect(catalog.offered.map(model => model.id)).toEqual(['deepseek-flash', 'deepseek-v4-pro'])
|
||||
expect([...catalog.badges]).toEqual([['deepseek-flash', badge]])
|
||||
expect(catalog.offered[0]).not.toHaveProperty('badge')
|
||||
expect(catalog.problems).toEqual([
|
||||
expect.stringMatching(/^model "deepseek-v4-pro" shows no badge: client_metadata\["deepseek-harness"\]\.badge\.text is not a string/),
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps bundled knowledge, with overrides, for request resolution of models the listing does not offer', () => {
|
||||
const catalog = resolve([PRO], { 'deepseek-flash': { contextWindow: 7 } })
|
||||
expect(catalog.offered.map(model => model.id)).toEqual(['deepseek-v4-pro'])
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/** Credential-scoped listing cache: first-use waits, background refresh, retention, and disposal. */
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import { afterEach, expect, it, onTestFinished, vi } from 'vitest'
|
||||
import { timeoutOf } from '@deepseek-ai/dsh-timeout'
|
||||
import { ModelListingCache } from '../src/listing-cache.ts'
|
||||
import type { ListingSettings } from '../src/listing-config.ts'
|
||||
import { LISTING_TIMEOUT } from '../src/listing.ts'
|
||||
import type { ModelListing } from '../src/listing.ts'
|
||||
|
||||
afterEach(() => { vi.useRealTimers() })
|
||||
|
||||
const listing = (...ids: string[]): ModelListing => ({
|
||||
skipped: 0,
|
||||
entries: ids.map(id => ({ id, requirement: { kind: 'none' }, capabilities: { usable: true, facts: { inputModalities: ['text'] } } })),
|
||||
@@ -17,9 +19,13 @@ function harness(timeoutMs = 60_000, honorsAbort = true) {
|
||||
const loads: Array<{ token: string; signal: AbortSignal; settle: PromiseWithResolvers<ModelListing> }> = []
|
||||
const info = vi.fn()
|
||||
const warn = vi.fn()
|
||||
const settings: ListingSettings = { url: 'https://api.example/models', timeoutMs, maxAgeMs: 100 }
|
||||
const changed = vi.fn()
|
||||
const settings: { -readonly [K in keyof ListingSettings]: ListingSettings[K] } = { url: 'https://api.example/models', timeoutMs, maxAgeMs: 100 }
|
||||
const resolveSettings = vi.fn<() => ListingSettings | undefined>(() => settings)
|
||||
const resolveCredential = vi.fn(() => Promise.resolve(token))
|
||||
const cache = new ModelListingCache({
|
||||
credential: () => Promise.resolve(token),
|
||||
settings: resolveSettings,
|
||||
credential: resolveCredential,
|
||||
load: (_url, value, signal) => {
|
||||
const settle = Promise.withResolvers<ModelListing>()
|
||||
if (honorsAbort) signal.addEventListener('abort', () => { settle.reject(signal.reason) }, { once: true })
|
||||
@@ -29,17 +35,19 @@ function harness(timeoutMs = 60_000, honorsAbort = true) {
|
||||
now: () => clock,
|
||||
info,
|
||||
warn,
|
||||
changed,
|
||||
})
|
||||
onTestFinished(() => cache.dispose())
|
||||
const flush = () => new Promise(resolve => setImmediate(resolve))
|
||||
// Every access passes the same configuration generation.
|
||||
const catalog = {
|
||||
view: (signal?: AbortSignal) => cache.view(settings, signal),
|
||||
current: () => cache.current(),
|
||||
invalidate: () => { cache.invalidate() },
|
||||
dispose: () => { cache.dispose() },
|
||||
dispose: () => cache.dispose(),
|
||||
}
|
||||
return {
|
||||
catalog, loads, info, warn, flush,
|
||||
catalog, loads, info, warn, changed, flush, settings, resolveSettings, resolveCredential,
|
||||
advance: (ms: number) => { clock += ms },
|
||||
credential: (value: string | undefined) => { token = value },
|
||||
}
|
||||
@@ -143,7 +151,7 @@ it('aborts in-flight work on dispose and loads nothing afterwards', async () =>
|
||||
const { catalog, loads, warn, flush } = harness()
|
||||
const waiting = catalog.view()
|
||||
await flush()
|
||||
catalog.dispose()
|
||||
await catalog.dispose()
|
||||
expect(loads[0]!.signal.aborted).toBe(true)
|
||||
await waiting
|
||||
await expect(catalog.view()).resolves.toEqual({ authenticated: true, listing: undefined })
|
||||
@@ -155,8 +163,9 @@ it('drops a successful result that settles after disposal', async () => {
|
||||
const { catalog, loads, flush } = harness(60_000, false)
|
||||
const waiting = catalog.view()
|
||||
await flush()
|
||||
catalog.dispose()
|
||||
const disposal = catalog.dispose()
|
||||
loads[0]!.settle.resolve(listing('late'))
|
||||
await disposal
|
||||
await waiting
|
||||
expect(catalog.current().listing).toBeUndefined()
|
||||
})
|
||||
@@ -189,3 +198,219 @@ it('aborts an in-flight load on invalidation and ignores its result', async () =
|
||||
expect(catalog.current().listing).toBeUndefined()
|
||||
expect(warn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
const badgedListing = (text: string): ModelListing => ({
|
||||
...listing('a'),
|
||||
entries: listing('a').entries.map(entry => ({ ...entry, badge: { kind: 'badge', badge: { text, tone: 'info' } } })),
|
||||
})
|
||||
|
||||
it('automatically publishes replacement badges and their removal without another read', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const { catalog, loads, changed, flush, advance } = harness()
|
||||
const first = catalog.view()
|
||||
await flush()
|
||||
loads[0]!.settle.resolve(badgedListing('New'))
|
||||
await first
|
||||
for (const next of [badgedListing('Updated'), listing('a')]) {
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
const request = loads.at(-1)!
|
||||
expect(request).not.toBe(loads[0])
|
||||
request.settle.resolve(next)
|
||||
await flush()
|
||||
expect(catalog.current().listing).toEqual(next)
|
||||
}
|
||||
expect(loads).toHaveLength(3)
|
||||
expect(changed).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it.each(['failure', 'timeout'] as const)('retains badges after refresh %s and replaces them after recovery', async (failure) => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const { catalog, loads, changed, warn, flush, advance } = harness(50)
|
||||
const first = catalog.view()
|
||||
await flush()
|
||||
loads[0]!.settle.resolve(badgedListing('New'))
|
||||
await first
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
if (failure === 'timeout') {
|
||||
advance(50)
|
||||
await vi.advanceTimersByTimeAsync(50)
|
||||
expect(loads[1]!.signal.aborted).toBe(true)
|
||||
} else {
|
||||
loads[1]!.settle.reject(new Error('offline'))
|
||||
}
|
||||
await flush()
|
||||
expect(catalog.current().listing).toEqual(badgedListing('New'))
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('keeping the previous catalog'))
|
||||
expect(changed).toHaveBeenCalledTimes(1)
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
loads[2]!.settle.resolve(badgedListing('Updated'))
|
||||
await flush()
|
||||
expect(catalog.current().listing).toEqual(badgedListing('Updated'))
|
||||
expect(changed).toHaveBeenCalledTimes(2)
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
loads[3]!.settle.resolve(listing('a'))
|
||||
await flush()
|
||||
expect(catalog.current().listing).toEqual(listing('a'))
|
||||
expect(changed).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('does not notify for identical listings and stops its timer on disposal', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const { catalog, loads, changed, flush, advance } = harness()
|
||||
const first = catalog.view()
|
||||
await flush()
|
||||
loads[0]!.settle.resolve(badgedListing('New'))
|
||||
await first
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
loads[1]!.settle.resolve(badgedListing('New'))
|
||||
await flush()
|
||||
expect(changed).toHaveBeenCalledTimes(1)
|
||||
await catalog.dispose()
|
||||
advance(1000)
|
||||
await vi.advanceTimersByTimeAsync(1000)
|
||||
expect(loads).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('awaits an admitted automatic load on disposal and suppresses its late publication', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const { catalog, loads, changed, flush, advance } = harness(60_000, false)
|
||||
const first = catalog.view()
|
||||
await flush()
|
||||
loads[0]!.settle.resolve(badgedListing('New'))
|
||||
await first
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
let disposed = false
|
||||
const disposal = catalog.dispose().then(() => { disposed = true })
|
||||
await flush()
|
||||
expect(disposed).toBe(false)
|
||||
expect(loads[1]!.signal.aborted).toBe(true)
|
||||
loads[1]!.settle.resolve(badgedListing('Late'))
|
||||
await disposal
|
||||
expect(catalog.current().listing).toEqual(badgedListing('New'))
|
||||
expect(changed).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('keeps zero max age as access-only refresh without an automatic loop', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const { catalog, loads, flush, advance, settings } = harness()
|
||||
settings.maxAgeMs = 0
|
||||
const first = catalog.view()
|
||||
await flush()
|
||||
loads[0]!.settle.resolve(listing('a'))
|
||||
await first
|
||||
advance(1000)
|
||||
await vi.advanceTimersByTimeAsync(1000)
|
||||
expect(loads).toHaveLength(1)
|
||||
await catalog.view()
|
||||
expect(loads).toHaveLength(2)
|
||||
loads[1]!.settle.resolve(listing('b'))
|
||||
await flush()
|
||||
})
|
||||
|
||||
it('uses current configuration and stops following a disabled listing', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const { catalog, loads, changed, flush, advance, settings, resolveSettings } = harness()
|
||||
const first = catalog.view()
|
||||
await flush()
|
||||
loads[0]!.settle.resolve(badgedListing('New'))
|
||||
await first
|
||||
settings.maxAgeMs = 200
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
expect(loads).toHaveLength(1)
|
||||
resolveSettings.mockReturnValue(undefined)
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
expect(catalog.current().listing).toBeUndefined()
|
||||
expect(changed).toHaveBeenCalledTimes(2)
|
||||
advance(1000)
|
||||
await vi.advanceTimersByTimeAsync(1000)
|
||||
expect(loads).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('removes a signed-out listing and discovers a replacement credential automatically', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const { catalog, loads, changed, flush, advance, credential } = harness()
|
||||
const first = catalog.view()
|
||||
await flush()
|
||||
loads[0]!.settle.resolve(badgedListing('New'))
|
||||
await first
|
||||
credential(undefined)
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
expect(catalog.current()).toEqual({ authenticated: false, listing: undefined })
|
||||
expect(changed).toHaveBeenCalledTimes(2)
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
expect(changed).toHaveBeenCalledTimes(2)
|
||||
credential('replacement')
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
expect(loads[1]!.token).toBe('replacement')
|
||||
loads[1]!.settle.resolve(listing('b'))
|
||||
await flush()
|
||||
expect(catalog.current().listing).toEqual(listing('b'))
|
||||
})
|
||||
|
||||
it('retains badges on credential lookup failures, reports the streak once, and retries', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const { catalog, loads, changed, warn, flush, advance, resolveCredential } = harness()
|
||||
const first = catalog.view()
|
||||
await flush()
|
||||
loads[0]!.settle.resolve(badgedListing('New'))
|
||||
await first
|
||||
resolveCredential.mockRejectedValue(new Error('credentials offline'))
|
||||
for (let index = 0; index < 2; index++) {
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
}
|
||||
expect(catalog.current().listing).toEqual(badgedListing('New'))
|
||||
expect(changed).toHaveBeenCalledTimes(1)
|
||||
expect(warn).toHaveBeenCalledExactlyOnceWith(expect.stringContaining('check listing configuration and credentials'))
|
||||
resolveCredential.mockResolvedValue('token-a')
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
loads[1]!.settle.resolve(badgedListing('Back'))
|
||||
await flush()
|
||||
expect(catalog.current().listing).toEqual(badgedListing('Back'))
|
||||
expect(changed).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it.each(['resolve', 'reject'] as const)('awaits a pending automatic credential lookup on disposal (%s)', async (outcome) => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const { catalog, loads, changed, warn, flush, advance, resolveCredential } = harness()
|
||||
const first = catalog.view()
|
||||
await flush()
|
||||
loads[0]!.settle.resolve(listing('a'))
|
||||
await first
|
||||
const credential = Promise.withResolvers<string | undefined>()
|
||||
resolveCredential.mockReturnValue(credential.promise)
|
||||
advance(100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
let disposed = false
|
||||
const disposal = catalog.dispose().then(() => { disposed = true })
|
||||
await flush()
|
||||
expect(disposed).toBe(false)
|
||||
if (outcome === 'resolve') credential.resolve('token-a')
|
||||
else credential.reject(new Error('lookup failed'))
|
||||
await disposal
|
||||
expect(loads).toHaveLength(1)
|
||||
expect(changed).toHaveBeenCalledTimes(1)
|
||||
expect(warn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps the committed listing and reports a failed change observer', async () => {
|
||||
const { catalog, loads, changed, warn, flush } = harness()
|
||||
changed.mockImplementation(() => { throw new Error('observer failed') })
|
||||
const first = catalog.view()
|
||||
await flush()
|
||||
loads[0]!.settle.resolve(listing('a'))
|
||||
await expect(first).resolves.toMatchObject({ listing: listing('a') })
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('change notification failed'))
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { IncomingMessage, Server, ServerResponse } from 'node:http'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { APP_IDENTITY } from '@deepseek-ai/dsh-llm'
|
||||
import { deadline } from '@deepseek-ai/dsh-timeout'
|
||||
import { LISTING_TIMEOUT, parseModelListing, requestModelListing } from '../src/listing.ts'
|
||||
import { LISTING_TIMEOUT, MAX_BADGE_TEXT_LENGTH, parseModelListing, requestModelListing } from '../src/listing.ts'
|
||||
import type { ListedCapabilities } from '../src/listing.ts'
|
||||
|
||||
/** The listing the service returned to an account token on 2026-09-24. */
|
||||
@@ -109,6 +109,89 @@ describe('parseModelListing', () => {
|
||||
expect(only({ api_capabilities: { anthropic_messages: { system_prompt_update: null, tool_update: null } } })?.capabilities)
|
||||
.toEqual({ usable: true, facts: { inputModalities: ['text'] } })
|
||||
})
|
||||
|
||||
const OWN = `client_metadata["${APP_IDENTITY.product}"]`
|
||||
const showing = (badge: unknown) => ({ ...MESSAGES, client_metadata: { [APP_IDENTITY.product]: { badge } } })
|
||||
const longest = '十'.repeat(MAX_BADGE_TEXT_LENGTH)
|
||||
it.each([
|
||||
[{ text: '1017', tone: 'info' }, { text: '1017', tone: 'info' }],
|
||||
[{ text: ' 1017 ', tone: 'success' }, { text: '1017', tone: 'success' }],
|
||||
[{ text: longest, tone: 'warning' }, { text: longest, tone: 'warning' }],
|
||||
[{ text: '👍🏽'.repeat(MAX_BADGE_TEXT_LENGTH) }, { text: '👍🏽'.repeat(MAX_BADGE_TEXT_LENGTH), tone: 'neutral' }],
|
||||
[{ text: '👨👩👧'.repeat(MAX_BADGE_TEXT_LENGTH) }, { text: '👨👩👧'.repeat(MAX_BADGE_TEXT_LENGTH), tone: 'neutral' }],
|
||||
[{ text: 'Beta' }, { text: 'Beta', tone: 'neutral' }],
|
||||
[{ text: 'Beta', tone: null }, { text: 'Beta', tone: 'neutral' }],
|
||||
[{ text: 'Beta', tone: 'accent' }, { text: 'Beta', tone: 'neutral' }],
|
||||
[{ text: 'Beta', tone: 1 }, { text: 'Beta', tone: 'neutral' }],
|
||||
[{ text: { en: 'New', zh: '新', 'zh-hant': ' 新 ' }, tone: 'info' }, { text: { en: 'New', zh: '新', 'zh-hant': '新' }, tone: 'info' }],
|
||||
])('decodes badge %j', (badge, expected) => {
|
||||
expect(only(showing(badge))?.badge).toEqual({ kind: 'badge', badge: expected })
|
||||
})
|
||||
|
||||
it.each([
|
||||
undefined,
|
||||
null,
|
||||
{},
|
||||
{ other: { badge: { text: '1017' } } },
|
||||
{ [APP_IDENTITY.product]: null },
|
||||
{ [APP_IDENTITY.product]: { other_field: 1 } },
|
||||
{ [APP_IDENTITY.product]: { badge: null } },
|
||||
])('declares no badge for client_metadata %j', (metadata) => {
|
||||
expect(only({ ...MESSAGES, client_metadata: metadata })).not.toHaveProperty('badge')
|
||||
})
|
||||
|
||||
it('reads badges only from client_metadata', () => {
|
||||
const badge = { text: '1017' }
|
||||
expect(only({ ...MESSAGES, badge, client_requirements: { [APP_IDENTITY.product]: { badge } } })).not.toHaveProperty('badge')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['1017', 'client_metadata is not an object'],
|
||||
[['1017'], 'client_metadata is not an object'],
|
||||
[{ [APP_IDENTITY.product]: '1017' }, `${OWN} is not an object`],
|
||||
[{ [APP_IDENTITY.product]: [] }, `${OWN} is not an object`],
|
||||
[{ [APP_IDENTITY.product]: { badge: '1017' } }, `${OWN}.badge is not an object`],
|
||||
[{ [APP_IDENTITY.product]: { badge: ['1017'] } }, `${OWN}.badge is not an object`],
|
||||
])('rejects client_metadata %j without hiding the entry', (metadata, reason) => {
|
||||
const entry = only({ ...MESSAGES, client_metadata: metadata })
|
||||
expect(entry?.badge).toEqual({ kind: 'invalid', reason })
|
||||
expect(entry?.capabilities.usable).toBe(true)
|
||||
})
|
||||
|
||||
const unreadableText = expect.stringContaining(`${OWN}.badge.text is not a string or an object of lowercase language ids`) as string
|
||||
it.each([
|
||||
{ tone: 'info' },
|
||||
{ text: null },
|
||||
{ text: ' ' },
|
||||
{ text: `${longest}十` },
|
||||
{ text: 'line\nbreak' },
|
||||
{ text: '\nNew' },
|
||||
{ text: 'New\u2028Model' },
|
||||
{ text: 'New\u2029Model' },
|
||||
{ text: 'New\u202e' },
|
||||
{ text: '\u2066New\u2069' },
|
||||
{ text: '\u200b' },
|
||||
{ text: '\u200d' },
|
||||
{ text: '\ufe0f' },
|
||||
{ text: ' \u00ad\u200b ' },
|
||||
{ text: 7 },
|
||||
{ text: { zh: '新' } },
|
||||
{ text: { en: 'New', ZH: '新' } },
|
||||
{ text: { en: 'New', zh: '' } },
|
||||
{ text: { en: 'New', zh: '\u200b' } },
|
||||
{ text: { en: 1 } },
|
||||
])('rejects badge %j without hiding the entry', (badge) => {
|
||||
const entry = only(showing(badge))
|
||||
expect(entry?.badge).toEqual({ kind: 'invalid', reason: unreadableText })
|
||||
expect(entry?.capabilities.usable).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects an own "__proto__" translation key', () => {
|
||||
const body: unknown = JSON.parse(
|
||||
`{"data":[{"id":"m","client_metadata":{"${APP_IDENTITY.product}":{"badge":{"text":{"en":"New","__proto__":"x"}}}}}]}`,
|
||||
)
|
||||
expect(parseModelListing(body).entries[0]?.badge).toEqual({ kind: 'invalid', reason: unreadableText })
|
||||
})
|
||||
})
|
||||
|
||||
describe('requestModelListing', () => {
|
||||
|
||||
@@ -182,6 +182,26 @@ describe('llm-deepseek real dynamic composition', () => {
|
||||
expect(keys).toEqual(['boot-key', 'boot-key'])
|
||||
})
|
||||
|
||||
it('advertises listed badges through the composed route and drops them with a customized catalog', async () => {
|
||||
vi.stubEnv('DEEPSEEK_API_KEY', '')
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const messages = { api_capabilities: { anthropic_messages: {} } }
|
||||
vi.spyOn(globalThis, 'fetch').mockImplementation(() => Promise.resolve(new Response(JSON.stringify({ data: [
|
||||
{ id: 'deepseek-flash', ...messages, client_metadata: { 'deepseek-harness': { badge: { text: { en: 'New', zh: '新' }, tone: 'info' } } } },
|
||||
{ id: 'deepseek-v4-pro', ...messages, client_metadata: { 'deepseek-harness': { badge: { text: 'far too long a badge' } } } },
|
||||
] }))))
|
||||
const { ctx } = await loadComposition({ withDynamic: true, baseURL: 'https://api.deepseek.com/anthropic' })
|
||||
const badges = async () => (await ctx.llm.listModels('deepseek-official')).map(model => [model.id, model.badge])
|
||||
expect(await badges()).toEqual([
|
||||
['deepseek-flash', { text: { en: 'New', zh: '新' }, tone: 'info' }],
|
||||
['deepseek-v4-pro', undefined],
|
||||
])
|
||||
expect(warn).toHaveBeenCalledWith('[llm-deepseek-api-key]', expect.stringMatching(/^model "deepseek-v4-pro" shows no badge: /))
|
||||
await ctx.settings.mutate(NS, [{ op: 'set', path: ['models'], value: [{ id: 'deepseek-flash' }] }])
|
||||
expect(await badges()).toEqual([['deepseek-flash', undefined]])
|
||||
})
|
||||
|
||||
it('keeps package inventory on when the Loader composition disables session upload', async () => {
|
||||
vi.stubEnv('DEEPSEEK_API_KEY', 'entry-key')
|
||||
const server = await mockServer([{ kind: 'sse', events: textEvents }])
|
||||
|
||||
@@ -7,3 +7,10 @@ it('advertises a minimal model declaration as text input with its ID as the labe
|
||||
provider: 'deepseek-official', id: 'embedded-model', name: 'embedded-model', inputModalities: ['text'],
|
||||
})
|
||||
})
|
||||
|
||||
it('advertises a listed badge beside the catalog entry', () => {
|
||||
const badge = { text: '1017', tone: 'info' } as const
|
||||
expect(catalogModelInfo('deepseek-official', { id: 'deepseek-flash', name: 'DeepSeek-V4.1-Flash' }, badge)).toEqual({
|
||||
provider: 'deepseek-official', id: 'deepseek-flash', name: 'DeepSeek-V4.1-Flash', inputModalities: ['text'], badge,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
/deepseek-ai-dsh-llm/use-this-package:
|
||||
en: 33738e61ce410ac0
|
||||
zh: 363760460f4d4bed
|
||||
en: b8fbcecd62f6cad0
|
||||
zh: d6eb81611ef0f3c5
|
||||
/deepseek-ai-dsh-llm/use-this-package/when-to-choose-it:
|
||||
en: c3ecb5a1421f27db
|
||||
zh: 4bbd3c0e513845e1
|
||||
|
||||
@@ -25,7 +25,7 @@ Use `@deepseek-ai/dsh-llm` to stream model calls through configured provider ada
|
||||
<a id="use-this-package"></a>
|
||||
## Use this package
|
||||
|
||||
`listModels` describes models offered by catalog-driven interfaces. Core resolution and streaming can still accept unlisted ids. The GUI requires catalog membership for selection and submission; adapters intended for GUI use must implement `listModels` and advertise their available models. The base implementation returns an empty list and therefore offers no GUI models.
|
||||
`listModels` describes models offered by catalog-driven interfaces. Core resolution and streaming can still accept unlisted ids. The GUI requires catalog membership for selection and submission; adapters intended for GUI use must implement `listModels` and advertise their available models. The base implementation returns an empty list and therefore offers no GUI models. An entry's optional `badge` holds selector text (one literal, or translations keyed by lowercase language id that include `en`) and a `neutral`, `info`, `success`, or `warning` tone; `resolveModelInfo` omits it, and no model request carries it. Providers publish `llm/models-updated(provider)` when their advertised catalog changes; clients reload the complete catalog, including omitted badges.
|
||||
|
||||
Any composition that calls a model provider — an agent loop, a session-title generator, a compaction summarizer — streams its requests through this service. Mount it together with at least one provider adapter; the service itself has no configuration and no provider wire code.
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ kind: "package-reference"
|
||||
<a id="use-this-package"></a>
|
||||
## 使用本包
|
||||
|
||||
`listModels` 描述目录驱动界面提供的模型。核心解析与流式调用仍可接受未列出的 ID。GUI 的模型选择与提交要求模型出现在目录中;供 GUI 使用的适配器必须实现 `listModels`,公布其可用模型。基类实现返回空列表,因此不向 GUI 提供模型。
|
||||
`listModels` 描述目录驱动界面提供的模型。核心解析与流式调用仍可接受未列出的 ID。GUI 的模型选择与提交要求模型出现在目录中;供 GUI 使用的适配器必须实现 `listModels`,公布其可用模型。基类实现返回空列表,因此不向 GUI 提供模型。条目可选的 `badge` 包含选择器文字(单一文字,或以小写语言 id 为键且包含 `en` 的译文)和 `neutral`、`info`、`success` 或 `warning` 语调;`resolveModelInfo` 不返回它,模型请求也不携带它。 提供方在公布的目录变化时发出 `llm/models-updated(provider)`;客户端重新加载完整目录,省略的角标也随之清除。
|
||||
|
||||
任何调用模型提供方的组合——agent loop(智能体循环)、会话标题生成器、压缩(compaction)摘要器——都会通过本服务流式发起请求。与至少一个提供方适配器一起挂载它;服务本身没有任何配置,也不包含提供方协议代码。
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-attachment": "workspace:*",
|
||||
"@deepseek-ai/dsh-package-manifest": "workspace:*",
|
||||
"@deepseek-ai/cordis": "workspace:~"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,12 +734,16 @@ export class LlmRuntime extends TypertRemoteService {
|
||||
}
|
||||
seen.add(model.id)
|
||||
const inputModalities = this.detachedModalities(model.inputModalities)
|
||||
const badge = model.badge
|
||||
return {
|
||||
provider: model.provider,
|
||||
id: model.id,
|
||||
name: model.name,
|
||||
...model.description === undefined ? {} : { description: model.description },
|
||||
...inputModalities === undefined ? {} : { inputModalities },
|
||||
...badge === undefined ? {} : {
|
||||
badge: { text: typeof badge.text === 'string' ? badge.text : { ...badge.text }, tone: badge.tone },
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user