@qilin-agent/web-deepseek-api 0.0.0-stage → 3.1.6
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 +21 -0
- package/README.i18n.yaml +49 -0
- package/README.md +105 -2
- package/README.zh.md +106 -0
- package/lib/index.js +372 -0
- package/lib/types/fetch.d.ts +28 -0
- package/lib/types/index.d.ts +51 -0
- package/lib/types/search.d.ts +47 -0
- package/lib/types/transport.d.ts +85 -0
- package/locale/en.json +5 -0
- package/locale/zh.json +6 -0
- package/package.json +52 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kkutysllb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/web/web-deepseek-api/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: eb52169ad8cf3b38
|
|
7
|
+
zh: 4b91a4dc90777892
|
|
8
|
+
/qilin-agent-web-deepseek-api:
|
|
9
|
+
en: 573be37817f28e89
|
|
10
|
+
zh: db508a438c0956c2
|
|
11
|
+
/qilin-agent-web-deepseek-api/summary:
|
|
12
|
+
en: 4b7dde72d306e8fb
|
|
13
|
+
zh: b87b4113682a0f09
|
|
14
|
+
/qilin-agent-web-deepseek-api/table-of-contents:
|
|
15
|
+
en: 76368b6b1da79c80
|
|
16
|
+
zh: 23ef7314e660bd7d
|
|
17
|
+
/qilin-agent-web-deepseek-api/use-this-package:
|
|
18
|
+
en: 1b958fb5099eeb23
|
|
19
|
+
zh: e3533327f06598f6
|
|
20
|
+
/qilin-agent-web-deepseek-api/use-this-package/authentication:
|
|
21
|
+
en: 57e660a47bc199e6
|
|
22
|
+
zh: 29f2cad58f71eef2
|
|
23
|
+
/qilin-agent-web-deepseek-api/use-this-package/results-and-failures:
|
|
24
|
+
en: c9c724975ab64fe9
|
|
25
|
+
zh: 13a82ad855885f44
|
|
26
|
+
/qilin-agent-web-deepseek-api/understand-the-implementation:
|
|
27
|
+
en: 61b1cc1b860121a1
|
|
28
|
+
zh: 5cf63ec1998d0234
|
|
29
|
+
/qilin-agent-web-deepseek-api/model-experience:
|
|
30
|
+
en: 215e7ba838619b7b
|
|
31
|
+
zh: a311da3843709f90
|
|
32
|
+
/qilin-agent-web-deepseek-api/model-experience/conversation-tool-results:
|
|
33
|
+
en: dfd3b61aff4a8aa8
|
|
34
|
+
zh: 06e2b6cc71f461e2
|
|
35
|
+
/qilin-agent-web-deepseek-api/model-experience/conversation-tool-results/what-the-model-sees:
|
|
36
|
+
en: a9fbb78ffe886f0b
|
|
37
|
+
zh: cb6840241bed1b40
|
|
38
|
+
/qilin-agent-web-deepseek-api/model-experience/conversation-tool-results/token-effect:
|
|
39
|
+
en: d82cd03f38fb4b38
|
|
40
|
+
zh: 8348604fae11cd82
|
|
41
|
+
/qilin-agent-web-deepseek-api/model-experience/conversation-tool-results/kv-cache-effect:
|
|
42
|
+
en: 54ed534b58469807
|
|
43
|
+
zh: fe809bb4f17e0e8e
|
|
44
|
+
/qilin-agent-web-deepseek-api/known-limitations-and-deferred-work:
|
|
45
|
+
en: 8aeb97e282d1dcab
|
|
46
|
+
zh: 900004bd43bae32d
|
|
47
|
+
/qilin-agent-web-deepseek-api/known-limitations-and-deferred-work/dev-note:
|
|
48
|
+
en: 0a1253c6f7b5e8b9
|
|
49
|
+
zh: 9f56528562c7c914
|
package/README.md
CHANGED
|
@@ -1,3 +1,106 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "DeepSeek Search and Fetch API providers, their shared authentication and transport, endpoint configuration, and normalized results."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @qilin-agent/web-deepseek-api
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Search and fetch the web through the DeepSeek Search and Fetch APIs: one direct request per operation, without an auxiliary model request. The plugin registers `deepseek-search-api` and `deepseek-fetch-api`, which share one credential and one API base, and authenticates with an API key resolved per operation. Search returns titles, URLs, available snippets, and available publication dates; Fetch returns page Markdown. Messages search stays in [qilin-web-search-deepseek](../web-search-deepseek/README.md); the model-facing tools live in [qilin-tool-web](../tool-web/README.md).
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Model Experience](#model-experience)
|
|
19
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
20
|
+
- [Dev Note](#dev-note)
|
|
21
|
+
|
|
22
|
+
-----
|
|
23
|
+
|
|
24
|
+
<a id="use-this-package"></a>
|
|
25
|
+
## Use this package
|
|
26
|
+
|
|
27
|
+
Mount the plugin alongside `qilin-web` and select its providers. Shipped profiles mount it while selecting Messages search and local HTTP fetch. Turning on **Use the new Web Search API** in **Plugins > Built-in plugin settings > Web search** and saving selects both providers of this package; turning it off restores the shipped selection. A saved selection applies to the next operation.
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
- name: '@qilin-agent/web'
|
|
31
|
+
config:
|
|
32
|
+
searchProvider: deepseek-search-api
|
|
33
|
+
fetchProvider: deepseek-fetch-api
|
|
34
|
+
- name: '@qilin-agent/web-deepseek-api'
|
|
35
|
+
config:
|
|
36
|
+
apiKeyEnv: DEEPSEEK_API_KEY
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| Field | Default | Meaning |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `apiKey` | omitted | Literal API key; prefer a credential reference |
|
|
42
|
+
| `apiKeyEnv` | `DEEPSEEK_API_KEY` | Credential reference resolved for each operation |
|
|
43
|
+
| `baseURL` | `https://api.deepseek.com/v1/tools` | Search and Fetch API base including its deployment path prefix; append `/search` or `/fetch` |
|
|
44
|
+
| `count` | `10` | Search API results per search, an integer from 1 to 50; a smaller `maxResults` on the request wins |
|
|
45
|
+
| `sendOriginalQuestion` | `true` | Send the triggering human prompt, cut to 128 characters, as the Search API's `ori_question` |
|
|
46
|
+
|
|
47
|
+
Fill `baseURL` with the complete API base, including its path prefix and excluding the operation name. The default reaches `https://api.deepseek.com/v1/tools/search` and `https://api.deepseek.com/v1/tools/fetch`; a deployment with another prefix, such as `/api/v1/tools`, needs its complete base.
|
|
48
|
+
|
|
49
|
+
Run the real API tests with `DEEPSEEK_API_KEY` and `DEEPSEEK_SEARCH_API_TEST_BASE_URL`; both are required, and the latter includes the API path prefix.
|
|
50
|
+
|
|
51
|
+
### Authentication
|
|
52
|
+
|
|
53
|
+
A non-empty literal `apiKey` wins over `apiKeyEnv`; the reference resolves through `ctx.credentials`, or the launch environment when that service is absent. Requests carry `Authorization: Bearer`. Credential resolution and endpoint selection share one settings snapshot. QiLin ships no account-token route, so every operation authenticates with this key.
|
|
54
|
+
|
|
55
|
+
### Results and failures
|
|
56
|
+
|
|
57
|
+
The Search API receives `query`, `count` (the configured count, or the request's smaller `maxResults`), `need_content: false`, and, unless `sendOriginalQuestion` is off, `ori_question`: the text of the initiating Session's latest human prompt with whitespace collapsed and cut to 128 characters. A search outside a Session or without such a prompt omits it. The provider maps `results[].snippet` directly and maps nullable `date` to optional `publishedAt`. An empty results array is successful. Fetch sends `url` and returns the original Markdown as a text body with HTTP status 200, because the API answers with an error status when the target yields no usable content. Its URL is the requested URL: the API reports no final redirected URL.
|
|
58
|
+
|
|
59
|
+
A blank or unparseable `baseURL` makes both providers unavailable, and a `count` outside 1 to 50 makes Search unavailable. Missing credentials fail with `WEB_PROVIDER_CREDENTIAL_MISSING`; cancellation uses `WEB_ABORTED`; transport, HTTP, and malformed response failures use `WEB_PROVIDER_ERROR`. Redirects are rejected before contacting their target. Non-success Fetch API statuses are retrieval failures, not target-page statuses. Dispatch failures identify the endpoint and the `baseURL` settings field. No automatic retry or provider fallback occurs.
|
|
60
|
+
|
|
61
|
+
-----
|
|
62
|
+
|
|
63
|
+
<a id="understand-the-implementation"></a>
|
|
64
|
+
## Understand the implementation
|
|
65
|
+
|
|
66
|
+
<details>
|
|
67
|
+
<summary>Implementation internals — click to expand</summary>
|
|
68
|
+
|
|
69
|
+
The [plugin](src/index.ts) reads its settings once per operation and registers both providers. The [Search provider](src/search.ts) validates response JSON before returning service results; the [Fetch provider](src/fetch.ts) validates the Markdown body. The [transport](src/transport.ts) owns credential resolution, cancellation, redirect rejection, and failure guidance. Search and Fetch API calls emit no auxiliary request event: the Search API needs no auxiliary model turn, so there is no request to record. Tool calls and results retain their normal Session logging.
|
|
70
|
+
|
|
71
|
+
See the [web subsystem](../../../docs/subsystems/web.md) for service types and [tool-web](../tool-web/README.md) for bounded model output.
|
|
72
|
+
|
|
73
|
+
</details>
|
|
74
|
+
|
|
75
|
+
-----
|
|
76
|
+
|
|
77
|
+
<a id="model-experience"></a>
|
|
78
|
+
## Model Experience
|
|
79
|
+
|
|
80
|
+
### Conversation tool results
|
|
81
|
+
|
|
82
|
+
#### What the model sees
|
|
83
|
+
|
|
84
|
+
The `web_search` and `web_fetch` tools render sources or Markdown with an external-content notice. Fetch results carry the requested URL and HTTP 200. Errors retain actionable credential or endpoint guidance. Search sends the query, with the triggering question, directly to the Search API.
|
|
85
|
+
|
|
86
|
+
#### Token effect
|
|
87
|
+
|
|
88
|
+
No auxiliary model tokens are consumed. Conversation result tokens scale with returned snippets or Markdown and are bounded by the consumer. API retrieval billing is independent of model tokens.
|
|
89
|
+
|
|
90
|
+
#### KV Cache effect
|
|
91
|
+
|
|
92
|
+
Tool results append after the existing conversation prefix. Provider registration adds no model context.
|
|
93
|
+
|
|
94
|
+
## Known Limitations and Deferred Work
|
|
95
|
+
|
|
96
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
97
|
+
|
|
98
|
+
- The shared search request does not expose `localization`; the provider requests snippets only and does not consume `summary`, `site_name`, `site_icon`, or full search content.
|
|
99
|
+
- The provider sends `ori_question` because the guide documents it as an intent signal; the adapter ignores any response term that would rank by it.
|
|
100
|
+
- API query and URL constraints are enforced by the remote service. Fetch exposes neither the target status nor the final redirect URL, so results report HTTP 200 and the requested URL; the provider discards the API's page title.
|
|
101
|
+
- Availability checks cannot query asynchronous credential stores. A configured resolver can still fail with a missing-credential error when called.
|
|
102
|
+
|
|
103
|
+
<a id="dev-note"></a>
|
|
104
|
+
### Dev Note
|
|
105
|
+
|
|
106
|
+
None.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "DeepSeek Search 与 Fetch API 提供方:共享的认证与传输、端点配置与归一化结果。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @qilin-agent/web-deepseek-api
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
通过 DeepSeek Search 与 Fetch API 搜索网页、抓取网页正文:每个操作一次直接请求,不需要辅助模型轮次。插件注册 `deepseek-search-api` 与 `deepseek-fetch-api`,两者共享同一凭据与同一 API 基础地址,并在每次操作解析 API Key 完成认证。搜索返回标题、URL、可用的摘要与可用的发布日期;Fetch 返回网页 Markdown。Messages 搜索仍由 [qilin-web-search-deepseek](../web-search-deepseek/README.zh.md) 提供;面向模型的工具位于 [qilin-tool-web](../tool-web/README.zh.md)。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [了解实现](#understand-the-implementation)
|
|
18
|
+
- [模型体验](#model-experience)
|
|
19
|
+
- [已知限制与延后工作](#known-limitations-and-deferred-work)
|
|
20
|
+
- [开发备注](#dev-note)
|
|
21
|
+
|
|
22
|
+
-----
|
|
23
|
+
|
|
24
|
+
<a id="use-this-package"></a>
|
|
25
|
+
## 使用本包
|
|
26
|
+
|
|
27
|
+
把插件与 `qilin-web` 一起挂载并选择它的提供方。随包部署挂载它,同时仍选择 Messages 搜索与本地 HTTP 抓取。在 **Plugins > Built-in plugin settings > Web search** 打开 **启用新版 Web 搜索 API** 并保存,会同时选中本包的两个提供方;关闭后恢复随包选择。保存的选择作用于下一次操作。
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
- name: '@qilin-agent/web'
|
|
31
|
+
config:
|
|
32
|
+
searchProvider: deepseek-search-api
|
|
33
|
+
fetchProvider: deepseek-fetch-api
|
|
34
|
+
- name: '@qilin-agent/web-deepseek-api'
|
|
35
|
+
config:
|
|
36
|
+
apiKeyEnv: DEEPSEEK_API_KEY
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| 字段 | 默认值 | 含义 |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `apiKey` | 省略 | 字面 API Key;优先使用凭据引用 |
|
|
42
|
+
| `apiKeyEnv` | `DEEPSEEK_API_KEY` | 每次操作解析的凭据引用 |
|
|
43
|
+
| `baseURL` | `https://api.deepseek.com/v1/tools` | Search 与 Fetch API 的基础地址(含部署的路径前缀);程序追加 `/search` 或 `/fetch` |
|
|
44
|
+
| `count` | `10` | 每次搜索的 Search API 结果数,1 到 50 的整数;请求上更小的 `maxResults` 优先 |
|
|
45
|
+
| `sendOriginalQuestion` | `true` | 把触发搜索的用户提示词截断到 128 个字符后作为 Search API 的 `ori_question` 发送 |
|
|
46
|
+
|
|
47
|
+
`baseURL` 必须填写完整 API 基础地址:包含路径前缀、不含操作名。默认值会请求 `https://api.deepseek.com/v1/tools/search` 与 `https://api.deepseek.com/v1/tools/fetch`;使用其他前缀(例如 `/api/v1/tools`)的部署需要填写自己的完整地址。
|
|
48
|
+
|
|
49
|
+
用 `DEEPSEEK_API_KEY` 与 `DEEPSEEK_SEARCH_API_TEST_BASE_URL` 运行真实 API 测试:两者都必须提供,后者包含 API 路径前缀。
|
|
50
|
+
|
|
51
|
+
### 认证
|
|
52
|
+
|
|
53
|
+
非空字面 `apiKey` 优先于 `apiKeyEnv`;凭据引用经 `ctx.credentials` 解析,该服务缺席时回落到启动环境。请求携带 `Authorization: Bearer`。凭据解析与端点选择共享同一份设置快照。QiLin 不提供账号令牌路径,因此每个操作都用该 Key 认证。
|
|
54
|
+
|
|
55
|
+
### 结果与失败
|
|
56
|
+
|
|
57
|
+
Search API 接收 `query`、`count`(配置的条数,或请求上更小的 `maxResults`)、`need_content: false`,以及 `ori_question`(`sendOriginalQuestion` 关闭时省略):发起 Session 最近一条人类提示词的文本,空白折叠后截断到 128 个字符。不在 Session 内或没有此类提示词时省略。提供方直接映射 `results[].snippet`,把可为 null 的 `date` 映射为可选 `publishedAt`。结果数组为空视为成功。Fetch 发送 `url`,把原始 Markdown 作为文本主体返回,状态码为 HTTP 200,因为目标没有可用内容时 API 会以错误状态应答。其 URL 就是请求的 URL:API 不报告最终重定向地址。
|
|
58
|
+
|
|
59
|
+
`baseURL` 为空或不可解析会使两个提供方不可用;`count` 超出 1 到 50 会使搜索不可用。凭据缺失以 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败;取消使用 `WEB_ABORTED`;传输、HTTP 与畸形响应失败使用 `WEB_PROVIDER_ERROR`。重定向会在接触目标之前被拒绝。Fetch API 的非成功状态是检索失败,不是目标页面的状态。请求发出后的失败会指出端点与 `baseURL` 设置字段。不进行自动重试,也不回落到其他提供方。
|
|
60
|
+
|
|
61
|
+
-----
|
|
62
|
+
|
|
63
|
+
<a id="understand-the-implementation"></a>
|
|
64
|
+
## 了解实现
|
|
65
|
+
|
|
66
|
+
<details>
|
|
67
|
+
<summary>实现细节——点击展开</summary>
|
|
68
|
+
|
|
69
|
+
[插件](src/index.ts)在每次操作读取一次设置并注册两个提供方。[Search 提供方](src/search.ts)在返回服务结果前校验响应 JSON;[Fetch 提供方](src/fetch.ts)校验 Markdown 主体。[传输层](src/transport.ts)负责凭据解析、取消、重定向拒绝与失败指引。Search 与 Fetch API 调用不产生辅助请求事件:Search API 不需要辅助模型轮次,因此没有需要记录的请求。工具调用与结果保持常规的 Session 记录。
|
|
70
|
+
|
|
71
|
+
服务类型见 [web 子系统](../../../docs/subsystems/web.zh.md),受限的模型输出见 [tool-web](../tool-web/README.zh.md)。
|
|
72
|
+
|
|
73
|
+
</details>
|
|
74
|
+
|
|
75
|
+
-----
|
|
76
|
+
|
|
77
|
+
<a id="model-experience"></a>
|
|
78
|
+
## 模型体验
|
|
79
|
+
|
|
80
|
+
### 会话工具结果
|
|
81
|
+
|
|
82
|
+
#### 模型看到的内容
|
|
83
|
+
|
|
84
|
+
`web_search` 与 `web_fetch` 工具会附带外部内容提示渲染来源或 Markdown。Fetch 结果携带请求的 URL 与 HTTP 200。错误保留可执行的凭据或端点指引。搜索把查询与触发问题直接发送给 Search API。
|
|
85
|
+
|
|
86
|
+
#### Token 影响
|
|
87
|
+
|
|
88
|
+
不消耗辅助模型 token。会话结果 token 随返回的摘要或 Markdown 规模变化,并由消费方设上限。API 检索计费独立于模型 token。
|
|
89
|
+
|
|
90
|
+
#### KV Cache 影响
|
|
91
|
+
|
|
92
|
+
工具结果追加在已有会话前缀之后。提供方注册不增加模型上下文。
|
|
93
|
+
|
|
94
|
+
## 已知限制与延后工作
|
|
95
|
+
|
|
96
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
97
|
+
|
|
98
|
+
- 共享的搜索请求不暴露 `localization`;提供方只请求摘要,不使用 `summary`、`site_name`、`site_icon` 或完整搜索内容。
|
|
99
|
+
- 提供方发送 `ori_question`,因为指南把它记录为意图信号;适配器不消费任何按它排序的响应项。
|
|
100
|
+
- API 的查询与 URL 约束由远端服务执行。Fetch 不暴露目标状态,也不暴露最终重定向 URL,因此结果报告 HTTP 200 与请求的 URL;提供方丢弃 API 的页面标题。
|
|
101
|
+
- 可用性检查无法查询异步凭据存储。已配置的解析器在调用时仍可能以凭据缺失错误失败。
|
|
102
|
+
|
|
103
|
+
<a id="dev-note"></a>
|
|
104
|
+
### 开发备注
|
|
105
|
+
|
|
106
|
+
无。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import z from "@qilin-agent/schemastery";
|
|
2
|
+
import { credentialRef } from "@qilin-agent/credentials";
|
|
3
|
+
import { launchEnvironmentOf } from "@qilin-agent/launch-environment";
|
|
4
|
+
import { WebError } from "@qilin-agent/web";
|
|
5
|
+
//#region lib/types/transport.js
|
|
6
|
+
const USER_AGENT = "deepseek-harness/0.0.1";
|
|
7
|
+
/**
|
|
8
|
+
* Resolve credential sources for one operation without retaining secret values.
|
|
9
|
+
* @param ctx - Provider context with request routing and credential services.
|
|
10
|
+
* @param config - Literal key and credential reference from the settings snapshot.
|
|
11
|
+
* @returns Authentication sources for one operation.
|
|
12
|
+
*/
|
|
13
|
+
function deepSeekWebAuth(ctx, config) {
|
|
14
|
+
const apiKeyEnv = credentialRef(config.apiKeyEnv);
|
|
15
|
+
const literalApiKey = config.apiKey !== void 0 && config.apiKey.length > 0 ? config.apiKey : void 0;
|
|
16
|
+
return {
|
|
17
|
+
...literalApiKey === void 0 ? {} : { apiKey: literalApiKey },
|
|
18
|
+
resolveApiKey: async () => {
|
|
19
|
+
const credentials = ctx.get("credentials");
|
|
20
|
+
if (credentials !== void 0) return (await credentials.resolve(apiKeyEnv))?.value;
|
|
21
|
+
const ambient = launchEnvironmentOf(ctx).get(apiKeyEnv);
|
|
22
|
+
return ambient !== void 0 && ambient.value.length > 0 ? ambient.value : void 0;
|
|
23
|
+
},
|
|
24
|
+
apiKeyEnv
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check whether an authentication source exists; does not resolve credentials.
|
|
29
|
+
* @param auth - Sources for the operation.
|
|
30
|
+
* @returns Whether a literal key or resolver is present.
|
|
31
|
+
*/
|
|
32
|
+
function hasDeepSeekWebCredential(auth) {
|
|
33
|
+
return (auth.apiKey?.length ?? 0) > 0 || auth.resolveApiKey !== void 0;
|
|
34
|
+
}
|
|
35
|
+
/** Narrow a remote JSON object.
|
|
36
|
+
* @param value - Untrusted JSON.
|
|
37
|
+
* @returns Whether readable object fields exist.
|
|
38
|
+
*/
|
|
39
|
+
function isRecord(value) {
|
|
40
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41
|
+
}
|
|
42
|
+
/** Report malformed upstream JSON without embedding response content.
|
|
43
|
+
* @returns The stable provider error.
|
|
44
|
+
*/
|
|
45
|
+
function invalidResponse() {
|
|
46
|
+
return new WebError("DeepSeek returned an invalid response body", "WEB_PROVIDER_ERROR");
|
|
47
|
+
}
|
|
48
|
+
/** Dispatch one operation using a single settings and credential snapshot.
|
|
49
|
+
* @param options - Authentication sources for this operation.
|
|
50
|
+
* @param request - Resolved endpoint, JSON payload, and response decoder.
|
|
51
|
+
* @param signal - Caller cancellation signal.
|
|
52
|
+
* @returns The validated provider result.
|
|
53
|
+
*/
|
|
54
|
+
async function dispatchDeepSeekWeb(options, request, signal) {
|
|
55
|
+
const { endpoint, body, decode, recordRequest, guidance } = request;
|
|
56
|
+
const headers = await authHeaders(options, request, signal);
|
|
57
|
+
throwIfSearchAborted(signal);
|
|
58
|
+
recordRequest?.(endpoint);
|
|
59
|
+
throwIfSearchAborted(signal);
|
|
60
|
+
let response;
|
|
61
|
+
try {
|
|
62
|
+
response = await fetch(endpoint, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
redirect: "error",
|
|
65
|
+
headers: {
|
|
66
|
+
...headers,
|
|
67
|
+
...request.headers,
|
|
68
|
+
"content-type": "application/json",
|
|
69
|
+
"accept": "application/json",
|
|
70
|
+
"user-agent": USER_AGENT
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify(body),
|
|
73
|
+
...signal !== void 0 ? { signal } : {}
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
77
|
+
throw searchEndpointError(guidance, endpoint, `DeepSeek web request failed: ${String(error)}`, error);
|
|
78
|
+
}
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
let message = `DeepSeek API error (HTTP ${response.status})`;
|
|
81
|
+
try {
|
|
82
|
+
const parsed = await response.json();
|
|
83
|
+
if (isRecord(parsed)) {
|
|
84
|
+
const detail = typeof parsed.error === "string" ? parsed.error : isRecord(parsed.error) ? parsed.error.message : parsed.message;
|
|
85
|
+
if (typeof detail === "string" && detail.length > 0) message += `: ${detail}`;
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
89
|
+
}
|
|
90
|
+
throw searchEndpointError(guidance, endpoint, message);
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const payload = await response.json();
|
|
94
|
+
throwIfSearchAborted(signal);
|
|
95
|
+
return decode(payload);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
98
|
+
throw searchEndpointError(guidance, endpoint, error instanceof WebError ? error.message : `DeepSeek returned an unprocessable response body: ${String(error)}`, error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Add endpoint recovery instructions to failures that occur after request dispatch begins.
|
|
103
|
+
* @param options - the plugin, settings field, and settings page the guidance names.
|
|
104
|
+
* @param endpoint - the dispatched endpoint.
|
|
105
|
+
* @param message - the failure description.
|
|
106
|
+
* @param cause - the underlying failure, when one exists.
|
|
107
|
+
* @returns the provider failure with recovery instructions.
|
|
108
|
+
*/
|
|
109
|
+
function searchEndpointError(options, endpoint, message, cause) {
|
|
110
|
+
return new WebError(`${message}\n\nThe web request used endpoint ${JSON.stringify(endpoint)}. ${options.subject} endpoint configuration is separate from chat. If that endpoint is not intended, guide the user to Plugins > Built-in plugin settings > ${options.page}, where they can change and save ${options.label}. If that settings page is unavailable, the user can set ${options.plugin}.${options.field} to a trusted DeepSeek endpoint. Only the user should choose or change the endpoint.`, "WEB_PROVIDER_ERROR", cause === void 0 ? void 0 : { cause });
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve one operation's authentication headers without retaining a credential on the provider.
|
|
114
|
+
* @param options - the caller's snapshot, so the credential and the endpoint it is sent to come from one section.
|
|
115
|
+
* @param request - the resolved request.
|
|
116
|
+
* @param signal - abort signal for the surrounding operation.
|
|
117
|
+
* @returns the API-key headers for this operation.
|
|
118
|
+
*/
|
|
119
|
+
async function authHeaders(options, request, signal) {
|
|
120
|
+
const apiKey = await resolveApiKey(options, request.guidance.plugin, signal);
|
|
121
|
+
return {
|
|
122
|
+
...request.apiKeyHeaders?.(apiKey),
|
|
123
|
+
"authorization": `Bearer ${apiKey}`
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve one operation's API key without retaining it on the provider.
|
|
128
|
+
* @param options - the caller's snapshot, so the key and the endpoint it is sent to come from one section.
|
|
129
|
+
* @param plugin - configuration namespace named by the missing-key diagnostic.
|
|
130
|
+
* @param signal - abort signal for the surrounding operation.
|
|
131
|
+
* @returns the resolved key.
|
|
132
|
+
*/
|
|
133
|
+
async function resolveApiKey(options, plugin, signal) {
|
|
134
|
+
throwIfSearchAborted(signal);
|
|
135
|
+
if (options.apiKey !== void 0 && options.apiKey.length > 0) return options.apiKey;
|
|
136
|
+
const { resolveApiKey } = options;
|
|
137
|
+
const resolved = resolveApiKey === void 0 ? void 0 : await resolveCredential(resolveApiKey, signal);
|
|
138
|
+
if (resolved !== void 0 && resolved.length > 0) return resolved;
|
|
139
|
+
throw new WebError(`DeepSeek web access has no API key for "${options.apiKeyEnv ?? "DEEPSEEK_API_KEY"}"; store it through the credentials service (the web Models page writes it), export it in the launching environment, or set a literal "apiKey" in the ${plugin} config`, "WEB_PROVIDER_CREDENTIAL_MISSING");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Run one credential resolver under the search's cancellation signal. An
|
|
143
|
+
* already-cancelled search never starts the resolver.
|
|
144
|
+
* @param resolve - the resolver; a synchronous throw is mapped like a rejection.
|
|
145
|
+
* @param signal - abort signal for the surrounding search.
|
|
146
|
+
* @returns the resolved credential, or undefined when the resolver supplied none.
|
|
147
|
+
* @throws {@link WebError} `WEB_ABORTED` on cancellation, `WEB_PROVIDER_ERROR` when the resolver fails.
|
|
148
|
+
*/
|
|
149
|
+
async function resolveCredential(resolve, signal) {
|
|
150
|
+
throwIfSearchAborted(signal);
|
|
151
|
+
try {
|
|
152
|
+
return await abortable(resolve(), signal);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (signal?.aborted === true || isAbortError(error)) throw searchAborted(signal, error);
|
|
155
|
+
throw new WebError(`DeepSeek web credential resolution failed: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Race a same-process asynchronous preflight against caller cancellation. The
|
|
160
|
+
* attached settlement handlers keep observing an uncooperative operation after
|
|
161
|
+
* abort so a later rejection cannot become unhandled.
|
|
162
|
+
*/
|
|
163
|
+
function abortable(operation, signal) {
|
|
164
|
+
if (signal === void 0) return operation;
|
|
165
|
+
if (signal.aborted) return Promise.reject(searchAborted(signal));
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
const onAbort = () => {
|
|
168
|
+
reject(searchAborted(signal));
|
|
169
|
+
};
|
|
170
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
171
|
+
operation.then((value) => {
|
|
172
|
+
signal.removeEventListener("abort", onAbort);
|
|
173
|
+
resolve(value);
|
|
174
|
+
}, (error) => {
|
|
175
|
+
signal.removeEventListener("abort", onAbort);
|
|
176
|
+
reject(new Error(String(error).replace(/^Error: /u, ""), { cause: error }));
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/** Throw the provider's stable cancellation error when the caller already aborted. */
|
|
181
|
+
function throwIfSearchAborted(signal) {
|
|
182
|
+
if (signal?.aborted === true) throw searchAborted(signal);
|
|
183
|
+
}
|
|
184
|
+
/** Build the provider's stable cancellation error while retaining the caller's reason. */
|
|
185
|
+
function searchAborted(signal, fallback) {
|
|
186
|
+
return new WebError("DeepSeek web request aborted", "WEB_ABORTED", { cause: signal?.aborted === true ? signal.reason : fallback });
|
|
187
|
+
}
|
|
188
|
+
/** True for a fetch/`AbortSignal` abort, surfaced as `WEB_ABORTED`. */
|
|
189
|
+
function isAbortError(error) {
|
|
190
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region lib/types/search.js
|
|
194
|
+
/** Stable id the Search API provider registers under in the search registry. */
|
|
195
|
+
const DEEPSEEK_SEARCH_API_PROVIDER_ID = "deepseek-search-api";
|
|
196
|
+
/** Default Search and Fetch API base, including its `/v1/tools` prefix; `/search` and `/fetch` are appended. */
|
|
197
|
+
const DEEPSEEK_WEB_API_BASE_URL = "https://api.deepseek.com/v1/tools";
|
|
198
|
+
/** Default Search API result count, the service's own default. */
|
|
199
|
+
const DEEPSEEK_DEFAULT_COUNT = 10;
|
|
200
|
+
/** Longest `ori_question` the Search API accepts, in characters. */
|
|
201
|
+
const ORIGINAL_QUESTION_MAX_CHARS = 128;
|
|
202
|
+
/** Settings named by endpoint failures of either API in this package. */
|
|
203
|
+
const DEEPSEEK_WEB_API_GUIDANCE = {
|
|
204
|
+
plugin: "web-deepseek-api",
|
|
205
|
+
field: "baseURL",
|
|
206
|
+
label: "Search / Fetch API base URL",
|
|
207
|
+
page: "Web search"
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Validate a Search API response and project its sources into the web service.
|
|
211
|
+
* @param response - untrusted JSON from the Search endpoint.
|
|
212
|
+
* @returns sources with provider order and nullable publication dates preserved.
|
|
213
|
+
* @throws {@link WebError} when the response does not contain valid search results.
|
|
214
|
+
*/
|
|
215
|
+
function mapSearchResponse(response) {
|
|
216
|
+
if (!isRecord(response) || !Array.isArray(response.results)) throw invalidResponse();
|
|
217
|
+
return {
|
|
218
|
+
sources: response.results.map((item) => {
|
|
219
|
+
if (!isRecord(item) || typeof item.url !== "string" || item.url.length === 0 || typeof item.title !== "string" || typeof item.snippet !== "string" || item.date !== null && typeof item.date !== "string") throw invalidResponse();
|
|
220
|
+
return {
|
|
221
|
+
url: item.url,
|
|
222
|
+
...item.title.length > 0 ? { title: item.title } : {},
|
|
223
|
+
...item.snippet.length > 0 ? { snippet: item.snippet } : {},
|
|
224
|
+
...item.date !== null && item.date.length > 0 ? { publishedAt: item.date } : {}
|
|
225
|
+
};
|
|
226
|
+
}),
|
|
227
|
+
truncated: false
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Search through the DeepSeek Search API. HTTP redirects fail as `WEB_PROVIDER_ERROR`;
|
|
232
|
+
* failures after dispatch name the endpoint and tell the model how the user can configure it.
|
|
233
|
+
*/
|
|
234
|
+
var DeepSeekSearchApiProvider = class {
|
|
235
|
+
resolveOptions;
|
|
236
|
+
id = DEEPSEEK_SEARCH_API_PROVIDER_ID;
|
|
237
|
+
/**
|
|
238
|
+
* @param resolveOptions - the options for the NEXT search, read once at each
|
|
239
|
+
* search's entry so one search never mixes two settings sections.
|
|
240
|
+
*/
|
|
241
|
+
constructor(resolveOptions) {
|
|
242
|
+
this.resolveOptions = resolveOptions;
|
|
243
|
+
}
|
|
244
|
+
available() {
|
|
245
|
+
const options = this.resolveOptions();
|
|
246
|
+
return hasDeepSeekWebCredential(options) && URL.canParse(options.baseURL) && isPositiveInteger(options.count);
|
|
247
|
+
}
|
|
248
|
+
search(request, signal) {
|
|
249
|
+
const options = this.resolveOptions();
|
|
250
|
+
const question = boundedQuestion(options.originalQuestion?.());
|
|
251
|
+
return dispatchDeepSeekWeb(options, {
|
|
252
|
+
endpoint: `${options.baseURL.replace(/\/+$/u, "")}/search`,
|
|
253
|
+
body: {
|
|
254
|
+
query: request.query,
|
|
255
|
+
count: Math.min(options.count, request.maxResults ?? options.count),
|
|
256
|
+
need_content: false,
|
|
257
|
+
...question === void 0 ? {} : { ori_question: question }
|
|
258
|
+
},
|
|
259
|
+
decode: mapSearchResponse,
|
|
260
|
+
guidance: {
|
|
261
|
+
subject: "Search",
|
|
262
|
+
...DEEPSEEK_WEB_API_GUIDANCE
|
|
263
|
+
}
|
|
264
|
+
}, signal);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* Bound the triggering question to what `ori_question` accepts: whitespace
|
|
269
|
+
* collapses to single spaces and the first 128 characters remain.
|
|
270
|
+
* @param question - the user question, when one exists.
|
|
271
|
+
* @returns the bounded question, or undefined when nothing remains.
|
|
272
|
+
*/
|
|
273
|
+
function boundedQuestion(question) {
|
|
274
|
+
const collapsed = question?.replace(/\s+/gu, " ").trim() ?? "";
|
|
275
|
+
if (collapsed.length === 0) return void 0;
|
|
276
|
+
return Array.from(collapsed).slice(0, ORIGINAL_QUESTION_MAX_CHARS).join("");
|
|
277
|
+
}
|
|
278
|
+
/** True for positive integral request limits. */
|
|
279
|
+
function isPositiveInteger(value) {
|
|
280
|
+
return Number.isInteger(value) && value > 0;
|
|
281
|
+
}
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region lib/types/fetch.js
|
|
284
|
+
/** Stable id the Fetch API provider registers under in the fetch registry. */
|
|
285
|
+
const DEEPSEEK_FETCH_API_PROVIDER_ID = "deepseek-fetch-api";
|
|
286
|
+
/**
|
|
287
|
+
* Fetch page Markdown through the DeepSeek Fetch API. The API reports neither the
|
|
288
|
+
* target's status nor a final URL, and it fails when the target yields no usable
|
|
289
|
+
* content, so a success is the requested page retrieved with HTTP 200.
|
|
290
|
+
*/
|
|
291
|
+
var DeepSeekFetchApiProvider = class {
|
|
292
|
+
resolveOptions;
|
|
293
|
+
id = DEEPSEEK_FETCH_API_PROVIDER_ID;
|
|
294
|
+
/** @param resolveOptions - the options for the NEXT fetch, read once at each fetch's entry. */
|
|
295
|
+
constructor(resolveOptions) {
|
|
296
|
+
this.resolveOptions = resolveOptions;
|
|
297
|
+
}
|
|
298
|
+
available() {
|
|
299
|
+
const options = this.resolveOptions();
|
|
300
|
+
return hasDeepSeekWebCredential(options) && URL.canParse(options.baseURL);
|
|
301
|
+
}
|
|
302
|
+
fetch(request, signal) {
|
|
303
|
+
const options = this.resolveOptions();
|
|
304
|
+
return dispatchDeepSeekWeb(options, {
|
|
305
|
+
endpoint: `${options.baseURL.replace(/\/+$/u, "")}/fetch`,
|
|
306
|
+
body: { url: request.url },
|
|
307
|
+
decode: (payload) => {
|
|
308
|
+
if (!isRecord(payload) || typeof payload.content !== "string") throw invalidResponse();
|
|
309
|
+
return {
|
|
310
|
+
url: request.url,
|
|
311
|
+
statusCode: 200,
|
|
312
|
+
body: {
|
|
313
|
+
kind: "text",
|
|
314
|
+
content: payload.content
|
|
315
|
+
},
|
|
316
|
+
truncated: false
|
|
317
|
+
};
|
|
318
|
+
},
|
|
319
|
+
guidance: {
|
|
320
|
+
subject: "Fetch",
|
|
321
|
+
...DEEPSEEK_WEB_API_GUIDANCE
|
|
322
|
+
}
|
|
323
|
+
}, signal);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region lib/types/index.js
|
|
328
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
329
|
+
const name = "web-deepseek-api";
|
|
330
|
+
/** The web seam both providers register into. */
|
|
331
|
+
const inject = ["web"];
|
|
332
|
+
const Config = z.object({
|
|
333
|
+
apiKey: z.string().role("secret").volatile(),
|
|
334
|
+
apiKeyEnv: z.string().role("credential-ref").default("DEEPSEEK_API_KEY").volatile(),
|
|
335
|
+
baseURL: z.string().default(DEEPSEEK_WEB_API_BASE_URL).volatile(),
|
|
336
|
+
count: z.number().step(1).min(1).max(50).default(10).volatile(),
|
|
337
|
+
sendOriginalQuestion: z.boolean().default(true).volatile()
|
|
338
|
+
});
|
|
339
|
+
/**
|
|
340
|
+
* Read the text of the latest human prompt, the question that triggered the
|
|
341
|
+
* current turn. Injected user-role context carries another source kind.
|
|
342
|
+
* @param session - the initiating Session, when the search runs inside one.
|
|
343
|
+
* @returns the prompt's joined text blocks, or undefined without a prompt.
|
|
344
|
+
*/
|
|
345
|
+
function latestUserQuestion(session) {
|
|
346
|
+
const prompt = session?.deriveMessages().findLast((message) => message.role === "user" && message.source.kind === "user");
|
|
347
|
+
if (prompt === void 0) return void 0;
|
|
348
|
+
return prompt.content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Register the Search API and Fetch API providers over one live settings section.
|
|
352
|
+
* @param ctx - plugin context owning both registrations.
|
|
353
|
+
* @param config - live settings, read once per operation.
|
|
354
|
+
*/
|
|
355
|
+
function apply(ctx, config) {
|
|
356
|
+
const auth = () => deepSeekWebAuth(ctx, {
|
|
357
|
+
apiKey: config.apiKey.get(),
|
|
358
|
+
apiKeyEnv: config.apiKeyEnv.get()
|
|
359
|
+
});
|
|
360
|
+
ctx.web.registerSearchProvider(new DeepSeekSearchApiProvider(() => ({
|
|
361
|
+
...auth(),
|
|
362
|
+
baseURL: config.baseURL.get(),
|
|
363
|
+
count: config.count.get(),
|
|
364
|
+
...config.sendOriginalQuestion.get() ? { originalQuestion: () => latestUserQuestion(ctx.get("agents")?.currentInitiator()?.session) } : {}
|
|
365
|
+
})));
|
|
366
|
+
ctx.web.registerFetchProvider(new DeepSeekFetchApiProvider(() => ({
|
|
367
|
+
...auth(),
|
|
368
|
+
baseURL: config.baseURL.get()
|
|
369
|
+
})));
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
372
|
+
export { Config, DEEPSEEK_DEFAULT_COUNT, DEEPSEEK_FETCH_API_PROVIDER_ID, DEEPSEEK_SEARCH_API_PROVIDER_ID, DEEPSEEK_WEB_API_BASE_URL, DeepSeekFetchApiProvider, DeepSeekSearchApiProvider, apply, inject, mapSearchResponse, name };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Fetch API provider: page Markdown through one direct request, without
|
|
3
|
+
* a model request.
|
|
4
|
+
* @module @qilin-agent/web-deepseek-api/fetch
|
|
5
|
+
*/
|
|
6
|
+
import type { WebFetchProvider, WebFetchRequest, WebFetchResult } from '@qilin-agent/web';
|
|
7
|
+
import { type DeepSeekWebAuth } from './transport.ts';
|
|
8
|
+
/** Stable id the Fetch API provider registers under in the fetch registry. */
|
|
9
|
+
export declare const DEEPSEEK_FETCH_API_PROVIDER_ID = "deepseek-fetch-api";
|
|
10
|
+
/** Authentication and endpoint settings snapshotted for one fetch. */
|
|
11
|
+
export interface DeepSeekFetchApiOptions extends DeepSeekWebAuth {
|
|
12
|
+
/** API base including its path prefix; `/fetch` is appended. */
|
|
13
|
+
baseURL: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Fetch page Markdown through the DeepSeek Fetch API. The API reports neither the
|
|
17
|
+
* target's status nor a final URL, and it fails when the target yields no usable
|
|
18
|
+
* content, so a success is the requested page retrieved with HTTP 200.
|
|
19
|
+
*/
|
|
20
|
+
export declare class DeepSeekFetchApiProvider implements WebFetchProvider {
|
|
21
|
+
private readonly resolveOptions;
|
|
22
|
+
readonly id = "deepseek-fetch-api";
|
|
23
|
+
/** @param resolveOptions - the options for the NEXT fetch, read once at each fetch's entry. */
|
|
24
|
+
constructor(resolveOptions: () => DeepSeekFetchApiOptions);
|
|
25
|
+
available(): boolean;
|
|
26
|
+
fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=fetch.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register the DeepSeek Search and Fetch APIs in `ctx.web` as `deepseek-search-api`
|
|
3
|
+
* and `deepseek-fetch-api`. Each operation is one direct API request without an
|
|
4
|
+
* auxiliary model request; both providers read this section's credential and API
|
|
5
|
+
* base. `web.searchProvider` and `web.fetchProvider` select them. Every request
|
|
6
|
+
* authenticates with the configured key; QiLin ships no account-token route.
|
|
7
|
+
* @module @qilin-agent/web-deepseek-api
|
|
8
|
+
*/
|
|
9
|
+
import type { Context, Volatile } from '@qilin-agent/kylin';
|
|
10
|
+
import z from '@qilin-agent/schemastery';
|
|
11
|
+
export { DEEPSEEK_FETCH_API_PROVIDER_ID, DeepSeekFetchApiProvider } from './fetch.ts';
|
|
12
|
+
export type { DeepSeekFetchApiOptions } from './fetch.ts';
|
|
13
|
+
export { DEEPSEEK_DEFAULT_COUNT, DEEPSEEK_SEARCH_API_PROVIDER_ID, DEEPSEEK_WEB_API_BASE_URL, DeepSeekSearchApiProvider, mapSearchResponse, } from './search.ts';
|
|
14
|
+
export type { DeepSeekSearchApiOptions } from './search.ts';
|
|
15
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
16
|
+
export declare const name = "web-deepseek-api";
|
|
17
|
+
/** The web seam both providers register into. */
|
|
18
|
+
export declare const inject: string[];
|
|
19
|
+
/** Settings read by the next search or fetch without re-registering either provider. */
|
|
20
|
+
export interface Config {
|
|
21
|
+
/** Literal DeepSeek API key; prefer {@link apiKeyEnv} so no secret enters configuration files. */
|
|
22
|
+
apiKey: Volatile<string | undefined>;
|
|
23
|
+
/** Credential reference resolved for each operation; defaults to `DEEPSEEK_API_KEY`. */
|
|
24
|
+
apiKeyEnv: Volatile<string>;
|
|
25
|
+
/** API base including its path prefix; `/search` and `/fetch` are appended. */
|
|
26
|
+
baseURL: Volatile<string>;
|
|
27
|
+
/** Search API results per search, from 1 through 50; a smaller request bound wins. Defaults to 10. */
|
|
28
|
+
count: Volatile<number>;
|
|
29
|
+
/** Send the triggering user question, bounded to 128 characters, as `ori_question`. Defaults to true. */
|
|
30
|
+
sendOriginalQuestion: Volatile<boolean>;
|
|
31
|
+
}
|
|
32
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
33
|
+
apiKey: z<string, string, "volatile">;
|
|
34
|
+
apiKeyEnv: z<string, string, "volatile-defined">;
|
|
35
|
+
baseURL: z<string, string, "volatile-defined">;
|
|
36
|
+
count: z<number, number, "volatile-defined">;
|
|
37
|
+
sendOriginalQuestion: z<boolean, boolean, "volatile-defined">;
|
|
38
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
39
|
+
apiKey: z<string, string, "volatile">;
|
|
40
|
+
apiKeyEnv: z<string, string, "volatile-defined">;
|
|
41
|
+
baseURL: z<string, string, "volatile-defined">;
|
|
42
|
+
count: z<number, number, "volatile-defined">;
|
|
43
|
+
sendOriginalQuestion: z<boolean, boolean, "volatile-defined">;
|
|
44
|
+
}>>, "plain">;
|
|
45
|
+
/**
|
|
46
|
+
* Register the Search API and Fetch API providers over one live settings section.
|
|
47
|
+
* @param ctx - plugin context owning both registrations.
|
|
48
|
+
* @param config - live settings, read once per operation.
|
|
49
|
+
*/
|
|
50
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
51
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Search API provider: one direct request per search, without an
|
|
3
|
+
* auxiliary model request.
|
|
4
|
+
* @module @qilin-agent/web-deepseek-api/search
|
|
5
|
+
*/
|
|
6
|
+
import type { WebSearchProvider, WebSearchRequest, WebSearchResult } from '@qilin-agent/web';
|
|
7
|
+
import { type DeepSeekWebAuth, type DeepSeekWebGuidance } from './transport.ts';
|
|
8
|
+
/** Stable id the Search API provider registers under in the search registry. */
|
|
9
|
+
export declare const DEEPSEEK_SEARCH_API_PROVIDER_ID = "deepseek-search-api";
|
|
10
|
+
/** Default Search and Fetch API base, including its `/v1/tools` prefix; `/search` and `/fetch` are appended. */
|
|
11
|
+
export declare const DEEPSEEK_WEB_API_BASE_URL = "https://api.deepseek.com/v1/tools";
|
|
12
|
+
/** Default Search API result count, the service's own default. */
|
|
13
|
+
export declare const DEEPSEEK_DEFAULT_COUNT = 10;
|
|
14
|
+
/** Settings named by endpoint failures of either API in this package. */
|
|
15
|
+
export declare const DEEPSEEK_WEB_API_GUIDANCE: Omit<DeepSeekWebGuidance, 'subject'>;
|
|
16
|
+
/** Authentication, endpoint, and result settings snapshotted for one search. */
|
|
17
|
+
export interface DeepSeekSearchApiOptions extends DeepSeekWebAuth {
|
|
18
|
+
/** API base including its path prefix; `/search` is appended. */
|
|
19
|
+
baseURL: string;
|
|
20
|
+
/** Results requested per search, from 1 through 50; a smaller request bound wins. */
|
|
21
|
+
count: number;
|
|
22
|
+
/** Read the user question that triggered the search; undefined or blank sends none. */
|
|
23
|
+
originalQuestion?: () => string | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validate a Search API response and project its sources into the web service.
|
|
27
|
+
* @param response - untrusted JSON from the Search endpoint.
|
|
28
|
+
* @returns sources with provider order and nullable publication dates preserved.
|
|
29
|
+
* @throws {@link WebError} when the response does not contain valid search results.
|
|
30
|
+
*/
|
|
31
|
+
export declare function mapSearchResponse(response: unknown): WebSearchResult;
|
|
32
|
+
/**
|
|
33
|
+
* Search through the DeepSeek Search API. HTTP redirects fail as `WEB_PROVIDER_ERROR`;
|
|
34
|
+
* failures after dispatch name the endpoint and tell the model how the user can configure it.
|
|
35
|
+
*/
|
|
36
|
+
export declare class DeepSeekSearchApiProvider implements WebSearchProvider {
|
|
37
|
+
private readonly resolveOptions;
|
|
38
|
+
readonly id = "deepseek-search-api";
|
|
39
|
+
/**
|
|
40
|
+
* @param resolveOptions - the options for the NEXT search, read once at each
|
|
41
|
+
* search's entry so one search never mixes two settings sections.
|
|
42
|
+
*/
|
|
43
|
+
constructor(resolveOptions: () => DeepSeekSearchApiOptions);
|
|
44
|
+
available(): boolean;
|
|
45
|
+
search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication and redirect-rejecting transport shared by the Search and
|
|
3
|
+
* Fetch API providers in this package. Credential resolution, the dispatch
|
|
4
|
+
* call, and the cancellation helpers deliberately repeat
|
|
5
|
+
* `qilin-web-search-deepseek`, so neither plugin depends on the other; the
|
|
6
|
+
* `jscpd:ignore` regions mark those repeats
|
|
7
|
+
* ([rationale](../../../../.agents/notes/implemented/feature/2026-10-15-deepseek-search-fetch-api.md)).
|
|
8
|
+
* @module @qilin-agent/web-deepseek-api/transport
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@qilin-agent/kylin';
|
|
11
|
+
import { type CredentialRef } from '@qilin-agent/credentials';
|
|
12
|
+
import { WebError } from '@qilin-agent/web';
|
|
13
|
+
/** Authentication sources resolved once for an operation. */
|
|
14
|
+
export interface DeepSeekWebAuth {
|
|
15
|
+
/** Literal API key; takes precedence over the credential resolver. */
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
/** Resolve the current API key without retaining it. */
|
|
18
|
+
resolveApiKey?: () => Promise<string | undefined>;
|
|
19
|
+
/** Credential reference named by missing-key diagnostics. */
|
|
20
|
+
apiKeyEnv?: CredentialRef;
|
|
21
|
+
}
|
|
22
|
+
/** User-owned endpoint settings named by dispatch failures. */
|
|
23
|
+
export interface DeepSeekWebGuidance {
|
|
24
|
+
/** Capability whose endpoint failed. */
|
|
25
|
+
subject: 'Search' | 'Fetch';
|
|
26
|
+
/** Plugin configuration namespace. */
|
|
27
|
+
plugin: string;
|
|
28
|
+
/** Endpoint configuration field. */
|
|
29
|
+
field: string;
|
|
30
|
+
/** Local settings label. */
|
|
31
|
+
label: string;
|
|
32
|
+
/** Built-in settings page title. */
|
|
33
|
+
page: string;
|
|
34
|
+
}
|
|
35
|
+
/** One fully resolved web API request. */
|
|
36
|
+
interface DeepSeekWebRequest<T> {
|
|
37
|
+
/** Absolute dispatch URL. */
|
|
38
|
+
endpoint: string;
|
|
39
|
+
/** JSON request body. */
|
|
40
|
+
body: object;
|
|
41
|
+
/** Protocol headers without credentials. */
|
|
42
|
+
headers?: Readonly<Record<string, string>>;
|
|
43
|
+
/** Additional API-key headers. */
|
|
44
|
+
apiKeyHeaders?: (key: string) => Readonly<Record<string, string>>;
|
|
45
|
+
/** Validate and project the response. */
|
|
46
|
+
decode: (payload: unknown) => T;
|
|
47
|
+
/** Endpoint recovery settings. */
|
|
48
|
+
guidance: DeepSeekWebGuidance;
|
|
49
|
+
/** Record model input before dispatch; a throw prevents dispatch. */
|
|
50
|
+
recordRequest?: (endpoint: string) => void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve credential sources for one operation without retaining secret values.
|
|
54
|
+
* @param ctx - Provider context with request routing and credential services.
|
|
55
|
+
* @param config - Literal key and credential reference from the settings snapshot.
|
|
56
|
+
* @returns Authentication sources for one operation.
|
|
57
|
+
*/
|
|
58
|
+
export declare function deepSeekWebAuth(ctx: Context, config: {
|
|
59
|
+
apiKey: string | undefined;
|
|
60
|
+
apiKeyEnv: string;
|
|
61
|
+
}): DeepSeekWebAuth;
|
|
62
|
+
/**
|
|
63
|
+
* Check whether an authentication source exists; does not resolve credentials.
|
|
64
|
+
* @param auth - Sources for the operation.
|
|
65
|
+
* @returns Whether a literal key or resolver is present.
|
|
66
|
+
*/
|
|
67
|
+
export declare function hasDeepSeekWebCredential(auth: DeepSeekWebAuth): boolean;
|
|
68
|
+
/** Narrow a remote JSON object.
|
|
69
|
+
* @param value - Untrusted JSON.
|
|
70
|
+
* @returns Whether readable object fields exist.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
73
|
+
/** Report malformed upstream JSON without embedding response content.
|
|
74
|
+
* @returns The stable provider error.
|
|
75
|
+
*/
|
|
76
|
+
export declare function invalidResponse(): WebError;
|
|
77
|
+
/** Dispatch one operation using a single settings and credential snapshot.
|
|
78
|
+
* @param options - Authentication sources for this operation.
|
|
79
|
+
* @param request - Resolved endpoint, JSON payload, and response decoder.
|
|
80
|
+
* @param signal - Caller cancellation signal.
|
|
81
|
+
* @returns The validated provider result.
|
|
82
|
+
*/
|
|
83
|
+
export declare function dispatchDeepSeekWeb<T>(options: DeepSeekWebAuth, request: DeepSeekWebRequest<T>, signal?: AbortSignal): Promise<T>;
|
|
84
|
+
export {};
|
|
85
|
+
//# sourceMappingURL=transport.d.ts.map
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qilin-agent/web-deepseek-api",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "DeepSeek Search and Fetch API providers for the QiLin web capability seam (ctx.web)",
|
|
4
|
+
"version": "3.1.6",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/kkutysllb/QiLin.git",
|
|
11
|
+
"directory": "packages/web/web-deepseek-api"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "lib/index.js",
|
|
18
|
+
"types": "lib/types/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./lib/types/index.d.ts",
|
|
22
|
+
"default": "./lib/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./src/*": "./src/*",
|
|
25
|
+
"./package.json": "./package.json",
|
|
26
|
+
"./locale/*.json": "./locale/*.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"locale/*.json",
|
|
30
|
+
"lib/index.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@qilin-agent/agent": "3.1.6",
|
|
36
|
+
"@qilin-agent/web": "3.1.6",
|
|
37
|
+
"@qilin-agent/launch-environment": "3.1.6",
|
|
38
|
+
"@qilin-agent/kylin": "~4.0.5",
|
|
39
|
+
"@qilin-agent/credentials": "3.1.6",
|
|
40
|
+
"@qilin-agent/session": "3.1.6"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@qilin-agent/schemastery": "~3.18.5"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@qilin-agent/credentials": "3.1.6",
|
|
47
|
+
"@qilin-agent/credentials-local": "3.1.6",
|
|
48
|
+
"@qilin-agent/agent": "3.1.6",
|
|
49
|
+
"@qilin-agent/launch-environment": "3.1.6",
|
|
50
|
+
"@qilin-agent/session": "3.1.6",
|
|
51
|
+
"@qilin-agent/web": "3.1.6",
|
|
52
|
+
"@qilin-agent/http-proxy": "3.1.6",
|
|
53
|
+
"@qilin-agent/kylin": "~4.0.5"
|
|
54
|
+
}
|
|
6
55
|
}
|