@dsh-plus/web-search-services 0.1.1 → 0.1.3
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/lib/index.js +19 -5
- package/package.json +1 -1
- package/src/normalize.ts +27 -8
package/lib/index.js
CHANGED
|
@@ -118,6 +118,12 @@ function asNonEmptyString(value) {
|
|
|
118
118
|
function asArray(value) {
|
|
119
119
|
return Array.isArray(value) ? value : [];
|
|
120
120
|
}
|
|
121
|
+
/** snippet 截断上限:官方 deepseek provider 的摘录是短引文;Tavily content /
|
|
122
|
+
* Exa highlights 原始值可达整页文本,直接透传会撑爆工具结果上下文。 */
|
|
123
|
+
const SNIPPET_MAX_CHARS = 500;
|
|
124
|
+
function truncateSnippet(text) {
|
|
125
|
+
return text.length <= SNIPPET_MAX_CHARS ? text : `${text.slice(0, SNIPPET_MAX_CHARS)}…`;
|
|
126
|
+
}
|
|
121
127
|
function makeSource(url, title, snippet, publishedAt) {
|
|
122
128
|
return {
|
|
123
129
|
url,
|
|
@@ -126,6 +132,12 @@ function makeSource(url, title, snippet, publishedAt) {
|
|
|
126
132
|
...publishedAt !== void 0 ? { publishedAt } : {}
|
|
127
133
|
};
|
|
128
134
|
}
|
|
135
|
+
/** skill 各服务 request() 统一信封 {ok, service, status, data: <API 响应>};
|
|
136
|
+
* 取 data 为业务 payload,data 缺失/空对象时回退顶层(旧形状容差)。 */
|
|
137
|
+
function payloadOf(raw) {
|
|
138
|
+
const data = asObject(raw.data);
|
|
139
|
+
return Object.keys(data).length > 0 ? data : raw;
|
|
140
|
+
}
|
|
129
141
|
function finish(content, sources) {
|
|
130
142
|
const seen = /* @__PURE__ */ new Set();
|
|
131
143
|
const deduped = sources.filter((source) => {
|
|
@@ -148,19 +160,21 @@ function sourcesFromResults(results, pick) {
|
|
|
148
160
|
return out;
|
|
149
161
|
}
|
|
150
162
|
function normalizeTavily(raw) {
|
|
151
|
-
const
|
|
163
|
+
const payload = payloadOf(raw);
|
|
164
|
+
const sources = sourcesFromResults(payload.results, (item) => {
|
|
152
165
|
const url = asNonEmptyString(item.url);
|
|
166
|
+
const content = asNonEmptyString(item.content);
|
|
153
167
|
if (url === void 0) return void 0;
|
|
154
|
-
return makeSource(url, asNonEmptyString(item.title),
|
|
168
|
+
return makeSource(url, asNonEmptyString(item.title), content !== void 0 ? truncateSnippet(content) : void 0, asNonEmptyString(item.published_date));
|
|
155
169
|
});
|
|
156
|
-
return finish(asNonEmptyString(
|
|
170
|
+
return finish(asNonEmptyString(payload.answer), sources);
|
|
157
171
|
}
|
|
158
172
|
function normalizeExa(raw) {
|
|
159
|
-
return finish(void 0, sourcesFromResults(raw.results, (item) => {
|
|
173
|
+
return finish(void 0, sourcesFromResults(payloadOf(raw).results, (item) => {
|
|
160
174
|
const url = asNonEmptyString(item.url) ?? asNonEmptyString(item.id);
|
|
161
175
|
if (url === void 0) return void 0;
|
|
162
176
|
const highlights = asArray(item.highlights).map((h) => asNonEmptyString(h)).filter((h) => h !== void 0);
|
|
163
|
-
return makeSource(url, asNonEmptyString(item.title), highlights.length > 0 ? highlights.join(" ") : void 0, asNonEmptyString(item.publishedDate));
|
|
177
|
+
return makeSource(url, asNonEmptyString(item.title), highlights.length > 0 ? truncateSnippet(highlights.join(" ")) : void 0, asNonEmptyString(item.publishedDate));
|
|
164
178
|
}));
|
|
165
179
|
}
|
|
166
180
|
const MARKDOWN_LINK = /\[([^\]]*)\]\((https?:\/\/[^)\s]+)\)/g;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/web-search-services",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "dsh-plus service plugin: web_search 免费后端聚合——经官方 ctx.web provider 扩展点桥接 search-services skill(Tavily/Exa/OpenAI Chat 免费额度,优先级+自动回退+key 配置),官方 web_search 工具与输出格式零修改",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/src/normalize.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* search.py 输出 → dsh-web 标准 WebSearchResult 归一化(纯函数)。
|
|
3
3
|
*
|
|
4
|
-
* skill
|
|
5
|
-
* openai-chat
|
|
6
|
-
*
|
|
4
|
+
* skill 各服务 request() 统一信封 {ok, service, status, data: <API 原始响应>}:
|
|
5
|
+
* tavily/exa 业务字段在 data 下(results/answer),openai-chat 回答在
|
|
6
|
+
* data.answer。按 selectedService 归一;字段缺失降级而非报错(对 skill
|
|
7
|
+
* 脚本版本漂移保持容差)。
|
|
7
8
|
* @module web-search-services/normalize
|
|
8
9
|
*/
|
|
9
10
|
import type { WebSearchResult, WebSearchSource } from '@deepseek-ai/dsh-web'
|
|
@@ -24,6 +25,14 @@ function asArray(value: unknown): unknown[] {
|
|
|
24
25
|
return Array.isArray(value) ? value : []
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/** snippet 截断上限:官方 deepseek provider 的摘录是短引文;Tavily content /
|
|
29
|
+
* Exa highlights 原始值可达整页文本,直接透传会撑爆工具结果上下文。 */
|
|
30
|
+
const SNIPPET_MAX_CHARS = 500
|
|
31
|
+
|
|
32
|
+
export function truncateSnippet(text: string): string {
|
|
33
|
+
return text.length <= SNIPPET_MAX_CHARS ? text : `${text.slice(0, SNIPPET_MAX_CHARS)}…`
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
function makeSource(
|
|
28
37
|
url: string,
|
|
29
38
|
title?: string,
|
|
@@ -38,6 +47,13 @@ function makeSource(
|
|
|
38
47
|
}
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
/** skill 各服务 request() 统一信封 {ok, service, status, data: <API 响应>};
|
|
51
|
+
* 取 data 为业务 payload,data 缺失/空对象时回退顶层(旧形状容差)。 */
|
|
52
|
+
function payloadOf(raw: JsonObject): JsonObject {
|
|
53
|
+
const data = asObject(raw.data)
|
|
54
|
+
return Object.keys(data).length > 0 ? data : raw
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
function finish(content: string | undefined, sources: WebSearchSource[]): WebSearchResult {
|
|
42
58
|
const seen = new Set<string>()
|
|
43
59
|
const deduped = sources.filter((source) => {
|
|
@@ -65,21 +81,24 @@ function sourcesFromResults(
|
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
function normalizeTavily(raw: JsonObject): WebSearchResult {
|
|
68
|
-
const
|
|
84
|
+
const payload = payloadOf(raw)
|
|
85
|
+
const sources = sourcesFromResults(payload.results, (item) => {
|
|
69
86
|
const url = asNonEmptyString(item.url)
|
|
87
|
+
const content = asNonEmptyString(item.content)
|
|
70
88
|
if (url === undefined) return undefined
|
|
71
89
|
return makeSource(
|
|
72
90
|
url,
|
|
73
91
|
asNonEmptyString(item.title),
|
|
74
|
-
|
|
92
|
+
content !== undefined ? truncateSnippet(content) : undefined,
|
|
75
93
|
asNonEmptyString(item.published_date),
|
|
76
94
|
)
|
|
77
95
|
})
|
|
78
|
-
return finish(asNonEmptyString(
|
|
96
|
+
return finish(asNonEmptyString(payload.answer), sources)
|
|
79
97
|
}
|
|
80
98
|
|
|
81
99
|
function normalizeExa(raw: JsonObject): WebSearchResult {
|
|
82
|
-
const
|
|
100
|
+
const payload = payloadOf(raw)
|
|
101
|
+
const sources = sourcesFromResults(payload.results, (item) => {
|
|
83
102
|
const url = asNonEmptyString(item.url) ?? asNonEmptyString(item.id)
|
|
84
103
|
if (url === undefined) return undefined
|
|
85
104
|
const highlights = asArray(item.highlights)
|
|
@@ -88,7 +107,7 @@ function normalizeExa(raw: JsonObject): WebSearchResult {
|
|
|
88
107
|
return makeSource(
|
|
89
108
|
url,
|
|
90
109
|
asNonEmptyString(item.title),
|
|
91
|
-
highlights.length > 0 ? highlights.join(' ') : undefined,
|
|
110
|
+
highlights.length > 0 ? truncateSnippet(highlights.join(' ')) : undefined,
|
|
92
111
|
asNonEmptyString(item.publishedDate),
|
|
93
112
|
)
|
|
94
113
|
})
|