@deepseek-ai/dsh-llm-deepseek-account 0.2.1-alpha.2 → 0.2.1-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +16 -16
- package/README.md +34 -9
- package/README.zh.md +34 -9
- package/lib/index.js +505 -22
- package/lib/types/catalog.d.ts +57 -0
- package/lib/types/config.d.ts +101 -40
- package/lib/types/listing.d.ts +68 -0
- package/lib/types/resolve-catalog.d.ts +45 -0
- package/locale/en.json +5 -0
- package/locale/zh.json +6 -0
- package/package.json +26 -15
package/README.i18n.yaml
CHANGED
|
@@ -3,26 +3,26 @@
|
|
|
3
3
|
# After editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/llm/llm-deepseek-account/README.md
|
|
5
5
|
/:
|
|
6
|
-
en:
|
|
7
|
-
zh:
|
|
6
|
+
en: fc1ff226d5dca691
|
|
7
|
+
zh: 770a99ef368c4767
|
|
8
8
|
/deepseek-ai-dsh-llm-deepseek-account:
|
|
9
9
|
en: 8fa1148e0ee9d351
|
|
10
10
|
zh: 634ec9d79d5ffbf0
|
|
11
11
|
/deepseek-ai-dsh-llm-deepseek-account/summary:
|
|
12
|
-
en:
|
|
13
|
-
zh:
|
|
12
|
+
en: b80e34cdfb51009d
|
|
13
|
+
zh: eda44bcc181793d8
|
|
14
14
|
/deepseek-ai-dsh-llm-deepseek-account/table-of-contents:
|
|
15
15
|
en: 23386452a66b2fbd
|
|
16
16
|
zh: 4cf93c4bf17b3da2
|
|
17
17
|
/deepseek-ai-dsh-llm-deepseek-account/use-this-package:
|
|
18
|
-
en:
|
|
19
|
-
zh:
|
|
18
|
+
en: f0ac16310ce5a5c4
|
|
19
|
+
zh: 6929f80d830c18df
|
|
20
20
|
/deepseek-ai-dsh-llm-deepseek-account/understand-the-implementation:
|
|
21
|
-
en:
|
|
22
|
-
zh:
|
|
21
|
+
en: 8e8d8e4f786e03c3
|
|
22
|
+
zh: 7b32d8cea29d2b1a
|
|
23
23
|
/deepseek-ai-dsh-llm-deepseek-account/further-exploration:
|
|
24
|
-
en:
|
|
25
|
-
zh:
|
|
24
|
+
en: 30a69b9c66d89580
|
|
25
|
+
zh: fcc026a6373c1bee
|
|
26
26
|
/deepseek-ai-dsh-llm-deepseek-account/model-experience:
|
|
27
27
|
en: 215e7ba838619b7b
|
|
28
28
|
zh: a311da3843709f90
|
|
@@ -33,14 +33,14 @@
|
|
|
33
33
|
en: bd62360e6e6965ab
|
|
34
34
|
zh: 519626fc34672187
|
|
35
35
|
/deepseek-ai-dsh-llm-deepseek-account/model-experience/authenticated-model-requests/token-effect:
|
|
36
|
-
en:
|
|
37
|
-
zh:
|
|
36
|
+
en: b0f531baf29daf52
|
|
37
|
+
zh: c89837e35981c318
|
|
38
38
|
/deepseek-ai-dsh-llm-deepseek-account/model-experience/authenticated-model-requests/kv-cache-effect:
|
|
39
|
-
en:
|
|
40
|
-
zh:
|
|
39
|
+
en: db6aea22ebba25e1
|
|
40
|
+
zh: caf4817596204df4
|
|
41
41
|
/deepseek-ai-dsh-llm-deepseek-account/known-limitations-and-deferred-work:
|
|
42
|
-
en:
|
|
43
|
-
zh:
|
|
42
|
+
en: ee8b7ff23c806e18
|
|
43
|
+
zh: bd26832c8d455012
|
|
44
44
|
/deepseek-ai-dsh-llm-deepseek-account/known-limitations-and-deferred-work/dev-note:
|
|
45
45
|
en: 0525041e573e96b6
|
|
46
46
|
zh: b05ed8d852498e6b
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "DeepSeek account authentication and model
|
|
2
|
+
description: "DeepSeek account authentication, model listing, and model gating."
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
Register authentication and model
|
|
12
|
+
Register authentication, the account model listing, and model gating for `deepseek-account`. This plugin shares the [Messages transport](../llm-deepseek/README.md) and owns credential resolution, the account catalog, and model admission.
|
|
13
13
|
|
|
14
14
|
Authentication resolution returns `x-dsh-auth-token` and a failure callback capturing the same token. HTTP 401 classification and rejection stay in this provider; a rejected older request cannot clear a replacement login.
|
|
15
15
|
|
|
@@ -25,30 +25,50 @@ Authentication resolution returns `x-dsh-auth-token` and a failure callback capt
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## Use this package
|
|
27
27
|
|
|
28
|
-
Only `deepseekAccount.resolveToken(
|
|
28
|
+
Only `deepseekAccount.resolveToken(url)` supplies the token; the account service validates the destination. Signed-out or ineligible requests fail with `ACCOUNT_SIGN_IN_REQUIRED` and discovery returns an empty catalog. The route never falls back to an API key. Other credential lookup errors propagate.
|
|
29
|
+
|
|
30
|
+
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.
|
|
31
|
+
|
|
32
|
+
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 the console with the `[llm-deepseek-account]` tag; secrets are never logged. Each account token has its own listing, so the service can roll a model out to some accounts first.
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
Offered models take the listing's name, `context_window`, input modalities, and Messages system prompt mode over the bundled entry for the same id, which keeps its tool-update mode, image budgets, and description. `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`.
|
|
37
|
+
|
|
38
|
+
`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.
|
|
29
39
|
|
|
30
40
|
```yaml
|
|
31
41
|
- id: llm-deepseek-account
|
|
32
42
|
name: '@deepseek-ai/dsh-llm-deepseek-account'
|
|
33
43
|
config:
|
|
34
44
|
reasoningEffort: high
|
|
45
|
+
modelOverrides:
|
|
46
|
+
deepseek-flash:
|
|
47
|
+
contextWindow: 524288
|
|
48
|
+
maxTokens: 131072
|
|
35
49
|
```
|
|
36
50
|
|
|
51
|
+
| Field | Default | Meaning |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `modelsTimeoutMs` | `5,000` | Deadline of one listing request and of a first-access wait |
|
|
54
|
+
| `modelsMaxAgeMs` | `600,000` | Listing age after which the next access refreshes it; a failed load retries after the same age |
|
|
55
|
+
| `modelOverrides` | `{}` | Field overrides by model id |
|
|
56
|
+
|
|
37
57
|
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.
|
|
38
58
|
|
|
39
|
-
|
|
59
|
+
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.
|
|
40
60
|
|
|
41
61
|
<a id="understand-the-implementation"></a>
|
|
42
62
|
## Understand the implementation
|
|
43
63
|
|
|
44
|
-
Registrations and listeners dispose with the plugin. Shared Host wiring supplies attachments, request extensions, anonymous identity, and atomic retry-policy updates; this plugin registers only its own route.
|
|
64
|
+
Registrations and listeners dispose with the plugin, and disposal aborts an in-flight listing request. Shared Host wiring supplies attachments, request extensions, anonymous identity, and atomic retry-policy updates; this plugin registers only its own route and uses the transport's `connect` and `admit` hooks for the catalog and the gate. The listing cache lives in memory and is keyed by endpoint and a SHA-256 of the token.
|
|
45
65
|
|
|
46
66
|
A failed request the shared transport classifies as `QUOTA` is rewritten to `ACCOUNT_QUOTA` before it leaves this provider, covering an HTTP 402 response and an in-band SSE error alike. The shared transport and the `deepseek-official` route keep `QUOTA`, so the account route's top-up action never appears for an API-key or third-party failure.
|
|
47
67
|
|
|
48
68
|
<a id="further-exploration"></a>
|
|
49
69
|
## Further Exploration
|
|
50
70
|
|
|
51
|
-
[LLM streaming](../../../docs/subsystems/llm-streaming.md) · [Messages](../llm-deepseek/README.md)
|
|
71
|
+
[LLM streaming](../../../docs/subsystems/llm-streaming.md) · [Messages](../llm-deepseek/README.md) · [Account catalog decision](../../../.agents/notes/implemented/architecture/2026-09-24-deepseek-account-remote-model-catalog.md)
|
|
52
72
|
|
|
53
73
|
<a id="model-experience"></a>
|
|
54
74
|
## Model Experience
|
|
@@ -61,17 +81,22 @@ Requests on `deepseek-account` add no model-visible text from this plugin; the s
|
|
|
61
81
|
|
|
62
82
|
#### Token effect
|
|
63
83
|
|
|
64
|
-
Authentication and
|
|
84
|
+
Authentication and the listing add no input tokens. The resolved catalog sets each model's context window and default output cap, which each request's logged context and header record.
|
|
65
85
|
|
|
66
86
|
#### KV Cache effect
|
|
67
87
|
|
|
68
|
-
Credentials and catalog availability do not enter model input; the protocol transport owns request-prefix serialization.
|
|
88
|
+
Credentials and catalog availability do not enter model input; the protocol transport owns request-prefix serialization. A catalog change to a model's system prompt mode changes how later system prompt updates are serialized.
|
|
69
89
|
|
|
70
90
|
## Known Limitations and Deferred Work
|
|
71
91
|
|
|
72
92
|
<a id="known-limitations-and-deferred-work"></a>
|
|
73
93
|
|
|
74
|
-
- The
|
|
94
|
+
- The listing cache lives in memory; a start without network serves bundled capabilities until a listing loads.
|
|
95
|
+
- A listing change during a running process reaches the Web model picker at its next catalog refresh; the plugin does not announce it.
|
|
96
|
+
- The gate runs in this client; a modified client can still request any id unless the service rejects it.
|
|
97
|
+
- The listing's effort levels are ignored.
|
|
98
|
+
- When the current version can serve no listed model, the account offers no models, and model selectors show their generic empty state rather than an upgrade prompt.
|
|
99
|
+
- An override can set but not remove `systemPromptUpdate` and `toolUpdate` declarations.
|
|
75
100
|
|
|
76
101
|
<a id="dev-note"></a>
|
|
77
102
|
### Dev Note
|
package/README.zh.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "DeepSeek
|
|
2
|
+
description: "DeepSeek 账号鉴权、模型列表与模型门控。"
|
|
3
3
|
kind: "package-reference"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
为 `deepseek-account`
|
|
12
|
+
为 `deepseek-account` 注册鉴权、账号模型列表和模型门控。通过 [llm-deepseek](../llm-deepseek/README.zh.md) 共用 Messages 请求实现;本包负责凭据解析、账号目录和模型准入。
|
|
13
13
|
|
|
14
14
|
鉴权解析返回 `x-dsh-auth-token` 和捕获同一 token 的失败回调。HTTP 401 的分类和凭据失效处理由此提供方负责;被拒绝的旧请求不能清除替换后的登录。
|
|
15
15
|
|
|
@@ -25,30 +25,50 @@ kind: "package-reference"
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## 使用此包
|
|
27
27
|
|
|
28
|
-
账号 token 仅由 `deepseekAccount.resolveToken(
|
|
28
|
+
账号 token 仅由 `deepseekAccount.resolveToken(url)` 提供,目标地址必须通过账号服务的来源校验。未登录或目标不允许账号鉴权时,请求以 `ACCOUNT_SIGN_IN_REQUIRED` 失败,目录为空;不会回退到 API key。其他凭据查询错误继续向调用者报告。
|
|
29
|
+
|
|
30
|
+
路由的目录来自账号的 `GET /models` 列表,地址为 Messages `baseURL` 的 origin 加 `/models`(官方端点即 `https://api.deepseek.com/models`),不可配置。列表请求用 `x-dsh-auth-token` 携带账号 token,带上 harness 的 `User-Agent`,并拒绝重定向。
|
|
31
|
+
|
|
32
|
+
同一凭据的第一次访问会等待列表,最长 `modelsTimeoutMs`。之后的访问直接使用已加载的列表,列表超过 `modelsMaxAgeMs` 后在后台刷新。获取失败或得到空列表时保留上一份;还没有加载到任何列表时,路由提供当前版本内置的目录(`deepseek-flash` 与 `deepseek-v4-pro`)。列表请求返回 HTTP 401 不会清除已保存的登录。每次加载成功、连续失败中的第一次失败及其退回的目录、以及每个被隐藏的列表条目,都会以 `[llm-deepseek-account]` 标签写到控制台日志;不记录任何密钥。每个账号 token 有自己的列表,因此服务端可以先对部分账号开放某个模型。
|
|
33
|
+
|
|
34
|
+
只有当前版本的 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 照常透传。
|
|
35
|
+
|
|
36
|
+
提供的模型以列表中的名称、`context_window`、输入模态和 Messages system prompt 模式覆盖同 id 的内置条目,内置条目保留工具更新模式、图片预算和描述。`max_output_tokens` 限制请求默认值:请求没有指定上限时,发送 `maxTokens` 与该能力上限中较小的一个。列表中的推理档位不被读取;每个模型都提供 `off`、`low`、`high`、`max`。
|
|
37
|
+
|
|
38
|
+
`modelOverrides` 调整单个模型而不替换整个目录。键是模型 id;值的字段与 [Messages `models` 条目](../llm-deepseek/README.zh.md#minimal-configuration)相同,未设置的字段沿用下层的值。覆盖作用在列表和内置目录之上;不能新增模型,也不能让被拒绝的模型变得可用。列表声明的能力不约束覆盖:覆盖的 `maxTokens` 高于列表的 `max_output_tokens` 时按覆盖值发送,超出服务端限制的请求由服务端拒绝。覆盖指向的模型既不在列表里、内置目录也不认识,或者被列出的模型拒绝时,会成为模型页上该提供方的诊断信息;模型页不为此路由提供编辑器。被拒绝且内置目录不认识的模型,其覆盖要等当前版本能用该模型时才校验。此路由没有 `models` 字段;配置里仍写了它时,加载即失败并提示改用 `modelOverrides`。修改 `$DSH_HOME/profiles/<profile>/cordis.patch.yml`,下一次请求即生效。
|
|
29
39
|
|
|
30
40
|
```yaml
|
|
31
41
|
- id: llm-deepseek-account
|
|
32
42
|
name: '@deepseek-ai/dsh-llm-deepseek-account'
|
|
33
43
|
config:
|
|
34
44
|
reasoningEffort: high
|
|
45
|
+
modelOverrides:
|
|
46
|
+
deepseek-flash:
|
|
47
|
+
contextWindow: 524288
|
|
48
|
+
maxTokens: 131072
|
|
35
49
|
```
|
|
36
50
|
|
|
51
|
+
| 字段 | 默认值 | 含义 |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `modelsTimeoutMs` | `5,000` | 单次列表请求以及首次访问等待的上限 |
|
|
54
|
+
| `modelsMaxAgeMs` | `600,000` | 列表超过这个时长后,下一次访问时刷新;获取失败后同样在这个时长后重试 |
|
|
55
|
+
| `modelOverrides` | `{}` | 按模型 id 的字段覆盖 |
|
|
56
|
+
|
|
37
57
|
账号请求的 HTTP 401 与响应正文无关:映射为 `ACCOUNT_TOKEN_INVALID`,并要求账号服务仅在当前 token 仍与请求所用 token 相同时清除凭据。403 和其他错误不会触发清除。账号模块负责退登后打断任务,本插件不遍历 Agent。
|
|
38
58
|
|
|
39
|
-
|
|
59
|
+
设置命名空间采用 Cordis entry id,没有 entry 时采用插件名。产品保留 official 的 `llm-deepseek` entry id,账号使用 `llm-deepseek-account`。
|
|
40
60
|
|
|
41
61
|
<a id="understand-the-implementation"></a>
|
|
42
62
|
## 理解实现
|
|
43
63
|
|
|
44
|
-
|
|
64
|
+
注册和监听随插件生命周期释放,释放时会中止进行中的列表请求。共享 Host 绑定提供附件访问、请求扩展、匿名标识和重试策略的原子更新;本包只注册自己的路由,并通过传输层的 `connect` 与 `admit` 钩子接入目录和门控。列表缓存只在内存中,以端点和 token 的 SHA-256 为键。
|
|
45
65
|
|
|
46
66
|
共享传输判定为 `QUOTA` 的失败请求离开本提供方前会被改写为 `ACCOUNT_QUOTA`,HTTP 402 响应与流内 SSE 错误都适用。共享传输与 `deepseek-official` 路由保留 `QUOTA`,因此账号路由的充值操作不会出现在 API Key 或第三方失败上。
|
|
47
67
|
|
|
48
68
|
<a id="further-exploration"></a>
|
|
49
69
|
## 进一步探索
|
|
50
70
|
|
|
51
|
-
[LLM streaming](../../../docs/subsystems/llm-streaming.zh.md) · [Messages](../llm-deepseek/README.zh.md)
|
|
71
|
+
[LLM streaming](../../../docs/subsystems/llm-streaming.zh.md) · [Messages](../llm-deepseek/README.zh.md) · [账号目录决策](../../../.agents/notes/implemented/architecture/2026-09-24-deepseek-account-remote-model-catalog.zh.md)
|
|
52
72
|
|
|
53
73
|
<a id="model-experience"></a>
|
|
54
74
|
## 模型体验
|
|
@@ -61,17 +81,22 @@ kind: "package-reference"
|
|
|
61
81
|
|
|
62
82
|
#### Token effect
|
|
63
83
|
|
|
64
|
-
|
|
84
|
+
鉴权和列表不增加输入 token。解析后的目录决定每个模型的上下文窗口和默认输出上限,每次请求记录的上下文与请求头会记下这些值。
|
|
65
85
|
|
|
66
86
|
#### KV Cache effect
|
|
67
87
|
|
|
68
|
-
|
|
88
|
+
凭据与目录可用性不进入模型输入;协议传输拥有请求前缀的序列化。目录中某个模型的 system prompt 模式变化,会改变之后 system prompt 更新的序列化方式。
|
|
69
89
|
|
|
70
90
|
## 已知限制与后续工作
|
|
71
91
|
|
|
72
92
|
<a id="known-limitations-and-deferred-work"></a>
|
|
73
93
|
|
|
74
|
-
-
|
|
94
|
+
- 列表缓存只在内存中;没有网络时启动,在加载到列表之前使用内置能力。
|
|
95
|
+
- 进程运行中途的列表变化,要等 Web 模型选择器下一次刷新目录时才会显示;插件不会主动通知。
|
|
96
|
+
- 门控在客户端执行;改过的客户端仍可以请求任意 id,除非服务端拒绝。
|
|
97
|
+
- 列表中的推理档位被忽略。
|
|
98
|
+
- 当前版本能用的已列出模型一个都没有时,账号不提供任何模型,模型选择器显示通用的空状态,而不是升级提示。
|
|
99
|
+
- 覆盖可以设置、但不能移除 `systemPromptUpdate` 与 `toolUpdate` 声明。
|
|
75
100
|
|
|
76
101
|
<a id="dev-note"></a>
|
|
77
102
|
### 开发备注
|
package/lib/index.js
CHANGED
|
@@ -1,23 +1,457 @@
|
|
|
1
|
-
import { ACCOUNT_QUOTA_EXCEEDED_CODE, LlmError, QUOTA_EXCEEDED_CODE } from "@deepseek-ai/dsh-llm";
|
|
1
|
+
import { ACCOUNT_QUOTA_EXCEEDED_CODE, APP_IDENTITY, LlmError, QUOTA_EXCEEDED_CODE, attributionHeaders } from "@deepseek-ai/dsh-llm";
|
|
2
2
|
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
3
|
-
import {
|
|
3
|
+
import { assertNever } from "@deepseek-ai/dsh-util-values";
|
|
4
|
+
import { catalogModelFields, catalogModelInfo, deepSeekConfigFields, registerDeepSeekProvider, resolveAdapterOptions } from "@deepseek-ai/dsh-llm-deepseek";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { MAX_TIMER_DELAY_MS, deadline, timeoutOf } from "@deepseek-ai/dsh-timeout";
|
|
7
|
+
import z from "@deepseek-ai/schemastery";
|
|
8
|
+
import { isVolatile } from "@deepseek-ai/cosmokit";
|
|
9
|
+
import semver from "semver";
|
|
10
|
+
//#region lib/types/listing.js
|
|
11
|
+
/** DeepSeek `GET /models` listing: one authenticated request and per-entry decoding. */
|
|
12
|
+
const LISTED_MODALITIES = ["text", "image"];
|
|
13
|
+
/** Code of a listing deadline's timeout reason; {@link requestModelListing} reports it as `TIMEOUT`. */
|
|
14
|
+
const LISTING_TIMEOUT = "MODEL_LISTING_TIMEOUT";
|
|
15
|
+
function fieldsOf(value) {
|
|
16
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
17
|
+
}
|
|
18
|
+
/** Listed modalities this build sends, in listing order without repeats; `undefined` when the value is not an array. */
|
|
19
|
+
function knownModalities(value) {
|
|
20
|
+
if (!Array.isArray(value)) return void 0;
|
|
21
|
+
const listed = value;
|
|
22
|
+
return LISTED_MODALITIES.filter((modality) => listed.includes(modality)).toSorted((left, right) => listed.indexOf(left) - listed.indexOf(right));
|
|
23
|
+
}
|
|
24
|
+
/** A declared capacity: `undefined` when absent, `false` when not a positive safe integer. */
|
|
25
|
+
function capacity(value) {
|
|
26
|
+
if (value === void 0) return void 0;
|
|
27
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : false;
|
|
28
|
+
}
|
|
29
|
+
function requirementOf(entry) {
|
|
30
|
+
if (entry.client_requirements === void 0) return { kind: "none" };
|
|
31
|
+
const requirements = fieldsOf(entry.client_requirements);
|
|
32
|
+
if (requirements === void 0) return { kind: "invalid" };
|
|
33
|
+
const declared = requirements[APP_IDENTITY.product];
|
|
34
|
+
if (declared === void 0) return { kind: "none" };
|
|
35
|
+
const own = fieldsOf(declared);
|
|
36
|
+
if (own === void 0) return { kind: "invalid" };
|
|
37
|
+
if (own.min_version === void 0) return { kind: "none" };
|
|
38
|
+
return typeof own.min_version === "string" && own.min_version.length > 0 ? {
|
|
39
|
+
kind: "min-version",
|
|
40
|
+
version: own.min_version
|
|
41
|
+
} : { kind: "invalid" };
|
|
42
|
+
}
|
|
43
|
+
function capabilitiesOf(entry) {
|
|
44
|
+
const messages = fieldsOf(fieldsOf(entry.api_capabilities)?.anthropic_messages);
|
|
45
|
+
if (messages === void 0) return {
|
|
46
|
+
usable: false,
|
|
47
|
+
reason: "declares no api_capabilities.anthropic_messages"
|
|
48
|
+
};
|
|
49
|
+
if ((entry.output_modalities === void 0 ? ["text"] : knownModalities(entry.output_modalities))?.includes("text") !== true) return {
|
|
50
|
+
usable: false,
|
|
51
|
+
reason: "declares no text output"
|
|
52
|
+
};
|
|
53
|
+
const inputs = entry.input_modalities === void 0 ? ["text"] : knownModalities(entry.input_modalities);
|
|
54
|
+
if (inputs === void 0 || inputs.length === 0) return {
|
|
55
|
+
usable: false,
|
|
56
|
+
reason: "declares no input modality DeepSeek Harness supports"
|
|
57
|
+
};
|
|
58
|
+
const contextWindow = capacity(entry.context_window);
|
|
59
|
+
const maxOutputTokens = capacity(entry.max_output_tokens);
|
|
60
|
+
if (contextWindow === false || maxOutputTokens === false) return {
|
|
61
|
+
usable: false,
|
|
62
|
+
reason: "declares an invalid context_window or max_output_tokens"
|
|
63
|
+
};
|
|
64
|
+
const update = messages.system_prompt_update;
|
|
65
|
+
return {
|
|
66
|
+
usable: true,
|
|
67
|
+
facts: {
|
|
68
|
+
inputModalities: inputs,
|
|
69
|
+
...contextWindow === void 0 ? {} : { contextWindow },
|
|
70
|
+
...maxOutputTokens === void 0 ? {} : { maxOutputTokens },
|
|
71
|
+
...update === void 0 ? {} : { systemPromptUpdate: update === "in-history" ? "in-history" : "leading-only" }
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Decode one `/models` body entry by entry.
|
|
77
|
+
* @param body - parsed JSON response.
|
|
78
|
+
* @returns the entries with a usable id, in listing order.
|
|
79
|
+
* @throws LlmError `INVALID_RESPONSE` when the body has no `data` array or no usable entry.
|
|
80
|
+
*/
|
|
81
|
+
function parseModelListing(body) {
|
|
82
|
+
const data = fieldsOf(body)?.data;
|
|
83
|
+
if (!Array.isArray(data)) throw new LlmError("DeepSeek model listing has no \"data\" array", "INVALID_RESPONSE");
|
|
84
|
+
const rows = data;
|
|
85
|
+
const entries = [];
|
|
86
|
+
const seen = /* @__PURE__ */ new Set();
|
|
87
|
+
for (const row of rows) {
|
|
88
|
+
const entry = fieldsOf(row);
|
|
89
|
+
const id = entry?.id;
|
|
90
|
+
if (entry === void 0 || typeof id !== "string" || id.length === 0 || seen.has(id)) continue;
|
|
91
|
+
seen.add(id);
|
|
92
|
+
entries.push({
|
|
93
|
+
id,
|
|
94
|
+
...typeof entry.name === "string" && entry.name.length > 0 ? { name: entry.name } : {},
|
|
95
|
+
requirement: requirementOf(entry),
|
|
96
|
+
capabilities: capabilitiesOf(entry)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (entries.length === 0) throw new LlmError("DeepSeek model listing contains no model entry", "INVALID_RESPONSE");
|
|
100
|
+
return {
|
|
101
|
+
entries,
|
|
102
|
+
skipped: rows.length - entries.length
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Request and decode one model listing. Redirects fail, so credentials stay on the requested origin.
|
|
107
|
+
* @param url - listing endpoint.
|
|
108
|
+
* @param headers - authentication headers for this endpoint.
|
|
109
|
+
* @param signal - cancellation and deadline of the whole exchange.
|
|
110
|
+
* @returns the decoded listing.
|
|
111
|
+
* @throws LlmError `TIMEOUT` when a {@link LISTING_TIMEOUT} deadline elapses before a complete body, `ABORTED` for
|
|
112
|
+
* another abort, `TRANSPORT` for a network failure, `HTTP_<status>` for a non-2xx answer,
|
|
113
|
+
* and `INVALID_RESPONSE` for a body that is not a JSON listing.
|
|
114
|
+
*/
|
|
115
|
+
async function requestModelListing(url, headers, signal) {
|
|
116
|
+
let response;
|
|
117
|
+
let text;
|
|
118
|
+
try {
|
|
119
|
+
response = await fetch(url, {
|
|
120
|
+
method: "GET",
|
|
121
|
+
redirect: "error",
|
|
122
|
+
signal,
|
|
123
|
+
headers: {
|
|
124
|
+
...attributionHeaders(),
|
|
125
|
+
accept: "application/json",
|
|
126
|
+
...headers
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
text = await response.text();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
const expired = timeoutOf(signal, LISTING_TIMEOUT);
|
|
132
|
+
if (expired !== void 0) throw new LlmError(`DeepSeek model listing request to ${url} timed out after ${expired.timeoutMs} ms`, "TIMEOUT", { cause: error });
|
|
133
|
+
throw signal.aborted ? new LlmError(`DeepSeek model listing request to ${url} was aborted`, "ABORTED", { cause: error }) : new LlmError(`could not reach ${url}`, "TRANSPORT", { cause: error });
|
|
134
|
+
}
|
|
135
|
+
if (!response.ok) throw new LlmError(`${url} answered HTTP ${response.status}`, `HTTP_${response.status}`, { status: response.status });
|
|
136
|
+
let body;
|
|
137
|
+
try {
|
|
138
|
+
body = JSON.parse(text);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw new LlmError(`${url} did not answer with JSON`, "INVALID_RESPONSE", { cause: error });
|
|
141
|
+
}
|
|
142
|
+
return parseModelListing(body);
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region lib/types/catalog.js
|
|
146
|
+
/** Credential-scoped `/models` cache with single-flight loads, background refresh, and last-known-good retention. */
|
|
147
|
+
/** Wait for `task` unless `signal` aborts first; the task keeps running. */
|
|
148
|
+
async function abandonable(task, signal) {
|
|
149
|
+
if (signal === void 0) return task;
|
|
150
|
+
const abandoned = Promise.withResolvers();
|
|
151
|
+
const abandon = () => {
|
|
152
|
+
abandoned.reject(signal.reason);
|
|
153
|
+
};
|
|
154
|
+
signal.addEventListener("abort", abandon, { once: true });
|
|
155
|
+
try {
|
|
156
|
+
await Promise.race([task, abandoned.promise]);
|
|
157
|
+
} finally {
|
|
158
|
+
signal.removeEventListener("abort", abandon);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** One account route's listing cache; each endpoint and credential pair has its own scope. */
|
|
162
|
+
var AccountModelCatalog = class {
|
|
163
|
+
dependencies;
|
|
164
|
+
scope;
|
|
165
|
+
listing;
|
|
166
|
+
/** Settle time of the latest load in this scope; `undefined` before the first one settles. */
|
|
167
|
+
settledAt;
|
|
168
|
+
inflight;
|
|
169
|
+
request;
|
|
170
|
+
generation = 0;
|
|
171
|
+
failing = false;
|
|
172
|
+
closed = false;
|
|
173
|
+
/** @param dependencies - settings, credential, transport, clock, and diagnostic callbacks. */
|
|
174
|
+
constructor(dependencies) {
|
|
175
|
+
this.dependencies = dependencies;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The last published view, without I/O.
|
|
179
|
+
* @returns the signed-in state and listing of the current scope.
|
|
180
|
+
*/
|
|
181
|
+
current() {
|
|
182
|
+
return {
|
|
183
|
+
signedIn: this.scope !== void 0,
|
|
184
|
+
listing: this.listing
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Resolve the view for the current credential. The first access of a scope
|
|
189
|
+
* waits for its load, which the request deadline bounds; later accesses
|
|
190
|
+
* return at once and refresh a stale listing in the background.
|
|
191
|
+
* @param signal - abandons this wait only; the shared load continues.
|
|
192
|
+
* @returns the view after any first load of the scope settles.
|
|
193
|
+
*/
|
|
194
|
+
async view(signal) {
|
|
195
|
+
const settings = this.dependencies.settings();
|
|
196
|
+
const token = await this.dependencies.token(settings.url);
|
|
197
|
+
signal?.throwIfAborted();
|
|
198
|
+
if (this.closed) return this.current();
|
|
199
|
+
const scope = token === void 0 ? void 0 : `${settings.url}\n${createHash("sha256").update(token).digest("hex")}`;
|
|
200
|
+
if (scope !== this.scope) this.rescope(scope);
|
|
201
|
+
if (token !== void 0) {
|
|
202
|
+
if (this.settledAt === void 0) await abandonable(this.start(settings, token), signal);
|
|
203
|
+
else if (this.dependencies.now() - this.settledAt >= settings.maxAgeMs) this.start(settings, token);
|
|
204
|
+
}
|
|
205
|
+
return this.current();
|
|
206
|
+
}
|
|
207
|
+
/** Abort in-flight loads; later results and accesses change nothing. */
|
|
208
|
+
dispose() {
|
|
209
|
+
this.closed = true;
|
|
210
|
+
this.generation += 1;
|
|
211
|
+
this.request?.abort();
|
|
212
|
+
}
|
|
213
|
+
rescope(scope) {
|
|
214
|
+
this.request?.abort();
|
|
215
|
+
this.scope = scope;
|
|
216
|
+
this.listing = void 0;
|
|
217
|
+
this.settledAt = void 0;
|
|
218
|
+
this.inflight = void 0;
|
|
219
|
+
this.failing = false;
|
|
220
|
+
this.generation += 1;
|
|
221
|
+
}
|
|
222
|
+
start(settings, token) {
|
|
223
|
+
if (this.inflight !== void 0) return this.inflight;
|
|
224
|
+
this.generation += 1;
|
|
225
|
+
const generation = this.generation;
|
|
226
|
+
const request = new AbortController();
|
|
227
|
+
this.request = request;
|
|
228
|
+
const limit = deadline(request.signal, settings.timeoutMs, LISTING_TIMEOUT);
|
|
229
|
+
const run = this.dependencies.load(settings.url, token, limit.signal).then((listing) => {
|
|
230
|
+
if (generation !== this.generation) return;
|
|
231
|
+
this.listing = listing;
|
|
232
|
+
this.settledAt = this.dependencies.now();
|
|
233
|
+
this.failing = false;
|
|
234
|
+
this.dependencies.info(`model listing from ${settings.url} loaded ${listing.entries.length} model(s), skipped ${listing.skipped} row(s)`);
|
|
235
|
+
}, (error) => {
|
|
236
|
+
if (generation !== this.generation) return;
|
|
237
|
+
if (!this.failing) {
|
|
238
|
+
const fallback = this.listing === void 0 ? "using the bundled catalog" : "keeping the previous catalog";
|
|
239
|
+
this.dependencies.warn(`model listing from ${settings.url} failed (${String(error)}); ${fallback}`);
|
|
240
|
+
}
|
|
241
|
+
this.settledAt = this.dependencies.now();
|
|
242
|
+
this.failing = true;
|
|
243
|
+
}).finally(() => {
|
|
244
|
+
limit[Symbol.dispose]();
|
|
245
|
+
if (this.inflight === run) this.inflight = void 0;
|
|
246
|
+
});
|
|
247
|
+
this.inflight = run;
|
|
248
|
+
return run;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
//#endregion
|
|
4
252
|
//#region lib/types/config.js
|
|
5
|
-
/**
|
|
6
|
-
const
|
|
253
|
+
/** Default deadline of one model-listing request and of a first-use wait. */
|
|
254
|
+
const DEFAULT_MODELS_TIMEOUT_MS = 5e3;
|
|
255
|
+
/** Default listing age after which the next access refreshes it in the background. */
|
|
256
|
+
const DEFAULT_MODELS_MAX_AGE_MS = 6e5;
|
|
257
|
+
const { models: _protocolCatalog, ...protocolFields } = deepSeekConfigFields;
|
|
258
|
+
const modelOverride = z.object({
|
|
259
|
+
...catalogModelFields,
|
|
260
|
+
inputModalities: z.array(z.union(["text", "image"]))
|
|
261
|
+
});
|
|
262
|
+
const Config = z.object({
|
|
263
|
+
...protocolFields,
|
|
264
|
+
modelsTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_MODELS_TIMEOUT_MS).volatile(),
|
|
265
|
+
modelsMaxAgeMs: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MODELS_MAX_AGE_MS).volatile(),
|
|
266
|
+
modelOverrides: z.dict(modelOverride).default({}).volatile()
|
|
267
|
+
});
|
|
268
|
+
/**
|
|
269
|
+
* Read the current value behind every reference of a validated Config.
|
|
270
|
+
* @param config - parsed plugin Config.
|
|
271
|
+
* @returns plain options for {@link resolveAccountOptions}.
|
|
272
|
+
*/
|
|
273
|
+
function plainAccountOptions(config) {
|
|
274
|
+
return Object.fromEntries(Object.entries(config).map(([key, value]) => [key, isVolatile(value) ? value.get() : value]));
|
|
275
|
+
}
|
|
276
|
+
/** The listing endpoint: `/models` at the Messages origin, the only origin that receives the account token. */
|
|
277
|
+
function listingEndpoint(baseURL) {
|
|
278
|
+
return new URL("/models", new URL(baseURL).origin).href;
|
|
279
|
+
}
|
|
280
|
+
/** Keep an override's declared fields: parsed configuration carries unset fields as `undefined` and unset arrays as `[]`. */
|
|
281
|
+
function resolveOverride(id, override) {
|
|
282
|
+
if (id.length === 0) throw new Error("llm-deepseek-account: modelOverrides keys must be non-empty model ids");
|
|
283
|
+
if (Object.hasOwn(override, "id")) throw new Error(`llm-deepseek-account: modelOverrides.${id} sets "id", which is its key`);
|
|
284
|
+
const declared = {};
|
|
285
|
+
for (const [key, value] of Object.entries(override)) {
|
|
286
|
+
if (value === void 0 || Array.isArray(value) && value.length === 0) continue;
|
|
287
|
+
Reflect.set(declared, key, value);
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
resolveAdapterOptions({ models: [{
|
|
291
|
+
inputModalities: ["text", "image"],
|
|
292
|
+
...declared,
|
|
293
|
+
id
|
|
294
|
+
}] });
|
|
295
|
+
} catch (error) {
|
|
296
|
+
throw new Error(`llm-deepseek-account: modelOverrides.${id} is invalid: ${String(error)}`, { cause: error });
|
|
297
|
+
}
|
|
298
|
+
return declared;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* The one explicit resolve step from raw account settings to validated facts.
|
|
302
|
+
* Settings updates can reach it without schema validation, so every bound is judged here.
|
|
303
|
+
* @param options - plain account options.
|
|
304
|
+
* @param environment - this run's environment layers, or `undefined` outside the product CLI.
|
|
305
|
+
* @returns Messages settings, listing settings, and validated overrides.
|
|
306
|
+
*/
|
|
307
|
+
function resolveAccountOptions(options, environment) {
|
|
308
|
+
if (Object.hasOwn(options, "models")) throw new Error("llm-deepseek-account: models is no longer configurable; the account /models listing supplies the catalog. Remove models from this entry and adjust single models with modelOverrides.<model id>");
|
|
309
|
+
const { modelsTimeoutMs = DEFAULT_MODELS_TIMEOUT_MS, modelsMaxAgeMs = DEFAULT_MODELS_MAX_AGE_MS, modelOverrides = {}, ...protocolOptions } = options;
|
|
310
|
+
if (!Number.isFinite(modelsTimeoutMs) || modelsTimeoutMs <= 0 || modelsTimeoutMs > MAX_TIMER_DELAY_MS) throw new Error(`llm-deepseek-account: modelsTimeoutMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
311
|
+
if (!Number.isSafeInteger(modelsMaxAgeMs) || modelsMaxAgeMs < 0) throw new Error("llm-deepseek-account: modelsMaxAgeMs must be a non-negative safe integer");
|
|
312
|
+
const protocol = resolveAdapterOptions(protocolOptions, environment);
|
|
313
|
+
return {
|
|
314
|
+
protocol,
|
|
315
|
+
catalog: {
|
|
316
|
+
url: listingEndpoint(protocol.baseURL),
|
|
317
|
+
timeoutMs: modelsTimeoutMs,
|
|
318
|
+
maxAgeMs: modelsMaxAgeMs
|
|
319
|
+
},
|
|
320
|
+
modelOverrides: Object.fromEntries(Object.entries(modelOverrides).map(([id, override]) => [id, resolveOverride(id, override)]))
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region lib/types/resolve-catalog.js
|
|
325
|
+
/** The account catalog: version gating, this build's model knowledge, and deployment overrides. */
|
|
326
|
+
/** Replace fields of an entry; a text-only result drops the image limits it inherited. */
|
|
327
|
+
function layered(model, layer) {
|
|
328
|
+
const merged = {
|
|
329
|
+
...model,
|
|
330
|
+
...layer
|
|
331
|
+
};
|
|
332
|
+
if (merged.inputModalities?.includes("image") !== true) {
|
|
333
|
+
if (layer.imagePixelBudget === void 0) delete merged.imagePixelBudget;
|
|
334
|
+
if (layer.imageMaxBytes === void 0) delete merged.imageMaxBytes;
|
|
335
|
+
}
|
|
336
|
+
return merged;
|
|
337
|
+
}
|
|
338
|
+
/** Layer one usable listing entry over this build's entry for the same id. */
|
|
339
|
+
function listed(base, entry, facts, routeMaxTokens) {
|
|
340
|
+
const { systemPromptUpdate: _replaced, ...leadingOnly } = base;
|
|
341
|
+
return layered(facts.systemPromptUpdate === "leading-only" ? leadingOnly : base, {
|
|
342
|
+
...entry.name === void 0 ? {} : { name: entry.name },
|
|
343
|
+
...facts.contextWindow === void 0 ? {} : { contextWindow: facts.contextWindow },
|
|
344
|
+
...facts.maxOutputTokens === void 0 ? {} : { maxTokens: Math.min(routeMaxTokens, facts.maxOutputTokens) },
|
|
345
|
+
...facts.systemPromptUpdate === "in-history" ? { systemPromptUpdate: "in-history" } : {},
|
|
346
|
+
inputModalities: [...facts.inputModalities]
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
function gateOf(entry, known, clientVersion, problems) {
|
|
350
|
+
const requirement = entry.requirement;
|
|
351
|
+
switch (requirement.kind) {
|
|
352
|
+
case "none": return known ? void 0 : { kind: "unsupported" };
|
|
353
|
+
case "min-version":
|
|
354
|
+
if (semver.valid(requirement.version) === null) break;
|
|
355
|
+
return semver.gte(clientVersion, requirement.version) ? void 0 : {
|
|
356
|
+
kind: "upgrade-required",
|
|
357
|
+
requiredVersion: requirement.version
|
|
358
|
+
};
|
|
359
|
+
case "invalid": break;
|
|
360
|
+
/* v8 ignore next 2 -- listing decoding produces only the closed requirement union */
|
|
361
|
+
default: return assertNever(requirement, "model listing client requirement");
|
|
362
|
+
}
|
|
363
|
+
problems.push(`model "${entry.id}" declares an unreadable client requirement`);
|
|
364
|
+
return { kind: "unsupported" };
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Resolve the account catalog. A declared minimum client version decides a
|
|
368
|
+
* listed model's availability; without one, only models this build supports
|
|
369
|
+
* are offered and the other listed models stay hidden.
|
|
370
|
+
* @param input - listing, supported models, overrides, route cap, and this build's version.
|
|
371
|
+
* @returns offered models, request-resolution entries, refusals, and diagnostics.
|
|
372
|
+
*/
|
|
373
|
+
function resolveAccountCatalog(input) {
|
|
374
|
+
const supported = new Map(input.supported.map((model) => [model.id, model]));
|
|
375
|
+
const problems = [];
|
|
376
|
+
const overrideErrors = [];
|
|
377
|
+
const withOverride = (model) => {
|
|
378
|
+
const override = input.overrides[model.id];
|
|
379
|
+
if (override === void 0) return model;
|
|
380
|
+
const candidate = layered(model, override);
|
|
381
|
+
try {
|
|
382
|
+
resolveAdapterOptions({ models: [candidate] });
|
|
383
|
+
} catch (error) {
|
|
384
|
+
overrideErrors.push(`modelOverrides.${model.id} does not apply (${String(error)})`);
|
|
385
|
+
return model;
|
|
386
|
+
}
|
|
387
|
+
return candidate;
|
|
388
|
+
};
|
|
389
|
+
if (input.listing === void 0) {
|
|
390
|
+
const resolution = resolveAdapterOptions({ models: input.supported.map(withOverride) }).models;
|
|
391
|
+
return {
|
|
392
|
+
offered: resolution,
|
|
393
|
+
resolution,
|
|
394
|
+
refusals: /* @__PURE__ */ new Map(),
|
|
395
|
+
problems,
|
|
396
|
+
overrideErrors
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const offered = [];
|
|
400
|
+
const refusals = /* @__PURE__ */ new Map();
|
|
401
|
+
for (const entry of input.listing) {
|
|
402
|
+
const refusal = gateOf(entry, supported.has(entry.id), input.clientVersion, problems);
|
|
403
|
+
if (refusal !== void 0) {
|
|
404
|
+
refusals.set(entry.id, refusal);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (!entry.capabilities.usable) {
|
|
408
|
+
problems.push(`model "${entry.id}" ${entry.capabilities.reason}`);
|
|
409
|
+
refusals.set(entry.id, { kind: "unsupported" });
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
offered.push(withOverride(listed(supported.get(entry.id) ?? { id: entry.id }, entry, entry.capabilities.facts, input.routeMaxTokens)));
|
|
413
|
+
}
|
|
414
|
+
const offeredIds = new Set(offered.map((model) => model.id));
|
|
415
|
+
const listedIds = new Set(input.listing.map((entry) => entry.id));
|
|
416
|
+
for (const id of Object.keys(input.overrides)) if (!listedIds.has(id) && !supported.has(id)) overrideErrors.push(`modelOverrides names "${id}", which neither the account listing nor the built-in catalog describes`);
|
|
417
|
+
const resolution = resolveAdapterOptions({ models: [...offered, ...input.supported.filter((model) => !offeredIds.has(model.id)).map(withOverride)] }).models;
|
|
418
|
+
return {
|
|
419
|
+
offered: resolution.slice(0, offered.length),
|
|
420
|
+
resolution,
|
|
421
|
+
refusals,
|
|
422
|
+
problems,
|
|
423
|
+
overrideErrors
|
|
424
|
+
};
|
|
425
|
+
}
|
|
7
426
|
//#endregion
|
|
8
427
|
//#region lib/types/index.js
|
|
9
428
|
const name = "llm-deepseek-account";
|
|
10
429
|
const inject = ["llm"];
|
|
11
430
|
const PROVIDER = "deepseek-account";
|
|
431
|
+
/** Header carrying the account token on Messages, Files, and model-listing requests. */
|
|
432
|
+
const TOKEN_HEADER = "x-dsh-auth-token";
|
|
433
|
+
/** Console tag of listing diagnostics; product profiles mount no console exporter for `ctx.logger`. */
|
|
434
|
+
const LOG_TAG = "[llm-deepseek-account]";
|
|
435
|
+
function refuse(model, refusal) {
|
|
436
|
+
if (refusal === void 0) return;
|
|
437
|
+
switch (refusal.kind) {
|
|
438
|
+
case "upgrade-required": throw new LlmError(`DeepSeek model "${model}" requires DeepSeek Harness ${refusal.requiredVersion} or later; current version is ${APP_IDENTITY.version}`, "CLIENT_UPGRADE_REQUIRED");
|
|
439
|
+
case "unsupported": throw new LlmError(`DeepSeek model "${model}" is not supported by DeepSeek Harness; current version is ${APP_IDENTITY.version}`, "MODEL_UNSUPPORTED");
|
|
440
|
+
/* v8 ignore next 2 -- catalog resolution produces only the closed refusal union */
|
|
441
|
+
default: assertNever(refusal, "account catalog refusal");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
12
444
|
function apply(ctx, config) {
|
|
13
|
-
const
|
|
14
|
-
|
|
445
|
+
const environment = launchEnvironmentOf(ctx);
|
|
446
|
+
const settings = () => resolveAccountOptions(plainAccountOptions(config), environment);
|
|
447
|
+
settings();
|
|
448
|
+
const supported = resolveAdapterOptions({}).models;
|
|
449
|
+
const account = () => ctx.get("deepseekAccount");
|
|
15
450
|
const resolveAuth = async (connection) => {
|
|
16
|
-
const
|
|
17
|
-
const token = await account?.resolveToken(connection.baseURL);
|
|
451
|
+
const token = await account()?.resolveToken(connection.baseURL);
|
|
18
452
|
if (token === void 0) throw new LlmError("Sign in to DeepSeek to use the account provider. The request destination must allow account authentication.", "ACCOUNT_SIGN_IN_REQUIRED");
|
|
19
453
|
return {
|
|
20
|
-
headers: {
|
|
454
|
+
headers: { [TOKEN_HEADER]: token },
|
|
21
455
|
onRequestError: async (error) => {
|
|
22
456
|
if (!(error instanceof LlmError)) return error;
|
|
23
457
|
if (error.code === QUOTA_EXCEEDED_CODE) return new LlmError(error.message, ACCOUNT_QUOTA_EXCEEDED_CODE, {
|
|
@@ -30,33 +464,82 @@ function apply(ctx, config) {
|
|
|
30
464
|
cause: error
|
|
31
465
|
});
|
|
32
466
|
try {
|
|
33
|
-
await account?.rejectToken(token);
|
|
467
|
+
await account()?.rejectToken(token);
|
|
34
468
|
} catch (_credentialRemovalFailed) {}
|
|
35
469
|
return rejected;
|
|
36
470
|
}
|
|
37
471
|
};
|
|
38
472
|
};
|
|
39
|
-
|
|
473
|
+
const catalog = new AccountModelCatalog({
|
|
474
|
+
settings: () => settings().catalog,
|
|
475
|
+
token: async (url) => account()?.resolveToken(url),
|
|
476
|
+
load: (url, token, signal) => requestModelListing(url, { [TOKEN_HEADER]: token }, signal),
|
|
477
|
+
now: () => Date.now(),
|
|
478
|
+
info: (message) => {
|
|
479
|
+
console.info(LOG_TAG, message);
|
|
480
|
+
},
|
|
481
|
+
warn: (message) => {
|
|
482
|
+
console.warn(LOG_TAG, message);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
let loggedListing;
|
|
486
|
+
/** Resolve the catalog for one view, logging the listing's hidden entries once per listing. */
|
|
487
|
+
const connectionOf = (view) => {
|
|
488
|
+
const resolved = settings();
|
|
489
|
+
const result = resolveAccountCatalog({
|
|
490
|
+
listing: view.listing?.entries,
|
|
491
|
+
supported,
|
|
492
|
+
overrides: resolved.modelOverrides,
|
|
493
|
+
routeMaxTokens: resolved.protocol.maxTokens,
|
|
494
|
+
clientVersion: APP_IDENTITY.version
|
|
495
|
+
});
|
|
496
|
+
if (view.listing !== loggedListing) {
|
|
497
|
+
loggedListing = view.listing;
|
|
498
|
+
for (const problem of result.problems) console.warn(LOG_TAG, problem);
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
...resolved.protocol,
|
|
502
|
+
models: result.resolution,
|
|
503
|
+
catalog: result
|
|
504
|
+
};
|
|
505
|
+
};
|
|
506
|
+
const entry = {
|
|
40
507
|
provider: PROVIDER,
|
|
41
508
|
displayName: "DeepSeek Account",
|
|
42
509
|
settingsNs: ctx.fiber.entry?.options.id ?? "llm-deepseek-account",
|
|
43
510
|
settingsPath: []
|
|
44
|
-
}
|
|
511
|
+
};
|
|
512
|
+
const directory = ctx.llm.registerConfigurableProviders([entry]);
|
|
513
|
+
let reportedError;
|
|
514
|
+
/** Publish override diagnostics on the provider directory. */
|
|
515
|
+
const discovered = (view) => {
|
|
516
|
+
const connection = connectionOf(view);
|
|
517
|
+
const error = connection.catalog.overrideErrors.length === 0 ? void 0 : connection.catalog.overrideErrors.join("; ");
|
|
518
|
+
if (error !== reportedError) {
|
|
519
|
+
reportedError = error;
|
|
520
|
+
directory.replace([error === void 0 ? entry : {
|
|
521
|
+
...entry,
|
|
522
|
+
error
|
|
523
|
+
}]);
|
|
524
|
+
}
|
|
525
|
+
return connection;
|
|
526
|
+
};
|
|
45
527
|
registerDeepSeekProvider(ctx, PROVIDER, {
|
|
46
|
-
options,
|
|
47
|
-
resolveAuth,
|
|
48
528
|
providerName: "DeepSeek Account",
|
|
529
|
+
options: () => connectionOf(catalog.current()),
|
|
530
|
+
connect: async (signal) => connectionOf(await catalog.view(signal)),
|
|
531
|
+
resolveAuth,
|
|
532
|
+
admit: (connection, model) => {
|
|
533
|
+
refuse(model, connection.catalog.refusals.get(model));
|
|
534
|
+
},
|
|
49
535
|
discoverModels: async (provider) => {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
await resolveAuth(connection);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
if (error instanceof LlmError && error.code === "ACCOUNT_SIGN_IN_REQUIRED") return [];
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
57
|
-
return connection.models.map((model) => catalogModelInfo(provider, model));
|
|
536
|
+
const view = await catalog.view();
|
|
537
|
+
return view.signedIn ? discovered(view).catalog.offered.map((model) => catalogModelInfo(provider, model)) : [];
|
|
58
538
|
}
|
|
59
539
|
});
|
|
540
|
+
ctx.effect(() => () => {
|
|
541
|
+
catalog.dispose();
|
|
542
|
+
}, "llm-deepseek-account: model catalog");
|
|
60
543
|
}
|
|
61
544
|
//#endregion
|
|
62
545
|
export { Config, apply, inject, name };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CatalogSettings } from './config.ts';
|
|
2
|
+
import type { ModelListing } from './listing.ts';
|
|
3
|
+
/** Host-owned inputs of one catalog instance. */
|
|
4
|
+
export interface CatalogDependencies {
|
|
5
|
+
/** Current endpoint settings, re-read on every access. */
|
|
6
|
+
readonly settings: () => CatalogSettings;
|
|
7
|
+
/** The account token allowed for `url`, or `undefined` while signed out. */
|
|
8
|
+
readonly token: (url: string) => Promise<string | undefined>;
|
|
9
|
+
/** Perform one listing request authenticated with `token`; `signal` carries the {@link LISTING_TIMEOUT} request deadline. */
|
|
10
|
+
readonly load: (url: string, token: string, signal: AbortSignal) => Promise<ModelListing>;
|
|
11
|
+
/** Wall clock used for listing age. */
|
|
12
|
+
readonly now: () => number;
|
|
13
|
+
/** Report a loaded listing. */
|
|
14
|
+
readonly info: (message: string) => void;
|
|
15
|
+
/** Report the first failure of a failing streak. */
|
|
16
|
+
readonly warn: (message: string) => void;
|
|
17
|
+
}
|
|
18
|
+
/** What request resolution reads. */
|
|
19
|
+
export interface CatalogView {
|
|
20
|
+
/** Whether an account token is available for the listing endpoint. */
|
|
21
|
+
readonly signedIn: boolean;
|
|
22
|
+
/** Last successful listing for this credential and endpoint; `undefined` until one succeeds. */
|
|
23
|
+
readonly listing: ModelListing | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** One account route's listing cache; each endpoint and credential pair has its own scope. */
|
|
26
|
+
export declare class AccountModelCatalog {
|
|
27
|
+
private readonly dependencies;
|
|
28
|
+
private scope;
|
|
29
|
+
private listing;
|
|
30
|
+
/** Settle time of the latest load in this scope; `undefined` before the first one settles. */
|
|
31
|
+
private settledAt;
|
|
32
|
+
private inflight;
|
|
33
|
+
private request;
|
|
34
|
+
private generation;
|
|
35
|
+
private failing;
|
|
36
|
+
private closed;
|
|
37
|
+
/** @param dependencies - settings, credential, transport, clock, and diagnostic callbacks. */
|
|
38
|
+
constructor(dependencies: CatalogDependencies);
|
|
39
|
+
/**
|
|
40
|
+
* The last published view, without I/O.
|
|
41
|
+
* @returns the signed-in state and listing of the current scope.
|
|
42
|
+
*/
|
|
43
|
+
current(): CatalogView;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the view for the current credential. The first access of a scope
|
|
46
|
+
* waits for its load, which the request deadline bounds; later accesses
|
|
47
|
+
* return at once and refresh a stale listing in the background.
|
|
48
|
+
* @param signal - abandons this wait only; the shared load continues.
|
|
49
|
+
* @returns the view after any first load of the scope settles.
|
|
50
|
+
*/
|
|
51
|
+
view(signal?: AbortSignal): Promise<CatalogView>;
|
|
52
|
+
/** Abort in-flight loads; later results and accesses change nothing. */
|
|
53
|
+
dispose(): void;
|
|
54
|
+
private rescope;
|
|
55
|
+
private start;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=catalog.d.ts.map
|
package/lib/types/config.d.ts
CHANGED
|
@@ -1,44 +1,105 @@
|
|
|
1
|
-
/** Account
|
|
2
|
-
import {
|
|
1
|
+
/** Account route configuration: Messages settings, the model-listing endpoint, and per-model overrides. */
|
|
2
|
+
import type { Volatile } from '@deepseek-ai/cordis';
|
|
3
|
+
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
import type { LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment';
|
|
5
|
+
import type { Config as ProtocolConfig, DeepSeekCatalogModel, Options as ProtocolOptions, ResolvedDeepSeekOptions } from '@deepseek-ai/dsh-llm-deepseek';
|
|
6
|
+
/** Default deadline of one model-listing request and of a first-use wait. */
|
|
7
|
+
export declare const DEFAULT_MODELS_TIMEOUT_MS = 5000;
|
|
8
|
+
/** Default listing age after which the next access refreshes it in the background. */
|
|
9
|
+
export declare const DEFAULT_MODELS_MAX_AGE_MS = 600000;
|
|
10
|
+
/** Field overrides for one model; unset fields and empty lists inherit the underlying entry. */
|
|
11
|
+
export type ModelOverride = Omit<DeepSeekCatalogModel, 'id'>;
|
|
3
12
|
/** Account route configuration; authentication comes exclusively from the account service. */
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
export interface Config extends Omit<ProtocolConfig, 'models'> {
|
|
14
|
+
/** Deadline of one listing request, which also bounds a first-use wait (default five seconds). */
|
|
15
|
+
modelsTimeoutMs: Volatile<number>;
|
|
16
|
+
/**
|
|
17
|
+
* Listing age after which the next access refreshes it in the background;
|
|
18
|
+
* a failed load retries after the same age (default ten minutes).
|
|
19
|
+
*/
|
|
20
|
+
modelsMaxAgeMs: Volatile<number>;
|
|
21
|
+
/** Field overrides by model id, layered over the account listing and the built-in catalog. */
|
|
22
|
+
modelOverrides: Volatile<Record<string, ModelOverride>>;
|
|
23
|
+
}
|
|
24
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
25
|
+
modelsTimeoutMs: z<number, number, "volatile-defined">;
|
|
26
|
+
modelsMaxAgeMs: z<number, number, "volatile-defined">;
|
|
27
|
+
modelOverrides: z<NoInfer<import("@deepseek-ai/cosmokit").Dict<ModelOverride, string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<ModelOverride, string>>, "volatile-defined">;
|
|
28
|
+
baseURL: z<string, string, "volatile">;
|
|
29
|
+
thinking: z<"enabled" | "disabled", "enabled" | "disabled", "volatile">;
|
|
30
|
+
reasoningEffort: z<"low" | "off" | "high" | "max", "low" | "off" | "high" | "max", "volatile">;
|
|
31
|
+
maxTokens: z<number, number, "volatile-defined">;
|
|
32
|
+
defaultContextWindow: z<number, number, "volatile-defined">;
|
|
33
|
+
streamIdleTimeoutMs: z<number, number, "volatile-defined">;
|
|
34
|
+
maxRequestFilesBytes: z<number, number, "volatile-defined">;
|
|
35
|
+
maxInlineRequestImageBytes: z<number, number, "volatile-defined">;
|
|
36
|
+
maxImagesPerRequest: z<number, number, "volatile-defined">;
|
|
37
|
+
imageOffloadByteQuantum: z<number, number, "volatile-defined">;
|
|
38
|
+
inlineImageOffloadByteQuantum: z<number, number, "volatile-defined">;
|
|
39
|
+
imageOffloadCountQuantum: z<number, number, "volatile-defined">;
|
|
40
|
+
filesApiTimeoutMs: z<number, number, "volatile-defined">;
|
|
41
|
+
fileExpiresAfterSeconds: z<number, number, "volatile-defined">;
|
|
42
|
+
fileRefreshMarginSeconds: z<number, number, "volatile-defined">;
|
|
43
|
+
fileQuotaCleanupBatch: z<number, number, "volatile-defined">;
|
|
44
|
+
retryPolicy: z<NoInfer<import("@deepseek-ai/dsh-llm").RetryPolicyConfig>, NoInfer<import("@deepseek-ai/dsh-llm").RetryPolicyConfig>, "volatile">;
|
|
24
45
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
modelsTimeoutMs: z<number, number, "volatile-defined">;
|
|
47
|
+
modelsMaxAgeMs: z<number, number, "volatile-defined">;
|
|
48
|
+
modelOverrides: z<NoInfer<import("@deepseek-ai/cosmokit").Dict<ModelOverride, string>>, NoInfer<import("@deepseek-ai/cosmokit").Dict<ModelOverride, string>>, "volatile-defined">;
|
|
49
|
+
baseURL: z<string, string, "volatile">;
|
|
50
|
+
thinking: z<"enabled" | "disabled", "enabled" | "disabled", "volatile">;
|
|
51
|
+
reasoningEffort: z<"low" | "off" | "high" | "max", "low" | "off" | "high" | "max", "volatile">;
|
|
52
|
+
maxTokens: z<number, number, "volatile-defined">;
|
|
53
|
+
defaultContextWindow: z<number, number, "volatile-defined">;
|
|
54
|
+
streamIdleTimeoutMs: z<number, number, "volatile-defined">;
|
|
55
|
+
maxRequestFilesBytes: z<number, number, "volatile-defined">;
|
|
56
|
+
maxInlineRequestImageBytes: z<number, number, "volatile-defined">;
|
|
57
|
+
maxImagesPerRequest: z<number, number, "volatile-defined">;
|
|
58
|
+
imageOffloadByteQuantum: z<number, number, "volatile-defined">;
|
|
59
|
+
inlineImageOffloadByteQuantum: z<number, number, "volatile-defined">;
|
|
60
|
+
imageOffloadCountQuantum: z<number, number, "volatile-defined">;
|
|
61
|
+
filesApiTimeoutMs: z<number, number, "volatile-defined">;
|
|
62
|
+
fileExpiresAfterSeconds: z<number, number, "volatile-defined">;
|
|
63
|
+
fileRefreshMarginSeconds: z<number, number, "volatile-defined">;
|
|
64
|
+
fileQuotaCleanupBatch: z<number, number, "volatile-defined">;
|
|
65
|
+
retryPolicy: z<NoInfer<import("@deepseek-ai/dsh-llm").RetryPolicyConfig>, NoInfer<import("@deepseek-ai/dsh-llm").RetryPolicyConfig>, "volatile">;
|
|
43
66
|
}>>, "plain">;
|
|
67
|
+
/** Plain account options accepted by {@link resolveAccountOptions}. */
|
|
68
|
+
export type AccountOptions = Omit<ProtocolOptions, 'models'> & {
|
|
69
|
+
modelsTimeoutMs?: number;
|
|
70
|
+
modelsMaxAgeMs?: number;
|
|
71
|
+
modelOverrides?: Record<string, ModelOverride>;
|
|
72
|
+
};
|
|
73
|
+
/** Model-listing endpoint and cache timing of one configuration generation. */
|
|
74
|
+
export interface CatalogSettings {
|
|
75
|
+
/** Listing endpoint; it always shares the Messages origin. */
|
|
76
|
+
readonly url: string;
|
|
77
|
+
/** Deadline of one listing request. */
|
|
78
|
+
readonly timeoutMs: number;
|
|
79
|
+
/** Listing age after which the next access refreshes it. */
|
|
80
|
+
readonly maxAgeMs: number;
|
|
81
|
+
}
|
|
82
|
+
/** One validated configuration generation of the account route. */
|
|
83
|
+
export interface ResolvedAccountOptions {
|
|
84
|
+
/** Messages settings; request resolution replaces `models` with the resolved account catalog. */
|
|
85
|
+
readonly protocol: ResolvedDeepSeekOptions;
|
|
86
|
+
/** Listing endpoint and cache timing. */
|
|
87
|
+
readonly catalog: CatalogSettings;
|
|
88
|
+
/** Overrides by model id, holding only the fields each one declares. */
|
|
89
|
+
readonly modelOverrides: Readonly<Record<string, ModelOverride>>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Read the current value behind every reference of a validated Config.
|
|
93
|
+
* @param config - parsed plugin Config.
|
|
94
|
+
* @returns plain options for {@link resolveAccountOptions}.
|
|
95
|
+
*/
|
|
96
|
+
export declare function plainAccountOptions(config: Config): AccountOptions;
|
|
97
|
+
/**
|
|
98
|
+
* The one explicit resolve step from raw account settings to validated facts.
|
|
99
|
+
* Settings updates can reach it without schema validation, so every bound is judged here.
|
|
100
|
+
* @param options - plain account options.
|
|
101
|
+
* @param environment - this run's environment layers, or `undefined` outside the product CLI.
|
|
102
|
+
* @returns Messages settings, listing settings, and validated overrides.
|
|
103
|
+
*/
|
|
104
|
+
export declare function resolveAccountOptions(options: AccountOptions, environment?: LaunchEnvironmentSnapshot): ResolvedAccountOptions;
|
|
44
105
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ModelModality } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
/** Code of a listing deadline's timeout reason; {@link requestModelListing} reports it as `TIMEOUT`. */
|
|
3
|
+
export declare const LISTING_TIMEOUT = "MODEL_LISTING_TIMEOUT";
|
|
4
|
+
/** The requirement one listing entry declares under this client's product token. */
|
|
5
|
+
export type ClientRequirement = {
|
|
6
|
+
readonly kind: 'none';
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: 'min-version';
|
|
9
|
+
readonly version: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: 'invalid';
|
|
12
|
+
};
|
|
13
|
+
/** Capabilities a usable listing entry declares, in catalog vocabulary. */
|
|
14
|
+
export interface ListedFacts {
|
|
15
|
+
/** Accepted request modalities this build sends. */
|
|
16
|
+
readonly inputModalities: readonly ModelModality[];
|
|
17
|
+
/** Combined request and response context, when declared. */
|
|
18
|
+
readonly contextWindow?: number;
|
|
19
|
+
/** Output capability, when declared. */
|
|
20
|
+
readonly maxOutputTokens?: number;
|
|
21
|
+
/** Messages system prompt mode, when declared; any value other than `in-history` reads as `leading-only`. */
|
|
22
|
+
readonly systemPromptUpdate?: 'in-history' | 'leading-only';
|
|
23
|
+
}
|
|
24
|
+
/** Declared capabilities, or why the Messages transport cannot drive the model. */
|
|
25
|
+
export type ListedCapabilities = {
|
|
26
|
+
readonly usable: true;
|
|
27
|
+
readonly facts: ListedFacts;
|
|
28
|
+
} | {
|
|
29
|
+
readonly usable: false;
|
|
30
|
+
readonly reason: string;
|
|
31
|
+
};
|
|
32
|
+
/** One decoded listing entry; unusable capabilities never hide its id, name, or requirement. */
|
|
33
|
+
export interface ListingEntry {
|
|
34
|
+
/** Wire model id. */
|
|
35
|
+
readonly id: string;
|
|
36
|
+
/** Display name, when the entry declares a non-empty one. */
|
|
37
|
+
readonly name?: string;
|
|
38
|
+
/** Requirement declared under `client_requirements[APP_IDENTITY.product]`. */
|
|
39
|
+
readonly requirement: ClientRequirement;
|
|
40
|
+
/** Declared capabilities. */
|
|
41
|
+
readonly capabilities: ListedCapabilities;
|
|
42
|
+
}
|
|
43
|
+
/** A decoded listing with at least one entry. */
|
|
44
|
+
export interface ModelListing {
|
|
45
|
+
/** Entries in listing order. */
|
|
46
|
+
readonly entries: readonly ListingEntry[];
|
|
47
|
+
/** Rows without a usable id, and repeated ids. */
|
|
48
|
+
readonly skipped: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Decode one `/models` body entry by entry.
|
|
52
|
+
* @param body - parsed JSON response.
|
|
53
|
+
* @returns the entries with a usable id, in listing order.
|
|
54
|
+
* @throws LlmError `INVALID_RESPONSE` when the body has no `data` array or no usable entry.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseModelListing(body: unknown): ModelListing;
|
|
57
|
+
/**
|
|
58
|
+
* Request and decode one model listing. Redirects fail, so credentials stay on the requested origin.
|
|
59
|
+
* @param url - listing endpoint.
|
|
60
|
+
* @param headers - authentication headers for this endpoint.
|
|
61
|
+
* @param signal - cancellation and deadline of the whole exchange.
|
|
62
|
+
* @returns the decoded listing.
|
|
63
|
+
* @throws LlmError `TIMEOUT` when a {@link LISTING_TIMEOUT} deadline elapses before a complete body, `ABORTED` for
|
|
64
|
+
* another abort, `TRANSPORT` for a network failure, `HTTP_<status>` for a non-2xx answer,
|
|
65
|
+
* and `INVALID_RESPONSE` for a body that is not a JSON listing.
|
|
66
|
+
*/
|
|
67
|
+
export declare function requestModelListing(url: string, headers: Readonly<Record<string, string>>, signal: AbortSignal): Promise<ModelListing>;
|
|
68
|
+
//# sourceMappingURL=listing.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DeepSeekCatalogModel } from '@deepseek-ai/dsh-llm-deepseek';
|
|
2
|
+
import type { ModelOverride } from './config.ts';
|
|
3
|
+
import type { ListingEntry } from './listing.ts';
|
|
4
|
+
/** Why this build refuses one listed model before network I/O. */
|
|
5
|
+
export type CatalogRefusal = {
|
|
6
|
+
readonly kind: 'upgrade-required';
|
|
7
|
+
readonly requiredVersion: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: 'unsupported';
|
|
10
|
+
};
|
|
11
|
+
/** Inputs of one catalog resolution. */
|
|
12
|
+
export interface AccountCatalogInput {
|
|
13
|
+
/** Entries of the last successful listing, or `undefined` before one succeeds. */
|
|
14
|
+
readonly listing: readonly ListingEntry[] | undefined;
|
|
15
|
+
/** Validated models this build supports, carrying its client-side knowledge. */
|
|
16
|
+
readonly supported: readonly DeepSeekCatalogModel[];
|
|
17
|
+
/** Overrides by model id, holding only the fields each one declares. */
|
|
18
|
+
readonly overrides: Readonly<Record<string, ModelOverride>>;
|
|
19
|
+
/** Route output cap that a listed output capability bounds. */
|
|
20
|
+
readonly routeMaxTokens: number;
|
|
21
|
+
/** This build's semantic version. */
|
|
22
|
+
readonly clientVersion: string;
|
|
23
|
+
}
|
|
24
|
+
/** One resolved account catalog. */
|
|
25
|
+
export interface AccountCatalog {
|
|
26
|
+
/** Selectable models in listing order, or this build's models when no listing succeeded. */
|
|
27
|
+
readonly offered: readonly DeepSeekCatalogModel[];
|
|
28
|
+
/** Entries request resolution consults: offered models, then supported models the listing does not offer. */
|
|
29
|
+
readonly resolution: readonly DeepSeekCatalogModel[];
|
|
30
|
+
/** Listed models refused before network I/O, by id. */
|
|
31
|
+
readonly refusals: ReadonlyMap<string, CatalogRefusal>;
|
|
32
|
+
/** Listing entries hidden because their declarations are unreadable or unusable. */
|
|
33
|
+
readonly problems: readonly string[];
|
|
34
|
+
/** Configuration diagnostics for `modelOverrides`. */
|
|
35
|
+
readonly overrideErrors: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the account catalog. A declared minimum client version decides a
|
|
39
|
+
* listed model's availability; without one, only models this build supports
|
|
40
|
+
* are offered and the other listed models stay hidden.
|
|
41
|
+
* @param input - listing, supported models, overrides, route cap, and this build's version.
|
|
42
|
+
* @returns offered models, request-resolution entries, refusals, and diagnostics.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveAccountCatalog(input: AccountCatalogInput): AccountCatalog;
|
|
45
|
+
//# sourceMappingURL=resolve-catalog.d.ts.map
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-llm-deepseek-account",
|
|
3
3
|
"description": "DeepSeek account provider authentication and discovery",
|
|
4
|
-
"version": "0.2.1-alpha.
|
|
4
|
+
"version": "0.2.1-alpha.4",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -19,29 +19,40 @@
|
|
|
19
19
|
"default": "./lib/index.js"
|
|
20
20
|
},
|
|
21
21
|
"./src/*": "./src/*",
|
|
22
|
-
"./package.json": "./package.json"
|
|
22
|
+
"./package.json": "./package.json",
|
|
23
|
+
"./locale/*.json": "./locale/*.json"
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
26
|
+
"locale/*.json",
|
|
25
27
|
"lib/index.js",
|
|
26
28
|
"lib/types/**/*.d.ts"
|
|
27
29
|
],
|
|
28
30
|
"license": "MIT",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@deepseek-ai/cosmokit": "~1.8.6-alpha.2",
|
|
33
|
+
"@deepseek-ai/dsh-util-values": "0.2.1-alpha.4",
|
|
34
|
+
"@deepseek-ai/schemastery": "~3.18.5-alpha.2",
|
|
35
|
+
"semver": "^7.8.5"
|
|
36
|
+
},
|
|
29
37
|
"peerDependencies": {
|
|
30
|
-
"@deepseek-ai/cordis": "~4.0.5-alpha.
|
|
31
|
-
"@deepseek-ai/dsh-llm": "0.2.1-alpha.
|
|
32
|
-
"@deepseek-ai/dsh-llm-deepseek": "0.2.1-alpha.
|
|
33
|
-
"@deepseek-ai/dsh-launch-environment": "0.2.1-alpha.
|
|
34
|
-
"@deepseek-ai/dsh-deepseek-account": "0.2.1-alpha.
|
|
35
|
-
"@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.
|
|
38
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.2",
|
|
39
|
+
"@deepseek-ai/dsh-llm": "0.2.1-alpha.4",
|
|
40
|
+
"@deepseek-ai/dsh-llm-deepseek": "0.2.1-alpha.4",
|
|
41
|
+
"@deepseek-ai/dsh-launch-environment": "0.2.1-alpha.4",
|
|
42
|
+
"@deepseek-ai/dsh-deepseek-account": "0.2.1-alpha.4",
|
|
43
|
+
"@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.2",
|
|
44
|
+
"@deepseek-ai/dsh-timeout": "0.2.1-alpha.4"
|
|
36
45
|
},
|
|
37
46
|
"devDependencies": {
|
|
38
|
-
"@deepseek-ai/cordis": "~4.0.5-alpha.
|
|
39
|
-
"@deepseek-ai/dsh-llm": "0.2.1-alpha.
|
|
40
|
-
"@deepseek-ai/dsh-llm-deepseek": "0.2.1-alpha.
|
|
41
|
-
"@deepseek-ai/dsh-launch-environment": "0.2.1-alpha.
|
|
42
|
-
"@deepseek-ai/dsh-deepseek-account": "0.2.1-alpha.
|
|
43
|
-
"@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.
|
|
44
|
-
"@deepseek-ai/dsh-llm-deepseek-api-key": "0.2.1-alpha.
|
|
47
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.2",
|
|
48
|
+
"@deepseek-ai/dsh-llm": "0.2.1-alpha.4",
|
|
49
|
+
"@deepseek-ai/dsh-llm-deepseek": "0.2.1-alpha.4",
|
|
50
|
+
"@deepseek-ai/dsh-launch-environment": "0.2.1-alpha.4",
|
|
51
|
+
"@deepseek-ai/dsh-deepseek-account": "0.2.1-alpha.4",
|
|
52
|
+
"@deepseek-ai/cordis-plugin-loader": "~1.0.6-alpha.2",
|
|
53
|
+
"@deepseek-ai/dsh-llm-deepseek-api-key": "0.2.1-alpha.4",
|
|
54
|
+
"@deepseek-ai/dsh-timeout": "0.2.1-alpha.4",
|
|
55
|
+
"@types/semver": "^7.8.0"
|
|
45
56
|
},
|
|
46
57
|
"peerDependenciesMeta": {
|
|
47
58
|
"@deepseek-ai/dsh-deepseek-account": {
|