@siliconflow-official/dsh-llm-siliconflow 0.1.0-rc.5
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/LICENSE +22 -0
- package/README.md +168 -0
- package/cordis.patch.yml +10 -0
- package/lib/bin.js +219 -0
- package/lib/index.js +2 -0
- package/lib/invariant.js +23 -0
- package/lib/types/adapter.d.ts +114 -0
- package/lib/types/bin.d.ts +9 -0
- package/lib/types/discovery.d.ts +51 -0
- package/lib/types/index.d.ts +78 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/serialize.d.ts +30 -0
- package/lib/types/setup.d.ts +102 -0
- package/lib/types/sse.d.ts +25 -0
- package/lib/types/translate.d.ts +37 -0
- package/lib/types/types.d.ts +145 -0
- package/lib/types-Bim1q3qs.js +937 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
Copyright (c) 2026 SiliconFlow
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# @siliconflow-official/dsh-llm-siliconflow
|
|
2
|
+
|
|
3
|
+
面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) LLM 接缝的 SiliconFlow chat-completions 适配器插件:用直接 `fetch` + SSE(由 `eventsource-parser` 分帧)把 SiliconFlow 的 OpenAI 兼容线上格式翻译成 `StreamChunk` 协议。SiliconFlow 托管着广泛的开源模型目录,其中包含 delta 携带 `reasoning_content` 的推理模型(DeepSeek-R1、QwQ、Kimi-K2-Thinking)——适配器把该通道翻译成 harness reasoning 块,并在工具调用轮次按这些模型的要求将其回传。
|
|
4
|
+
|
|
5
|
+
本包拥有 `siliconflow` 提供方路由,因此部署只需提供一个 SiliconFlow API key 即可使用。其模型选择器从实时 `GET /models?sub_type=chat` 列表按端点顺序填充;配置的 `models` 列表是在没有 key 或发现失败时展示的回退目录。这是一个纯 OpenAI 兼容端点,没有 `thinking`/`reasoning_effort` 开关,因此适配器不暴露任何推理档位元数据、也不序列化任何推理档位字段:推理模型通过其目录 id 选择,请求上显式指定 `reasoningEffort` 会在网络 I/O 前以 `UNSUPPORTED_REASONING_EFFORT` 被拒绝。为 `siliconflow` 注册另一个适配器会抛出 `LlmError('DUPLICATE_ADAPTER')`。
|
|
6
|
+
|
|
7
|
+
包根导出 Cordis 插件契约与 `SiliconFlowAdapter`;线上序列化、SSE 解析、chunk 翻译与发现辅助函数不属于该根契约。
|
|
8
|
+
|
|
9
|
+
本项目由 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的 dsh agent 开发完成——实现、单元测试与覆盖率、工程化质量门禁、GitHub 仓库与 CI、文档,均在一个 dsh 会话内完成。
|
|
10
|
+
|
|
11
|
+
## 安装
|
|
12
|
+
|
|
13
|
+
一行命令装进任意 profile:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
dsh plugin --profile <name> add @siliconflow-official/dsh-llm-siliconflow
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
该命令由 `dsh plugin` 转发给 pnpm,把本包装进 profile 并把它声明的 bundle patch(`cordis.patch.yml`,自动挂载 `siliconflow` 路由)合并进 `dsh.profile.bundles`。`@deepseek-ai/dsh-*` 以 peerDependencies 声明,由 harness 安装闭包在运行时提供,无需重复打包。
|
|
20
|
+
|
|
21
|
+
装完后运行随包发布的配置向导,交互式地填 key、拉取实时模型列表并把它设为默认渠道:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
dsh-siliconflow-setup
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
向导依次:询问是否把 SiliconFlow 设为默认渠道 → 未找到 `SILICONFLOW_API_KEY` 时引导填写并写入 `$DSH_HOME/.credentials.yaml` → 用 key 做 live discovery 拉取 `/models?sub_type=chat` 列表(失败则回退到内置目录)→ 选择默认模型 → 写入 `$DSH_HOME/settings.yaml` 的 `agent-default-model`。它只读 `$DSH_HOME`(缺省 `~/.dsh`),不改动 harness 本体。
|
|
28
|
+
|
|
29
|
+
不想用向导时,装完填一个 key 即可使用:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
export SILICONFLOW_API_KEY=sk-... # 或写入 $DSH_HOME/.credentials.yaml
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
在包发布到 npm 之前,先用 `pnpm install && pnpm build` 构建出 `lib/`,再从本地路径安装:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
dsh plugin --profile <name> add /path/to/dsh-llm-siliconflow
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
本代码派生自 MIT 许可的 DeepSeek Harness `llm-deepseek` 适配器;见 [LICENSE](LICENSE)。
|
|
42
|
+
|
|
43
|
+
## 启动
|
|
44
|
+
|
|
45
|
+
插件随 profile 挂载,启动方式不影响其可用性——`npx`、本地或全局安装的 `dsh` 都读同一个 `$DSH_HOME`(缺省 `~/.dsh`)下的 profile。
|
|
46
|
+
|
|
47
|
+
### 用 npx 启动(无需本地安装 dsh)
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
npx @deepseek-ai/dsh plugin --profile web add @siliconflow-official/dsh-llm-siliconflow # 装到 web profile
|
|
51
|
+
npx @siliconflow-official/dsh-llm-siliconflow # 交互式 setup
|
|
52
|
+
npx @deepseek-ai/dsh web # 打开 http://127.0.0.1:3080
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`npx @siliconflow-official/dsh-llm-siliconflow` 运行本包唯一 bin(`dsh-siliconflow-setup`),与被装进哪个 profile 无关——setup 只读写 `$DSH_HOME` 下的 credentials 与 settings。
|
|
56
|
+
|
|
57
|
+
### 后台启动
|
|
58
|
+
|
|
59
|
+
后台只是进程脱离终端,不影响插件加载;唯一要求是 `dsh-siliconflow-setup` 是交互式向导,须先前台、在有 TTY 的环境跑一次。
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
# web UI 常驻后台(默认 http://127.0.0.1:3080;远程访问需把 host 配成 0.0.0.0)
|
|
63
|
+
nohup npx @deepseek-ai/dsh web > ~/.dsh/web.log 2>&1 &
|
|
64
|
+
|
|
65
|
+
# headless 后台跑一次性任务
|
|
66
|
+
nohup npx @deepseek-ai/dsh --profile headless "任务" > ~/.dsh/task.log 2>&1 &
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 配置
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
- id: llm-siliconflow
|
|
73
|
+
name: '@siliconflow-official/dsh-llm-siliconflow'
|
|
74
|
+
config:
|
|
75
|
+
apiKeyEnv: SILICONFLOW_API_KEY # default; resolved per request via ctx.credentials, then the environment
|
|
76
|
+
baseURL: https://api.siliconflow.cn/v1 # optional; $SILICONFLOW_BASE_URL then the public API when omitted
|
|
77
|
+
maxTokens: 8192 # optional positive per-request output cap; this is the default
|
|
78
|
+
streamIdleTimeoutMs: 300000 # optional; positive finite Node timer delay; five-minute default
|
|
79
|
+
retryPolicy: # optional; omission uses bounded normal defaults
|
|
80
|
+
mode: normal # normal | always
|
|
81
|
+
backoff:
|
|
82
|
+
initialDelayMs: 500
|
|
83
|
+
maxDelayMs: 10000
|
|
84
|
+
jitterRatio: 0.1
|
|
85
|
+
defaultContextWindow: 32768 # optional positive-integer fallback; this is the default
|
|
86
|
+
models: # optional; the fallback catalog shown when discovery cannot run
|
|
87
|
+
- id: zai-org/GLM-5.2
|
|
88
|
+
- id: deepseek-ai/DeepSeek-V4-Flash
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
插件把单个提供方路由 `siliconflow` 连同其已解析的 `retryPolicy` 一并注册。请求用 `provider: siliconflow` 选中它;其 `model` 原样作为线上 `model` 字符串透传,因此更换 SiliconFlow 模型不需要生命周期级重新注册。线上模型 id 是 SiliconFlow 的 `org/model` 写法(如 `deepseek-ai/DeepSeek-V4-Flash`),绝不是短别名。省略 `models` 时保留一份由六个当前托管对话模型组成的小回退目录;显式列表会替换这些默认值,而 `models: []` 则一个都不通告。目录条目通过 `ctx.llm.listModels('siliconflow')` 暴露给 ACP 编辑器与 Web 选择器这类客户端,但始终是建议性的:未列出的模型 id 依然原样透传。省略的条目名默认等于其 id。
|
|
92
|
+
|
|
93
|
+
`contextWindow` 按模型可选。`ctx.llm.resolveModelInfo('siliconflow', model).context` 先返回精确值——来自配置条目或温热的发现缓存——再对未被任何来源定容的模型回退到 `defaultContextWindow`。适配器默认值是 32,768;SiliconFlow 目录大致横跨 8k 到 1M 上下文(GLM-5.2、DeepSeek-V4-Pro/Flash 均支持 1M),因此披露了上下文的发现列表是权威值,回退值仅在没有任何来源披露时使用。对压力敏感的插件由此获得部署自有的容量,而不把模型选择器当作权威。
|
|
94
|
+
|
|
95
|
+
`maxTokens` 是对话请求的适配器级输出上限,默认 8,192。目录条目可携带自己的 `maxTokens`,对该模型优先生效;没有该字段的条目以及任何未列出的透传 id 解析为配置值。精确模型解析把胜出者暴露为 `defaultMaxTokens`;`LlmRuntime` 在 agent 循环写 `request/header` 之前把该值物化进 `GenerateOptions.maxTokens`,因此线上请求可重建。显式请求或 `AgentOptions.maxTokens` 值优先,并被序列化为 `max_tokens`。适配器不会把该请求预算对照 `contextWindow` 裁剪;上下文更小或有提供方输出限制的部署必须配置兼容的 `maxTokens`。
|
|
96
|
+
|
|
97
|
+
`streamIdleTimeoutMs` 约束每次未完成的提供方读取(包括首次 `fetch`),不计消费者在 chunk 之间花费的时间。SSE 注释会为未完成的读取重新计时作为传输活动,但永远不会成为 `StreamChunk` 值或会话日志事件。一次调用全程只有一个稳定的中止信号同时到达请求与响应体读取器;超时会停止传输并抛出 `LlmError('TIMEOUT')`,而更早的调用方中止抛出 `LlmError('ABORTED')`。适配器每次 `stream()` 调用只发一次提供方请求;它把配置的策略注册为提供方元数据,`dsh-llm-retry` 再在持久化的 agent 步骤边界单独执行它。
|
|
98
|
+
|
|
99
|
+
## 动态模型发现
|
|
100
|
+
|
|
101
|
+
`listModels` 通告实时对话列表而非手工维护的快照:它用已解析的 key 询问 `GET {baseURL}/models?sub_type=chat`,按**端点顺序**保留回复,并缓存五分钟。发现是建议性的、尽力而为的——缺少 key、端点不可达、凭据被拒、或回复不可读,都会回退到配置的 `models` 列表而非破坏选择器,因为空目录会整个隐藏该提供方。成功的列表同样服务于精确模型解析:静态目录未命名的模型仍能得到其列表披露的上下文窗口与输出上限。
|
|
102
|
+
|
|
103
|
+
配置面的「获取可用模型」动作通过 `ctx.llm.discoverModels('llm-siliconflow', …)` 使用同一询问:表单里输入的 key 优先,否则探测已存凭据或环境变量;没有 key 的路由以未认证方式探测,因此配置面仍能回答「这个端点服务哪些模型」。
|
|
104
|
+
|
|
105
|
+
## 动态配置(settings + credentials)
|
|
106
|
+
|
|
107
|
+
连接事实不在加载时冻结。`resolveAdapterOptions` 是从原始配置到已校验事实的唯一显式解析步骤,适配器通过 thunk **每个操作一次** 重读它们:base URL、目录、请求默认值与空闲预算都在下一个请求生效,而进行中的流保留其启动时的事实。两个可选接缝为该 thunk 供料:
|
|
108
|
+
|
|
109
|
+
- **`ctx.settings`** —— 插件用同样的 `Config` schema 注册 `llm-siliconflow` 命名空间,并把其 `cordis.yml` 条目作为组合 `base`,因此用户设置文档中的 `llm-siliconflow:` 段可以在不重启的情况下覆盖任意字段。未挂载 settings 服务时仅由条目配置驱动适配器,行为不变。一个通过 schema 但越过 schema 之外界限(如重复目录 id)的实时设置快照会保留最后的好事实并记录失败;条目配置本身仍会在插件加载时失败。
|
|
110
|
+
- **`ctx.credentials`** —— API key 每个流调用解析一次,且来自提供端点的 *同一个* 已解析快照。配置只携带 `apiKeyEnv`,绝不携带明文 key:该引用通过凭证接缝解析,未挂载该接缝时通过受信任的环境层解析。由于凭证事实随连接事实一起传递,解析器拒绝的设置快照既不贡献其端点也不贡献其 key。每个解析出的 key 在使用前都做格式检查,因此 HTTP 头无法承载的值会以 `LlmError('INVALID_CREDENTIAL')` 被拒绝,并指出失败的入口点——绝不包含 key 的任何部分。任何地方都没有 key 的请求以 `MISSING_CREDENTIAL` 失败,并指出每个配置入口点,而路由保持注册、目录保持可浏览。
|
|
111
|
+
|
|
112
|
+
唯一在注册时捕获的事实是重试策略:当其解析值变化时,插件原地重注册路由(同一适配器实例、一个同步区段),因此 `ctx.llm.providerRetryPolicy('siliconflow')` 始终报告当前策略。插件还会在可配置提供方目录(`ctx.llm.listConfigurableProviders()`)中声明其路由:提供方 `siliconflow`,settings 命名空间 `llm-siliconflow`,settings 路径为空。
|
|
113
|
+
|
|
114
|
+
## 应用归属
|
|
115
|
+
|
|
116
|
+
每个请求都携带 dsh-llm 的 `attributionHeaders()` 提供的共享归属头——标识 harness 的强制 `User-Agent` 基线。在凭证解析之后,每个提供方请求都携带 `x-siliconflow-harness-user-id`(来自 `@deepseek-ai/dsh-anonymous-user-id` 的稳定匿名 id);携带 `GenerateOptions.sessionId` 的请求还会把该精确值作为 `x-siliconflow-harness-session-id` 发送,而没有会话的直接调用省略该会话头。`GenerateOptions.purpose` 为 `compaction` 的请求额外携带 `x-siliconflow-harness-compact: 1`。这三个头都发往已解析的 `baseURL`,并保持在请求体与模型可见内容之外。
|
|
117
|
+
|
|
118
|
+
## 线上格式说明
|
|
119
|
+
|
|
120
|
+
- 仅流式(`stream_options.include_usage` 始终开启)。`usage` 可能附在结束 chunk 上,也可能作为尾随的仅 usage chunk 出现——翻译器把两者都推迟到 `[DONE]`,因此 `usage` 始终先于 `finish`,且 `finish` 之后无内容。
|
|
121
|
+
- 适配器从不发送 `thinking` 或 `reasoning_effort`;SiliconFlow 端点没有这些开关,推理模型通过 id 选择。
|
|
122
|
+
- 推理模型首个 chunk 携带 `reasoning_content: ""` —— 已处理(不会产生多余 reasoning 块)。
|
|
123
|
+
- **推理回传规则**:在携带工具调用的 assistant 轮次,`reasoning_content` 会被序列化回历史(托管的 DeepSeek-R1 类模型所要求);无工具调用的轮次则丢弃(反正被忽略——省 token)。
|
|
124
|
+
- 缓存记账:`cacheReadTokens` ← `prompt_cache_hit_tokens` / `prompt_tokens_details.cached_tokens`;SiliconFlow 不报告缓存写入指标。
|
|
125
|
+
|
|
126
|
+
## 错误
|
|
127
|
+
|
|
128
|
+
非 2xx 响应抛出带稳定码的 `LlmError`:`AUTH`(401/403)、`QUOTA`(提供方详情指明配额、余额或额度耗尽的响应)、`RATE_LIMIT`(其他 429)、`CONTEXT_WINDOW_EXCEEDED`(400 且其提供方 code、type 或 message 指明上下文溢出)、`INVALID_REQUEST`(其他 400)、`SERVER`(5xx)、其余 `HTTP_<status>`。其可序列化的 `failure` 保留 HTTP 状态,以及存在时的有效正 `Retry-After` 秒数/日期延迟和 `x-request-id`。响应前的传输失败(DNS、连接拒绝、TLS、代理)抛出 `TRANSPORT`,指明已配置端点并把原始拒绝链为 `cause`;调用方中止抛出 `ABORTED`,循环的取消信号保持权威。协议违规抛出 `STREAM_CLOSED`(无 `[DONE]`)或 `MALFORMED_RESPONSE`(坏 JSON payload)。未知的线上 `finish_reason`(如 `content_filter`、`insufficient_system_resource`)变为 `finish {kind: 'error', failure}` chunk;一个 `stop`(或缺省)结束但未打开任何内容块的已完成流变为 `finish {kind: 'error'}`,码为 `EMPTY_RESPONSE`(默认策略会重试)。
|
|
129
|
+
|
|
130
|
+
## Model Experience
|
|
131
|
+
|
|
132
|
+
### SiliconFlow 请求
|
|
133
|
+
|
|
134
|
+
#### 模型看到什么
|
|
135
|
+
|
|
136
|
+
选中的 SiliconFlow 模型收到 harness 系统提示、消息历史、工具 schema、停止序列与调用配置,不含适配器编写的提示文本。在先前携带工具调用的 assistant 轮次,其推理内容按要求回传;无工具调用轮次的推理被省略。
|
|
137
|
+
|
|
138
|
+
#### Token 影响
|
|
139
|
+
|
|
140
|
+
提供方分词决定精确输入。条件性推理回传增加工具往返上下文,而丢弃其他推理避免重复付费;缓存读取用量在可用时上报。
|
|
141
|
+
|
|
142
|
+
#### KV Cache 影响
|
|
143
|
+
|
|
144
|
+
未变化的组装前缀有资格被提供方缓存复用,本适配器在用量中上报。模型路由变更或任何上游提示、schema、前缀、历史变更都可能从第一个变化 token 起阻止复用;推理回传在工具往返期间追加。
|
|
145
|
+
|
|
146
|
+
### SiliconFlow 响应
|
|
147
|
+
|
|
148
|
+
#### 模型看到什么
|
|
149
|
+
|
|
150
|
+
推理、文本与原始字符串工具参数被翻译成 harness chunk,供循环记录与组装。
|
|
151
|
+
|
|
152
|
+
#### Token 影响
|
|
153
|
+
|
|
154
|
+
生成 token 遵循请求记录的 `maxTokens`;只有循环保留的块影响后续输入。
|
|
155
|
+
|
|
156
|
+
#### KV Cache 影响
|
|
157
|
+
|
|
158
|
+
循环保留的响应块追加到下一个请求并保留其更早的可复用前缀;被丢弃的块没有后续缓存影响。更换提供方或模型会选择不同的缓存域。
|
|
159
|
+
|
|
160
|
+
## Known Limitations and Deferred Work
|
|
161
|
+
|
|
162
|
+
- **回退 `models` 列表是手工维护的** —— 六个默认值是一份小快照,仅在发现无法运行时展示;实时列表才是权威目录。
|
|
163
|
+
- **发现不跨 baseURL 变化缓存** —— 缓存按端点键控,因此改指路由会在下一次 `listModels` 重新询问。
|
|
164
|
+
- **settings 的 `models` 列表整体替换组合列表** —— settings 层合并在字段粒度进行,数组是一个字段;按条目合并目录需要键控结构。
|
|
165
|
+
- **未映射 `tool_choice`** —— 不属于核心词汇表(MVP 裁剪,与 pi-ai 和 DeepSeek 双胞胎相同)。
|
|
166
|
+
- **请求使用原始 `fetch`,而非 `@cordisjs/plugin-http`** —— 没有共享代理/拦截配置;待有第二个直接 fetch 适配器需要时再采用(`TODO(http)`)。
|
|
167
|
+
- **序列化把 user 与 tool-result 内容扁平化为文本块** —— 插件添加的块类型被跳过,空工具输出以字面 `(no output)` 上线。
|
|
168
|
+
- **图片内容被拒绝** —— 这里的 chat-completions 线上路由仅文本;多模态 SiliconFlow 路由需要自己的内容序列化器。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# @siliconflow-official/dsh-llm-siliconflow bundle patch: mounts the SiliconFlow LLM
|
|
2
|
+
# adapter into a dsh profile. Install with:
|
|
3
|
+
# dsh plugin --profile <name> add @siliconflow-official/dsh-llm-siliconflow
|
|
4
|
+
# The plugin registers the `siliconflow` provider route; supply only
|
|
5
|
+
# SILICONFLOW_API_KEY (or the credential store) and select a siliconflow model.
|
|
6
|
+
- insert:
|
|
7
|
+
- id: llm-siliconflow
|
|
8
|
+
# Node-resolvable package name — kept in sync with package.json `name`.
|
|
9
|
+
# Quoted because `@` opens a reserved indicator in YAML.
|
|
10
|
+
name: '@siliconflow-official/dsh-llm-siliconflow'
|
package/lib/bin.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as PUBLIC_BASE_URL, h as discoverChatModels, i as PROVIDER, n as DEFAULT_API_KEY_ENV, r as DEFAULT_MODELS } from "./types-Bim1q3qs.js";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { stdin, stdout } from "node:process";
|
|
5
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
import { parseDocument } from "yaml";
|
|
9
|
+
//#region lib/types/setup.js
|
|
10
|
+
/**
|
|
11
|
+
* Interactive setup wizard core for {@link @siliconflow-official/dsh-llm-siliconflow}:
|
|
12
|
+
* the pure steps (harness-home and document-path resolution, credential and
|
|
13
|
+
* settings read/write, model-choice parsing) plus the orchestration over an
|
|
14
|
+
* injected I/O face, so the thin bin entry stays untested glue and the whole
|
|
15
|
+
* flow is unit-testable without a terminal.
|
|
16
|
+
*
|
|
17
|
+
* The wizard guides a fresh install to a working route: confirm the default
|
|
18
|
+
* channel, obtain an API key, interrogate the live chat-model listing, pick a
|
|
19
|
+
* default model, then persist `agent-default-model` into `settings.yaml`.
|
|
20
|
+
* @module @siliconflow-official/dsh-llm-siliconflow/setup
|
|
21
|
+
*/
|
|
22
|
+
/** Directory name for the default DeepSeek Harness home under the OS home. */
|
|
23
|
+
const DSH_HOME_DIR_NAME = ".dsh";
|
|
24
|
+
/** Environment variable that overrides the default DeepSeek Harness home. */
|
|
25
|
+
const DSH_HOME_ENV = "DSH_HOME";
|
|
26
|
+
/** Settings namespace the wizard writes the default model into. */
|
|
27
|
+
const DEFAULT_MODEL_NAMESPACE = "agent-default-model";
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the DeepSeek Harness home: non-empty `$DSH_HOME`, else `~/.dsh`.
|
|
30
|
+
* @param env - environment mapping; defaults to `process.env`.
|
|
31
|
+
* @returns the normalized absolute home path.
|
|
32
|
+
*/
|
|
33
|
+
function resolveHome(env = process.env) {
|
|
34
|
+
const fromEnv = env[DSH_HOME_ENV];
|
|
35
|
+
return resolve(fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : join(homedir(), DSH_HOME_DIR_NAME));
|
|
36
|
+
}
|
|
37
|
+
/** The managed credential document path under a harness home. */
|
|
38
|
+
function credentialsPath(home) {
|
|
39
|
+
return join(home, ".credentials.yaml");
|
|
40
|
+
}
|
|
41
|
+
/** The settings document path under a harness home. */
|
|
42
|
+
function settingsPath(home) {
|
|
43
|
+
return join(home, "settings.yaml");
|
|
44
|
+
}
|
|
45
|
+
function isEnoent(error) {
|
|
46
|
+
return error.code === "ENOENT";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read one credential reference from a comment-preserving document.
|
|
50
|
+
* @param path - the credentials document path.
|
|
51
|
+
* @param keyEnv - the top-level reference name (e.g. `SILICONFLOW_API_KEY`).
|
|
52
|
+
* @returns the stored value, or `undefined` when absent or the file is missing.
|
|
53
|
+
*/
|
|
54
|
+
async function readCredential(path, keyEnv) {
|
|
55
|
+
let text;
|
|
56
|
+
try {
|
|
57
|
+
text = await readFile(path, "utf8");
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (isEnoent(error)) return void 0;
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
const value = parseDocument(text).toJS()?.[keyEnv];
|
|
63
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Set one credential reference, preserving every other entry and comment.
|
|
67
|
+
* @param path - the credentials document path; created when absent.
|
|
68
|
+
* @param keyEnv - the top-level reference name to write.
|
|
69
|
+
* @param key - the value.
|
|
70
|
+
*/
|
|
71
|
+
async function writeCredential(path, keyEnv, key) {
|
|
72
|
+
const doc = await loadDocument(path);
|
|
73
|
+
doc.set(keyEnv, key);
|
|
74
|
+
await persistDocument(path, doc);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Replace the default-model section, preserving every other section and comment.
|
|
78
|
+
* The replacement carries only `provider` and `model`, so a previous
|
|
79
|
+
* `reasoningEffort` (unsupported by SiliconFlow) is dropped with the section.
|
|
80
|
+
* @param path - the settings document path; created when absent.
|
|
81
|
+
* @param selection - the provider/model pair to persist.
|
|
82
|
+
*/
|
|
83
|
+
async function writeDefaultModel(path, selection) {
|
|
84
|
+
const doc = await loadDocument(path);
|
|
85
|
+
doc.set(DEFAULT_MODEL_NAMESPACE, {
|
|
86
|
+
provider: selection.provider,
|
|
87
|
+
model: selection.model
|
|
88
|
+
});
|
|
89
|
+
await persistDocument(path, doc);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Parse a 1-based model-choice answer.
|
|
93
|
+
* @param raw - the user's answer.
|
|
94
|
+
* @param count - the number of offered models.
|
|
95
|
+
* @returns the 0-based index, or `undefined` when the answer is out of range.
|
|
96
|
+
*/
|
|
97
|
+
function parseModelIndex(raw, count) {
|
|
98
|
+
const value = Number(raw.trim());
|
|
99
|
+
if (!Number.isInteger(value) || value < 1 || value > count) return void 0;
|
|
100
|
+
return value - 1;
|
|
101
|
+
}
|
|
102
|
+
async function loadDocument(path) {
|
|
103
|
+
try {
|
|
104
|
+
return parseDocument(await readFile(path, "utf8"));
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (isEnoent(error)) return parseDocument("");
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function persistDocument(path, doc) {
|
|
111
|
+
await mkdir(dirname(path), {
|
|
112
|
+
recursive: true,
|
|
113
|
+
mode: 448
|
|
114
|
+
});
|
|
115
|
+
await writeFile(path, doc.toString(), { mode: 384 });
|
|
116
|
+
}
|
|
117
|
+
/** Render one model row as a display label. */
|
|
118
|
+
function labelOf(model) {
|
|
119
|
+
return model.name ?? model.id;
|
|
120
|
+
}
|
|
121
|
+
/** Convert a discovery listing to wizard rows, keeping endpoint order. */
|
|
122
|
+
function rowsOf(entries) {
|
|
123
|
+
return entries.map((entry) => ({
|
|
124
|
+
id: entry.id,
|
|
125
|
+
...entry.name === void 0 ? {} : { name: entry.name }
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Run the wizard: confirm the default channel, obtain a key, list models,
|
|
130
|
+
* pick the default, and persist the selection. Never throws for an unroutable
|
|
131
|
+
* endpoint — discovery falls back to the static catalog — but a malformed
|
|
132
|
+
* existing document or an unwritable one fails loud.
|
|
133
|
+
* @param deps - home, terminal face, and discovery function.
|
|
134
|
+
*/
|
|
135
|
+
async function runSetup(deps) {
|
|
136
|
+
const { home, io, discover } = deps;
|
|
137
|
+
const credentials = credentialsPath(home);
|
|
138
|
+
const settings = settingsPath(home);
|
|
139
|
+
if ((await io.question("是否将 SiliconFlow 设为默认渠道? [Y/n] ")).trim().toLowerCase() === "n") {
|
|
140
|
+
io.log("已跳过。之后可在 web 的 Models 页配置,或手动编辑 settings.yaml。");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
let key = await readCredential(credentials, DEFAULT_API_KEY_ENV);
|
|
144
|
+
if (key === void 0) {
|
|
145
|
+
const answer = (await io.question(`未找到 ${DEFAULT_API_KEY_ENV},请输入 API key(sk-...):`)).trim();
|
|
146
|
+
if (answer.length === 0) io.log("未提供 key,将以未认证方式探测模型列表(列表可能受限)。");
|
|
147
|
+
else {
|
|
148
|
+
key = answer;
|
|
149
|
+
await writeCredential(credentials, DEFAULT_API_KEY_ENV, key);
|
|
150
|
+
io.log(`已写入 ${credentials}`);
|
|
151
|
+
}
|
|
152
|
+
} else io.log(`已找到 ${DEFAULT_API_KEY_ENV}。`);
|
|
153
|
+
let models;
|
|
154
|
+
try {
|
|
155
|
+
const listed = await discover(key);
|
|
156
|
+
models = listed.length > 0 ? rowsOf(listed) : DEFAULT_MODELS;
|
|
157
|
+
if (listed.length > 0) io.log(`从端点获取到 ${String(listed.length)} 个模型。`);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
models = DEFAULT_MODELS;
|
|
160
|
+
io.log(`实时获取模型列表失败,回退到默认目录(${error instanceof Error ? error.message : String(error)})。`);
|
|
161
|
+
}
|
|
162
|
+
io.log("选择默认模型:");
|
|
163
|
+
models.forEach((model, index) => {
|
|
164
|
+
io.log(` ${String(index + 1)}) ${labelOf(model)}`);
|
|
165
|
+
});
|
|
166
|
+
let index = 0;
|
|
167
|
+
for (;;) {
|
|
168
|
+
const answer = (await io.question(`请选择 [1-${String(models.length)}](默认 1):`)).trim();
|
|
169
|
+
if (answer.length === 0) break;
|
|
170
|
+
const parsed = parseModelIndex(answer, models.length);
|
|
171
|
+
if (parsed === void 0) {
|
|
172
|
+
io.log(`请输入 1-${String(models.length)} 之间的编号。`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
index = parsed;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
const model = models[index];
|
|
179
|
+
/* v8 ignore next 3 -- models is non-empty here: discovery rows or the non-empty static catalog both supply entries */
|
|
180
|
+
if (model === void 0) throw new Error("setup: no model selected");
|
|
181
|
+
await writeDefaultModel(settings, {
|
|
182
|
+
provider: PROVIDER,
|
|
183
|
+
model: model.id
|
|
184
|
+
});
|
|
185
|
+
io.log(`已写入 ${settings}:agent-default-model = ${PROVIDER} / ${model.id}`);
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region lib/types/bin.js
|
|
189
|
+
/**
|
|
190
|
+
* Executable entry for the SiliconFlow setup wizard: wire the real terminal
|
|
191
|
+
* and the public endpoint's live listing into {@link runSetup}. Kept as thin
|
|
192
|
+
* glue so the wizard core is testable without a terminal.
|
|
193
|
+
* @module @siliconflow-official/dsh-llm-siliconflow/bin
|
|
194
|
+
*/
|
|
195
|
+
async function main() {
|
|
196
|
+
const io = createInterface({
|
|
197
|
+
input: stdin,
|
|
198
|
+
output: stdout,
|
|
199
|
+
terminal: true
|
|
200
|
+
});
|
|
201
|
+
try {
|
|
202
|
+
await runSetup({
|
|
203
|
+
home: resolveHome(),
|
|
204
|
+
io: {
|
|
205
|
+
question: (prompt) => io.question(prompt),
|
|
206
|
+
log: (message) => stdout.write(`${message}\n`)
|
|
207
|
+
},
|
|
208
|
+
discover: (apiKey) => discoverChatModels(PUBLIC_BASE_URL, apiKey)
|
|
209
|
+
});
|
|
210
|
+
} finally {
|
|
211
|
+
io.close();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
main().catch((error) => {
|
|
215
|
+
stdout.write(`setup failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
216
|
+
process.exitCode = 1;
|
|
217
|
+
});
|
|
218
|
+
//#endregion
|
|
219
|
+
export {};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as readListing, a as PUBLIC_BASE_URL, c as name, d as DEFAULT_MAX_TOKENS, f as DEFAULT_STREAM_IDLE_TIMEOUT_MS, g as listingUrl, h as discoverChatModels, i as PROVIDER, l as resolveAdapterOptions, m as SiliconFlowAdapter, n as DEFAULT_API_KEY_ENV, o as apply, p as DISCOVERY_TTL_MS, r as DEFAULT_MODELS, s as inject, t as Config, u as DEFAULT_CONTEXT_WINDOW } from "./types-Bim1q3qs.js";
|
|
2
|
+
export { Config, DEFAULT_API_KEY_ENV, DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_TOKENS, DEFAULT_MODELS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DISCOVERY_TTL_MS, PROVIDER, PUBLIC_BASE_URL, SiliconFlowAdapter, apply, discoverChatModels, inject, listingUrl, name, readListing, resolveAdapterOptions };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@siliconflow-official/dsh-llm-siliconflow`.
|
|
4
|
+
* @module @siliconflow-official/dsh-llm-siliconflow/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@siliconflow-official/dsh-llm-siliconflow";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "llm-siliconflow-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: this package exposes no independent event sequence or mutable data relation
|
|
13
|
+
* beyond contracts enforced at its owning seam.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => {};
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
//#endregion
|
|
23
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SiliconFlowAdapter`: fetch + SSE against a SiliconFlow (OpenAI-compatible)
|
|
3
|
+
* chat-completions endpoint, emitting harness StreamChunks. The adapter is
|
|
4
|
+
* transport-only: connection facts arrive through a thunk resolved once per
|
|
5
|
+
* operation and the bearer token through a per-request resolver, so the
|
|
6
|
+
* registering plugin owns validation, layering, and credential policy.
|
|
7
|
+
*
|
|
8
|
+
* @module dsh-llm-siliconflow/adapter
|
|
9
|
+
*/
|
|
10
|
+
import { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
11
|
+
import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, ResolvedRetryPolicy, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
12
|
+
import type { CredentialRef } from '@deepseek-ai/dsh-credentials';
|
|
13
|
+
import type { AnonymousUserId } from '@deepseek-ai/dsh-anonymous-user-id';
|
|
14
|
+
import type { WireError } from './types.ts';
|
|
15
|
+
/** One optional model entry advertised by the direct-fetch adapter. */
|
|
16
|
+
export interface SiliconFlowCatalogModel {
|
|
17
|
+
/** Wire model id accepted by the configured endpoint. */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Selector label; defaults to {@link id}. */
|
|
20
|
+
name?: string;
|
|
21
|
+
/** Optional selector detail for deployments with similar model variants. */
|
|
22
|
+
description?: string;
|
|
23
|
+
/** Known combined request/response context capacity; omitted when deployment metadata is unavailable. */
|
|
24
|
+
contextWindow?: number;
|
|
25
|
+
/** Per-request output cap for this model; omission falls back to the profile's {@link SiliconFlowConnectionOptions.maxTokens}. */
|
|
26
|
+
maxTokens?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validated connection facts for one operation. The plugin's
|
|
30
|
+
* `resolveAdapterOptions` is the one explicit resolve step producing this
|
|
31
|
+
* shape; the adapter trusts it and re-reads it per operation, which is what
|
|
32
|
+
* makes a configuration change reach the next request without re-registration.
|
|
33
|
+
*/
|
|
34
|
+
export interface SiliconFlowConnectionOptions {
|
|
35
|
+
/** Endpoint base; `/chat/completions` is appended. */
|
|
36
|
+
baseURL: string;
|
|
37
|
+
/**
|
|
38
|
+
* Credential reference of this same resolution, resolved per request.
|
|
39
|
+
* Travelling with the endpoint is the point: a request can never pair one
|
|
40
|
+
* generation's URL with another generation's secret. Configuration carries
|
|
41
|
+
* only this name — a literal key is not a configuration value.
|
|
42
|
+
*/
|
|
43
|
+
apiKeyEnv: CredentialRef;
|
|
44
|
+
/** Default per-request output cap; explicit request values win. */
|
|
45
|
+
maxTokens: number;
|
|
46
|
+
/** Positive context capacity used when the selected model has no exact value. */
|
|
47
|
+
defaultContextWindow: number;
|
|
48
|
+
/** Advisory models exposed to discovery consumers; requests remain unrestricted. */
|
|
49
|
+
models: readonly SiliconFlowCatalogModel[];
|
|
50
|
+
/** Maximum provider idle time while one stream read is outstanding. */
|
|
51
|
+
streamIdleTimeoutMs: number;
|
|
52
|
+
/** Provider-owned model-request retry policy, already resolved. */
|
|
53
|
+
retryPolicy: ResolvedRetryPolicy;
|
|
54
|
+
}
|
|
55
|
+
/** Constructor options for {@link SiliconFlowAdapter}: the operation-local resolution hooks the plugin owns. */
|
|
56
|
+
export interface SiliconFlowAdapterOptions {
|
|
57
|
+
/** Current validated connection facts; called once per operation. */
|
|
58
|
+
options: () => SiliconFlowConnectionOptions;
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the bearer token for the connection facts of one request. The
|
|
61
|
+
* snapshot is passed in — never re-read — so the key can only ever come
|
|
62
|
+
* from the same resolution as the endpoint it is sent to. Throws `LlmError`
|
|
63
|
+
* `MISSING_CREDENTIAL` when no key is available anywhere.
|
|
64
|
+
*/
|
|
65
|
+
resolveApiKey: (connection: SiliconFlowConnectionOptions) => Promise<string>;
|
|
66
|
+
/** Resolve the harness-home anonymous id shared with telemetry and feedback. */
|
|
67
|
+
resolveUserId: () => AnonymousUserId;
|
|
68
|
+
}
|
|
69
|
+
/** Default maximum idle interval while an adapter stream read is outstanding. */
|
|
70
|
+
export declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300000;
|
|
71
|
+
/** Default combined request/response context capacity. */
|
|
72
|
+
export declare const DEFAULT_CONTEXT_WINDOW = 32768;
|
|
73
|
+
/** Default per-request output-token cap. */
|
|
74
|
+
export declare const DEFAULT_MAX_TOKENS = 8192;
|
|
75
|
+
/** How long a cached model-listing discovery stays fresh before the next `listModels` re-interrogates. */
|
|
76
|
+
export declare const DISCOVERY_TTL_MS: number;
|
|
77
|
+
/**
|
|
78
|
+
* Map an HTTP status to a stable LlmError code.
|
|
79
|
+
* @param status - status of a non-2xx provider response.
|
|
80
|
+
* @param error - parsed provider error body, when available.
|
|
81
|
+
* @returns the normalized harness error code.
|
|
82
|
+
*/
|
|
83
|
+
export declare function httpErrorCode(status: number, error?: WireError['error']): string;
|
|
84
|
+
/**
|
|
85
|
+
* A direct-fetch `LlmAdapter` for SiliconFlow's OpenAI-compatible
|
|
86
|
+
* chat-completions endpoint. One instance serves every model name it was
|
|
87
|
+
* registered under (the harness model name IS the wire model name).
|
|
88
|
+
*
|
|
89
|
+
* One stable signal reaches both initial fetch and body reads. Caller aborts
|
|
90
|
+
* map to `ABORTED`; the configured per-read idle watchdog maps to `TIMEOUT`.
|
|
91
|
+
*/
|
|
92
|
+
export declare class SiliconFlowAdapter extends LlmAdapter {
|
|
93
|
+
private readonly config;
|
|
94
|
+
/** Cached listing result, keyed by the baseURL it was read from. */
|
|
95
|
+
private discovery;
|
|
96
|
+
constructor(config: SiliconFlowAdapterOptions);
|
|
97
|
+
providerInfo(provider: string): LlmProviderInfo;
|
|
98
|
+
providerRetryPolicy(_provider: string): ResolvedRetryPolicy;
|
|
99
|
+
/** The cached listing for `connection` when it is still fresh; never re-interrogates. */
|
|
100
|
+
private freshDiscovery;
|
|
101
|
+
/**
|
|
102
|
+
* The models this adapter currently advertises: the live chat listing when a
|
|
103
|
+
* key and a reachable endpoint can supply one, else the configured catalog.
|
|
104
|
+
* Discovery is advisory and best-effort — a missing key or any interrogation
|
|
105
|
+
* failure falls back to the configured `models` rather than breaking the
|
|
106
|
+
* picker, because an absent catalog would hide the provider entirely.
|
|
107
|
+
*/
|
|
108
|
+
private discover;
|
|
109
|
+
listModels(provider: string): Promise<readonly LlmModelInfo[]>;
|
|
110
|
+
resolveModel(provider: string, model: string, _signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
|
|
111
|
+
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
112
|
+
private request;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Executable entry for the SiliconFlow setup wizard: wire the real terminal
|
|
4
|
+
* and the public endpoint's live listing into {@link runSetup}. Kept as thin
|
|
5
|
+
* glue so the wizard core is testable without a terminal.
|
|
6
|
+
* @module @siliconflow-official/dsh-llm-siliconflow/bin
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=bin.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interrogate the SiliconFlow (OpenAI-compatible) `GET /models` listing for
|
|
3
|
+
* the chat models an endpoint serves, filtered with `sub_type=chat` and kept
|
|
4
|
+
* in the endpoint's own order — SiliconFlow's listing is already ordered by
|
|
5
|
+
* its own preference, so the adapter must not re-sort it.
|
|
6
|
+
*
|
|
7
|
+
* This module is transport-only: it takes the endpoint and bearer token for
|
|
8
|
+
* one interrogation and returns the entries it read. The registering plugin
|
|
9
|
+
* owns credential policy, and the adapter owns caching and the fallback to its
|
|
10
|
+
* configured catalog.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-llm-siliconflow/discovery
|
|
13
|
+
*/
|
|
14
|
+
/** One chat model the listing endpoint reports. Structural subset of the adapter's catalog entry. */
|
|
15
|
+
export interface SiliconFlowListingEntry {
|
|
16
|
+
/** Model id the endpoint accepts. */
|
|
17
|
+
id: string;
|
|
18
|
+
/** Human-readable name when the endpoint supplies one; selectors default to the id. */
|
|
19
|
+
name?: string;
|
|
20
|
+
/** Maximum combined request/response context in tokens, when disclosed. */
|
|
21
|
+
contextWindow?: number;
|
|
22
|
+
/** Maximum output tokens, when disclosed. */
|
|
23
|
+
maxTokens?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Join the endpoint base with the chat-filtered listing path. The base is a
|
|
27
|
+
* prefix, not a URL to resolve against, so a gateway path such as
|
|
28
|
+
* `https://gateway.example/openai/v1` keeps its segments.
|
|
29
|
+
* @param baseURL - the chat-completions base.
|
|
30
|
+
* @returns the `GET /models?sub_type=chat` URL.
|
|
31
|
+
*/
|
|
32
|
+
export declare function listingUrl(baseURL: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Map one listing reply into entries, preserving endpoint order. A row without
|
|
35
|
+
* a usable id is skipped rather than failing the whole interrogation: a single
|
|
36
|
+
* malformed row should not hide the rest of a working endpoint's catalog.
|
|
37
|
+
* @param body - the parsed reply body.
|
|
38
|
+
* @returns the entries in arrival order.
|
|
39
|
+
*/
|
|
40
|
+
export declare function readListing(body: unknown): SiliconFlowListingEntry[];
|
|
41
|
+
/**
|
|
42
|
+
* Interrogate one endpoint for the chat models it advertises.
|
|
43
|
+
* @param baseURL - the chat-completions base; `/models?sub_type=chat` is appended.
|
|
44
|
+
* @param apiKey - bearer token, or `undefined` to probe unauthenticated.
|
|
45
|
+
* @param signal - caller cancellation; the fetch and body read honor it.
|
|
46
|
+
* @returns the advertised models in endpoint order.
|
|
47
|
+
* @throws LlmError when the endpoint is unreachable, refuses the request, or
|
|
48
|
+
* the reply is not a readable listing.
|
|
49
|
+
*/
|
|
50
|
+
export declare function discoverChatModels(baseURL: string, apiKey: string | undefined, signal?: AbortSignal): Promise<readonly SiliconFlowListingEntry[]>;
|
|
51
|
+
//# sourceMappingURL=discovery.d.ts.map
|