@deepseek-ai/dsh-web-search-perplexity 0.0.1-rc.1
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 +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +65 -0
- package/README.zh.md +65 -0
- package/lib/index.js +154 -0
- package/lib/invariant.js +23 -0
- package/lib/types/index.d.ts +33 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/provider.d.ts +57 -0
- package/lib/types/types.d.ts +41 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, DeepSeek
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/web/web-search-perplexity/README.md
|
|
5
|
+
README.md: 8800609a05e99d3fa8a61e12b2360e633f0fd330
|
|
6
|
+
README.zh.md: fb20d796e9e943a5254b1d22eac3650e45ec95bf
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-web-search-perplexity
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
A [Perplexity](https://perplexity.ai)-backed `WebSearchProvider` for the harness [web capability seam](../web/README.md) (`ctx.web`). It calls Perplexity's OpenAI-compatible `POST /chat/completions` endpoint and maps the generated answer plus citations into the seam's normalized `WebSearchResult`.
|
|
6
|
+
|
|
7
|
+
This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the key and it does not register a model-facing tool. Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`). The OpenAI-compatible wire shape is a provider-private detail — it does **not** make this provider depend on `ctx.llm`.
|
|
8
|
+
|
|
9
|
+
## Config
|
|
10
|
+
|
|
11
|
+
| Key | Default | Meaning |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `apiKey` | `$PERPLEXITY_API_KEY` | Perplexity API key. Empty/absent makes the provider unavailable. |
|
|
14
|
+
| `baseURL` | `https://api.perplexity.ai` | Endpoint base; `/chat/completions` is appended. An unparseable value makes the provider unavailable. |
|
|
15
|
+
| `model` | `sonar` | Search model name. |
|
|
16
|
+
| `maxTokens` | `1024` | Upper bound on generated answer tokens (`max_tokens`). Must be a positive integer. |
|
|
17
|
+
| `searchRecency` | (unset) | Recency window sent as `search_recency_filter`: `day`, `week`, `month`, or `year`. Unset sends no filter. |
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
- id: web-search-perplexity
|
|
21
|
+
name: '@deepseek-ai/dsh-web-search-perplexity'
|
|
22
|
+
config:
|
|
23
|
+
apiKey: !!js process.env.PERPLEXITY_API_KEY
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Mapping
|
|
27
|
+
|
|
28
|
+
`content` ← `choices[0].message.content` (the generated answer). `sources[]` prefers the structured `search_results[]` (`url`, `title`, `snippet`, `publishedAt` ← `date`), falling back to the URL-only `citations[]` array only when `search_results` is absent — those sources carry just a `url`, which is why `title`/`snippet`/`publishedAt` are optional on the seam. Provider failures surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. HTTP redirects are rejected before the `Location` target is contacted and surface as `WEB_PROVIDER_ERROR`. Perplexity has no result-count control, so `maxResults` is enforced by the seam (truncating `sources[]` and setting `truncated`).
|
|
29
|
+
|
|
30
|
+
## Model Experience
|
|
31
|
+
|
|
32
|
+
### Auxiliary Perplexity request
|
|
33
|
+
|
|
34
|
+
#### What the model sees
|
|
35
|
+
|
|
36
|
+
A separate Perplexity model receives `<query>` verbatim as its sole user message through the chat-completions endpoint. This request is not part of the conversation model's context.
|
|
37
|
+
|
|
38
|
+
#### Token effect
|
|
39
|
+
|
|
40
|
+
Separate provider tokens are incurred per search; `maxTokens` caps the generated answer.
|
|
41
|
+
|
|
42
|
+
#### KV Cache effect
|
|
43
|
+
|
|
44
|
+
Independent of the conversation request cache. An identical query under the same model route may reuse provider cache; a changed query or route establishes a different prefix.
|
|
45
|
+
|
|
46
|
+
### Conversation tool result, indirectly
|
|
47
|
+
|
|
48
|
+
#### What the model sees
|
|
49
|
+
|
|
50
|
+
Through [`dsh-tool-web`](../tool-web/README.md), the conversation model sees the generated answer plus structured result metadata or URL-only citations. This provider's exact failures are `Perplexity search aborted`, `Perplexity search request failed: <error>`, and `Perplexity returned an unprocessable response body: <error>`; HTTP failures preserve the provider message. The consumer owns the error wrapper.
|
|
51
|
+
|
|
52
|
+
#### Token effect
|
|
53
|
+
|
|
54
|
+
Zero direct conversation tokens from registration. Answer and source tokens are data-dependent, source count is service-bounded, and the retained result or error is resent until compaction.
|
|
55
|
+
|
|
56
|
+
#### KV Cache effect
|
|
57
|
+
|
|
58
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
59
|
+
|
|
60
|
+
## Known Limitations and Deferred Work
|
|
61
|
+
|
|
62
|
+
- **Citation-fallback sources are URL-only** — when Perplexity omits structured `search_results[]`, sources carry no `title`/`snippet`/`publishedAt`, so the tool renders bare hostname labels.
|
|
63
|
+
- **Over-returned sources still cost tokens and latency** — with no result-count control on the wire, `maxResults` is enforced only post-hoc by seam truncation.
|
|
64
|
+
- **Only `model`/`maxTokens`/`searchRecency` are exposed** — Perplexity's other search controls (domain filters, `web_search_options` context size, images) wait on provider-neutral Service Definition fields ([seam Agent Note](../../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md)).
|
|
65
|
+
- **Abort classification is error-shape-based** — only a `DOMException` named `AbortError` maps to `WEB_ABORTED`; an abort carrying a custom reason (e.g. `dsh-timeout`'s `TimeoutReason`) surfaces as `WEB_PROVIDER_ERROR`.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-web-search-perplexity
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
由 [Perplexity](https://perplexity.ai) 支持的 `WebSearchProvider`,用于 harness [web 能力 seam](../web/README.md)(`ctx.web`)。它调用 Perplexity 的 OpenAI 兼容 `POST /chat/completions` 端点,把生成答案与引用映射为 seam 规范化的 `WebSearchResult`。
|
|
6
|
+
|
|
7
|
+
这是一个**实现**包:它向 `ctx.web` 注册提供方,不拥有该键,也不注册面向模型的工具。与 `@deepseek-ai/dsh-llm-deepseek` 一样,它是函数/命名空间插件(`inject: ['web']`)。OpenAI 兼容协议格式(wire format)是提供方私有细节,并**不**使该提供方依赖 `ctx.llm`。
|
|
8
|
+
|
|
9
|
+
## 配置
|
|
10
|
+
|
|
11
|
+
| 配置键 | 默认值 | 含义 |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `apiKey` | `$PERPLEXITY_API_KEY` | Perplexity API 密钥。为空或缺失时提供方不可用。 |
|
|
14
|
+
| `baseURL` | `https://api.perplexity.ai` | 端点基址;追加 `/chat/completions`。无法解析时提供方不可用。 |
|
|
15
|
+
| `model` | `sonar` | 搜索模型名称。 |
|
|
16
|
+
| `maxTokens` | `1024` | 生成答案 token 上限(`max_tokens`)。必须是正整数。 |
|
|
17
|
+
| `searchRecency` | (未设置) | 以 `search_recency_filter` 发送的新近程度窗口:`day`、`week`、`month` 或 `year`。未设置时不发送过滤条件。 |
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
- id: web-search-perplexity
|
|
21
|
+
name: '@deepseek-ai/dsh-web-search-perplexity'
|
|
22
|
+
config:
|
|
23
|
+
apiKey: !!js process.env.PERPLEXITY_API_KEY
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 映射
|
|
27
|
+
|
|
28
|
+
`content` ← `choices[0].message.content`(生成答案)。`sources[]` 优先使用结构化 `search_results[]`(`url`、`title`、`snippet`、`publishedAt` ← `date`),否则回退到只含 URL 的 `citations[]` 数组;仅当不存在 `search_results` 时才采取这条回退路径。这些源只携带 `url`,因此 seam 上的 `title`/`snippet`/`publishedAt` 是可选字段。提供方失败以 `WebError` `WEB_PROVIDER_ERROR` 呈现;中止请求以 `WEB_ABORTED` 呈现。HTTP 重定向会在访问 `Location` 指向的目标之前被拒绝,并以 `WEB_PROVIDER_ERROR` 呈现。Perplexity 没有结果数量控制,因此 seam 会强制执行 `maxResults`(截断 `sources[]` 并设置 `truncated`)。
|
|
29
|
+
|
|
30
|
+
## 模型体验
|
|
31
|
+
|
|
32
|
+
### 辅助 Perplexity 请求
|
|
33
|
+
|
|
34
|
+
#### 模型看到的内容
|
|
35
|
+
|
|
36
|
+
独立的 Perplexity 模型通过 chat-completions 端点将 `<query>` 原样作为唯一用户消息接收。该请求不属于会话模型上下文。
|
|
37
|
+
|
|
38
|
+
#### Token 影响
|
|
39
|
+
|
|
40
|
+
每次搜索会产生独立的提供方 token;`maxTokens` 限制生成答案。
|
|
41
|
+
|
|
42
|
+
#### KV Cache 影响
|
|
43
|
+
|
|
44
|
+
与会话请求缓存相互独立。同一模型路由下的相同查询可能复用提供方缓存;查询或路由改变会建立不同前缀。
|
|
45
|
+
|
|
46
|
+
### 间接的会话工具结果
|
|
47
|
+
|
|
48
|
+
#### 模型看到的内容
|
|
49
|
+
|
|
50
|
+
通过 [`dsh-tool-web`](../tool-web/README.md),会话模型会看到生成答案及结构化结果元数据,或只含 URL 的引用。该提供方确切的错误消息为 `Perplexity search aborted`、`Perplexity search request failed: <error>` 和 `Perplexity returned an unprocessable response body: <error>`;HTTP 失败保留提供方消息。错误包装层属于消费方。
|
|
51
|
+
|
|
52
|
+
#### Token 影响
|
|
53
|
+
|
|
54
|
+
注册不会直接产生会话 token。答案与源 token 取决于数据,源数量受服务限制;保留的结果或错误会重复发送,直到发生压缩(compaction)。
|
|
55
|
+
|
|
56
|
+
#### KV Cache 影响
|
|
57
|
+
|
|
58
|
+
仅追加;新可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
|
|
59
|
+
|
|
60
|
+
## 已知限制与暂缓事项
|
|
61
|
+
|
|
62
|
+
- **引用回退源只含 URL**:Perplexity 省略结构化 `search_results[]` 时,源不含 `title`/`snippet`/`publishedAt`,因此工具只渲染纯主机名标签。
|
|
63
|
+
- **超量返回的来源仍会增加 token 消耗和延迟**:协议没有结果数量控制,`maxResults` 只能由 seam 在事后截断。
|
|
64
|
+
- **只公开 `model`/`maxTokens`/`searchRecency`**:Perplexity 的其他搜索控制项(域名过滤条件、`web_search_options` 上下文大小、图片)等待提供方无关的 Service Definition 字段(见 [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md))。
|
|
65
|
+
- **按错误形状分类中止**:只有 `DOMException` 且名为 `AbortError` 时才映射为 `WEB_ABORTED`;携带自定义原因的中止(例如 `dsh-timeout` 的 `TimeoutReason`)会呈现为 `WEB_PROVIDER_ERROR`。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { environmentOf } from "@deepseek-ai/dsh-environment";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { WebError } from "@deepseek-ai/dsh-web";
|
|
4
|
+
//#region lib/types/provider.js
|
|
5
|
+
/**
|
|
6
|
+
* Perplexity search over its OpenAI-compatible chat-completions endpoint. The generated answer
|
|
7
|
+
* becomes `content`; sources prefer structured `search_results[]` and fall back to URL-only
|
|
8
|
+
* `citations[]`. The wire format and native `fetch` client are provider-private and do not use
|
|
9
|
+
* `ctx.llm`.
|
|
10
|
+
* @module @deepseek-ai/dsh-web-search-perplexity/provider
|
|
11
|
+
*/
|
|
12
|
+
/** Stable id this provider registers under. */
|
|
13
|
+
const PERPLEXITY_PROVIDER_ID = "perplexity";
|
|
14
|
+
/** Default Perplexity endpoint; `/chat/completions` is the operation. */
|
|
15
|
+
const PERPLEXITY_DEFAULT_BASE_URL = "https://api.perplexity.ai";
|
|
16
|
+
/** Default search model. */
|
|
17
|
+
const PERPLEXITY_DEFAULT_MODEL = "sonar";
|
|
18
|
+
/** Default upper bound on generated answer tokens. */
|
|
19
|
+
const PERPLEXITY_DEFAULT_MAX_TOKENS = 1024;
|
|
20
|
+
/** Attribution header sent on every request. Bump with the package version. */
|
|
21
|
+
const USER_AGENT = "deepseek-harness/0.0.1";
|
|
22
|
+
/**
|
|
23
|
+
* Map one structured Perplexity search result to a normalized source.
|
|
24
|
+
*
|
|
25
|
+
* @param result - one entry of the response's `search_results[]`.
|
|
26
|
+
* @returns the normalized source; blank fields are omitted rather than set empty.
|
|
27
|
+
*/
|
|
28
|
+
function mapPerplexityResult(result) {
|
|
29
|
+
return {
|
|
30
|
+
url: result.url,
|
|
31
|
+
...result.title != null && result.title.length > 0 ? { title: result.title } : {},
|
|
32
|
+
...result.snippet != null && result.snippet.length > 0 ? { snippet: result.snippet } : {},
|
|
33
|
+
...result.date != null && result.date.length > 0 ? { publishedAt: result.date } : {}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Map a Perplexity response envelope to a normalized search result. Prefers
|
|
38
|
+
* structured `search_results[]`; falls back to URL-only `citations[]` (those
|
|
39
|
+
* sources carry just a `url`) only when `search_results` is absent.
|
|
40
|
+
*
|
|
41
|
+
* @param response - the parsed chat-completions response body.
|
|
42
|
+
* @returns the normalized result; `content` is omitted when the answer is empty.
|
|
43
|
+
*/
|
|
44
|
+
function mapPerplexityResponse(response) {
|
|
45
|
+
const content = response.choices?.[0]?.message?.content;
|
|
46
|
+
const sources = response.search_results !== void 0 ? response.search_results.map(mapPerplexityResult) : (response.citations ?? []).map((url) => ({ url }));
|
|
47
|
+
return {
|
|
48
|
+
...content != null && content.length > 0 ? { content } : {},
|
|
49
|
+
sources,
|
|
50
|
+
truncated: false
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** The Perplexity-backed search provider; HTTP redirects fail as `WEB_PROVIDER_ERROR`. */
|
|
54
|
+
var PerplexitySearchProvider = class {
|
|
55
|
+
options;
|
|
56
|
+
id = PERPLEXITY_PROVIDER_ID;
|
|
57
|
+
constructor(options) {
|
|
58
|
+
this.options = options;
|
|
59
|
+
}
|
|
60
|
+
available() {
|
|
61
|
+
return this.options.apiKey.length > 0 && URL.canParse(this.options.baseURL) && isPositiveInteger(this.options.maxTokens);
|
|
62
|
+
}
|
|
63
|
+
async search(request, signal) {
|
|
64
|
+
let response;
|
|
65
|
+
try {
|
|
66
|
+
response = await fetch(`${this.options.baseURL}/chat/completions`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
redirect: "error",
|
|
69
|
+
headers: {
|
|
70
|
+
"authorization": `Bearer ${this.options.apiKey}`,
|
|
71
|
+
"content-type": "application/json",
|
|
72
|
+
"accept": "application/json",
|
|
73
|
+
"user-agent": USER_AGENT
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
model: this.options.model,
|
|
77
|
+
max_tokens: this.options.maxTokens,
|
|
78
|
+
messages: [{
|
|
79
|
+
role: "user",
|
|
80
|
+
content: request.query
|
|
81
|
+
}],
|
|
82
|
+
...this.options.searchRecency !== void 0 ? { search_recency_filter: this.options.searchRecency } : {}
|
|
83
|
+
}),
|
|
84
|
+
...signal !== void 0 ? { signal } : {}
|
|
85
|
+
});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (isAbortError(error)) throw new WebError("Perplexity search aborted", "WEB_ABORTED", { cause: error });
|
|
88
|
+
throw new WebError(`Perplexity search request failed: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
|
|
89
|
+
}
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
let message = `Perplexity API error (HTTP ${response.status})`;
|
|
92
|
+
try {
|
|
93
|
+
const parsed = await response.json();
|
|
94
|
+
const detail = typeof parsed.error === "string" ? parsed.error : parsed.error?.message ?? parsed.message;
|
|
95
|
+
if (detail !== void 0 && detail.length > 0) message = detail;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (isAbortError(error)) throw new WebError("Perplexity search aborted", "WEB_ABORTED", { cause: error });
|
|
98
|
+
}
|
|
99
|
+
throw new WebError(message, "WEB_PROVIDER_ERROR");
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
return mapPerplexityResponse(await response.json());
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (isAbortError(error)) throw new WebError("Perplexity search aborted", "WEB_ABORTED", { cause: error });
|
|
105
|
+
throw new WebError(`Perplexity returned an unprocessable response body: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
/** True for a fetch/`AbortSignal` abort, surfaced as `WEB_ABORTED`. */
|
|
110
|
+
function isAbortError(error) {
|
|
111
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
112
|
+
}
|
|
113
|
+
/** True for a request limit that can be sent to Perplexity (a positive whole number). */
|
|
114
|
+
function isPositiveInteger(value) {
|
|
115
|
+
return Number.isInteger(value) && value > 0;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region lib/types/index.js
|
|
119
|
+
/**
|
|
120
|
+
* `@deepseek-ai/dsh-web-search-perplexity`: registers a Perplexity-backed
|
|
121
|
+
* `WebSearchProvider` with `ctx.web`. A function/namespace plugin (NOT a
|
|
122
|
+
* default-export service): it registers INTO the seam's provider registry, like
|
|
123
|
+
* `@deepseek-ai/dsh-llm-deepseek` registers an adapter into `ctx.llm`.
|
|
124
|
+
*
|
|
125
|
+
* @module @deepseek-ai/dsh-web-search-perplexity
|
|
126
|
+
*/
|
|
127
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
128
|
+
const name = "web-search-perplexity";
|
|
129
|
+
/** The web seam this provider registers into. */
|
|
130
|
+
const inject = ["web"];
|
|
131
|
+
const Config = z.object({
|
|
132
|
+
apiKey: z.string(),
|
|
133
|
+
baseURL: z.string(),
|
|
134
|
+
model: z.string(),
|
|
135
|
+
maxTokens: z.number().step(1).min(1),
|
|
136
|
+
searchRecency: z.union([
|
|
137
|
+
"day",
|
|
138
|
+
"week",
|
|
139
|
+
"month",
|
|
140
|
+
"year"
|
|
141
|
+
])
|
|
142
|
+
});
|
|
143
|
+
/** Register the Perplexity search provider with `ctx.web`. */
|
|
144
|
+
function apply(ctx, config) {
|
|
145
|
+
ctx.web.registerSearchProvider(new PerplexitySearchProvider({
|
|
146
|
+
apiKey: config.apiKey ?? environmentOf(ctx).get("PERPLEXITY_API_KEY")?.value ?? "",
|
|
147
|
+
baseURL: config.baseURL ?? "https://api.perplexity.ai",
|
|
148
|
+
model: config.model ?? "sonar",
|
|
149
|
+
maxTokens: config.maxTokens ?? 1024,
|
|
150
|
+
...config.searchRecency !== void 0 ? { searchRecency: config.searchRecency } : {}
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { Config, PERPLEXITY_DEFAULT_BASE_URL, PERPLEXITY_DEFAULT_MAX_TOKENS, PERPLEXITY_DEFAULT_MODEL, PERPLEXITY_PROVIDER_ID, PerplexitySearchProvider, apply, inject, name };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-web-search-perplexity`.
|
|
4
|
+
* @module @deepseek-ai/dsh-web-search-perplexity/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-web-search-perplexity";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "web-search-perplexity-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,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@deepseek-ai/dsh-web-search-perplexity`: registers a Perplexity-backed
|
|
3
|
+
* `WebSearchProvider` with `ctx.web`. A function/namespace plugin (NOT a
|
|
4
|
+
* default-export service): it registers INTO the seam's provider registry, like
|
|
5
|
+
* `@deepseek-ai/dsh-llm-deepseek` registers an adapter into `ctx.llm`.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-web-search-perplexity
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import z from '@deepseek-ai/schemastery';
|
|
11
|
+
export { PERPLEXITY_DEFAULT_BASE_URL, PERPLEXITY_DEFAULT_MAX_TOKENS, PERPLEXITY_DEFAULT_MODEL, PERPLEXITY_PROVIDER_ID, PerplexitySearchProvider, } from './provider.ts';
|
|
12
|
+
export type { PerplexityRecency, PerplexitySearchProviderOptions } from './provider.ts';
|
|
13
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
14
|
+
export declare const name = "web-search-perplexity";
|
|
15
|
+
/** The web seam this provider registers into. */
|
|
16
|
+
export declare const inject: string[];
|
|
17
|
+
/** Plugin config (all optional — `apply` fills env-var and constant defaults). */
|
|
18
|
+
export interface Config {
|
|
19
|
+
/** Perplexity API key. Falls back to `$PERPLEXITY_API_KEY`. Empty → unavailable. */
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
/** Endpoint base; `/chat/completions` is appended. Defaults to the public API. */
|
|
22
|
+
baseURL?: string;
|
|
23
|
+
/** Search model name. Defaults to `sonar`. */
|
|
24
|
+
model?: string;
|
|
25
|
+
/** Upper bound on generated answer tokens. Defaults to 1024. */
|
|
26
|
+
maxTokens?: number;
|
|
27
|
+
/** Recency window sent as `search_recency_filter`. Omitted = no filter. */
|
|
28
|
+
searchRecency?: 'day' | 'week' | 'month' | 'year';
|
|
29
|
+
}
|
|
30
|
+
export declare const Config: z<Config>;
|
|
31
|
+
/** Register the Perplexity search provider with `ctx.web`. */
|
|
32
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-web-search-perplexity`.
|
|
3
|
+
* @module @deepseek-ai/dsh-web-search-perplexity/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "web-search-perplexity-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Perplexity search over its OpenAI-compatible chat-completions endpoint. The generated answer
|
|
3
|
+
* becomes `content`; sources prefer structured `search_results[]` and fall back to URL-only
|
|
4
|
+
* `citations[]`. The wire format and native `fetch` client are provider-private and do not use
|
|
5
|
+
* `ctx.llm`.
|
|
6
|
+
* @module @deepseek-ai/dsh-web-search-perplexity/provider
|
|
7
|
+
*/
|
|
8
|
+
import type { WebSearchProvider, WebSearchRequest, WebSearchResult, WebSearchSource } from '@deepseek-ai/dsh-web';
|
|
9
|
+
import type { PerplexityResponse, PerplexitySearchResult } from './types.ts';
|
|
10
|
+
/** Stable id this provider registers under. */
|
|
11
|
+
export declare const PERPLEXITY_PROVIDER_ID = "perplexity";
|
|
12
|
+
/** Default Perplexity endpoint; `/chat/completions` is the operation. */
|
|
13
|
+
export declare const PERPLEXITY_DEFAULT_BASE_URL = "https://api.perplexity.ai";
|
|
14
|
+
/** Default search model. */
|
|
15
|
+
export declare const PERPLEXITY_DEFAULT_MODEL = "sonar";
|
|
16
|
+
/** Default upper bound on generated answer tokens. */
|
|
17
|
+
export declare const PERPLEXITY_DEFAULT_MAX_TOKENS = 1024;
|
|
18
|
+
/** Recency filter values Perplexity accepts for `search_recency_filter`. */
|
|
19
|
+
export type PerplexityRecency = 'day' | 'week' | 'month' | 'year';
|
|
20
|
+
/** Resolved provider options (the plugin's `apply` supplies env-var and constant defaults). */
|
|
21
|
+
export interface PerplexitySearchProviderOptions {
|
|
22
|
+
/** Perplexity API key. Empty/absent makes the provider unavailable. */
|
|
23
|
+
apiKey: string;
|
|
24
|
+
/** Endpoint base; `/chat/completions` is appended. */
|
|
25
|
+
baseURL: string;
|
|
26
|
+
/** Search model name. */
|
|
27
|
+
model: string;
|
|
28
|
+
/** Upper bound on generated answer tokens (`max_tokens`). */
|
|
29
|
+
maxTokens: number;
|
|
30
|
+
/** Optional recency window sent as `search_recency_filter`; omitted = no filter. */
|
|
31
|
+
searchRecency?: PerplexityRecency;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Map one structured Perplexity search result to a normalized source.
|
|
35
|
+
*
|
|
36
|
+
* @param result - one entry of the response's `search_results[]`.
|
|
37
|
+
* @returns the normalized source; blank fields are omitted rather than set empty.
|
|
38
|
+
*/
|
|
39
|
+
export declare function mapPerplexityResult(result: PerplexitySearchResult): WebSearchSource;
|
|
40
|
+
/**
|
|
41
|
+
* Map a Perplexity response envelope to a normalized search result. Prefers
|
|
42
|
+
* structured `search_results[]`; falls back to URL-only `citations[]` (those
|
|
43
|
+
* sources carry just a `url`) only when `search_results` is absent.
|
|
44
|
+
*
|
|
45
|
+
* @param response - the parsed chat-completions response body.
|
|
46
|
+
* @returns the normalized result; `content` is omitted when the answer is empty.
|
|
47
|
+
*/
|
|
48
|
+
export declare function mapPerplexityResponse(response: PerplexityResponse): WebSearchResult;
|
|
49
|
+
/** The Perplexity-backed search provider; HTTP redirects fail as `WEB_PROVIDER_ERROR`. */
|
|
50
|
+
export declare class PerplexitySearchProvider implements WebSearchProvider {
|
|
51
|
+
private readonly options;
|
|
52
|
+
readonly id = "perplexity";
|
|
53
|
+
constructor(options: PerplexitySearchProviderOptions);
|
|
54
|
+
available(): boolean;
|
|
55
|
+
search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types for the Perplexity search API (`POST https://api.perplexity.ai/chat/completions`,
|
|
3
|
+
* an OpenAI-compatible chat shape). Results prefer structured `search_results` and fall back to
|
|
4
|
+
* URL-only `citations`; the provider-private wire shape does not depend on `ctx.llm`.
|
|
5
|
+
* @module @deepseek-ai/dsh-web-search-perplexity/types
|
|
6
|
+
*/
|
|
7
|
+
/** Request body sent to Perplexity's chat-completions endpoint. */
|
|
8
|
+
export interface PerplexityRequest {
|
|
9
|
+
model: string;
|
|
10
|
+
messages: {
|
|
11
|
+
role: 'user';
|
|
12
|
+
content: string;
|
|
13
|
+
}[];
|
|
14
|
+
}
|
|
15
|
+
/** One structured search result (the preferred citation surface). */
|
|
16
|
+
export interface PerplexitySearchResult {
|
|
17
|
+
url: string;
|
|
18
|
+
title?: string | null;
|
|
19
|
+
snippet?: string | null;
|
|
20
|
+
date?: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** Perplexity's response envelope. */
|
|
23
|
+
export interface PerplexityResponse {
|
|
24
|
+
choices?: {
|
|
25
|
+
message?: {
|
|
26
|
+
content?: string | null;
|
|
27
|
+
};
|
|
28
|
+
}[];
|
|
29
|
+
/** Structured citation surface (preferred). */
|
|
30
|
+
search_results?: PerplexitySearchResult[];
|
|
31
|
+
/** URL-only citation fallback. */
|
|
32
|
+
citations?: string[];
|
|
33
|
+
}
|
|
34
|
+
/** Perplexity's error response envelope (best-effort; fields vary). */
|
|
35
|
+
export interface PerplexityError {
|
|
36
|
+
error?: {
|
|
37
|
+
message?: string;
|
|
38
|
+
} | string;
|
|
39
|
+
message?: string;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-web-search-perplexity",
|
|
3
|
+
"description": "Perplexity-backed search provider for the DeepSeek Harness web capability seam (ctx.web)",
|
|
4
|
+
"version": "0.0.1-rc.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/web/web-search-perplexity"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "BSD-3-Clause",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@deepseek-ai/dsh-environment": "^0.0.1-rc.1",
|
|
36
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
37
|
+
"@deepseek-ai/dsh-web": "^0.0.1-rc.1",
|
|
38
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@deepseek-ai/schemastery": "^3.18.1-rc.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@deepseek-ai/dsh-environment": "^0.0.1-rc.1",
|
|
45
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-web": "^0.0.1-rc.1",
|
|
47
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
48
|
+
}
|
|
49
|
+
}
|