@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 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 sources = sourcesFromResults(raw.results, (item) => {
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), asNonEmptyString(item.content), asNonEmptyString(item.published_date));
168
+ return makeSource(url, asNonEmptyString(item.title), content !== void 0 ? truncateSnippet(content) : void 0, asNonEmptyString(item.published_date));
155
169
  });
156
- return finish(asNonEmptyString(raw.answer), sources);
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.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 各后端 payload 形状不同:tavily/exa 是原生 API 形状(results[]),
5
- * openai-chat 是生成式回答(data.answer)。按 selectedService 归一;
6
- * 字段缺失降级而非报错(对 skill 脚本版本漂移保持容差)。
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 sources = sourcesFromResults(raw.results, (item) => {
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
- asNonEmptyString(item.content),
92
+ content !== undefined ? truncateSnippet(content) : undefined,
75
93
  asNonEmptyString(item.published_date),
76
94
  )
77
95
  })
78
- return finish(asNonEmptyString(raw.answer), sources)
96
+ return finish(asNonEmptyString(payload.answer), sources)
79
97
  }
80
98
 
81
99
  function normalizeExa(raw: JsonObject): WebSearchResult {
82
- const sources = sourcesFromResults(raw.results, (item) => {
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
  })