@klarkxy/dsh-zhihu 0.1.10 → 0.2.0
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/README.md +3 -7
- package/docs/README.zh-CN.md +3 -7
- package/lib/client.js +1 -162
- package/lib/index.d.ts +1 -1
- package/lib/index.js +77 -107
- package/lib/index.js.map +1 -1
- package/lib/open-platform.js +8 -8
- package/lib/open-platform.js.map +1 -1
- package/package.json +14 -13
package/README.md
CHANGED
|
@@ -8,17 +8,13 @@ Requires Node.js ≥22 and DSH `0.1.7-rc.2`. No repository build is needed.
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
npm install @klarkxy/dsh-zhihu
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Then load it:
|
|
11
|
+
Install the published package into the target profile:
|
|
16
12
|
|
|
17
13
|
```sh
|
|
18
14
|
dsh plugin --profile web add @klarkxy/dsh-zhihu
|
|
19
15
|
```
|
|
20
16
|
|
|
21
|
-
Restart DSH Web, open **Plugins → @klarkxy/dsh-zhihu**, and enter your Access Secret (DSH credential `ZHIHU_ACCESS_TOKEN`). Use the full scoped name; the unscoped `dsh-zhihu` package belongs to another maintainer.
|
|
17
|
+
Replace `web` with your profile name. Restart DSH Web, open **Plugins → @klarkxy/dsh-zhihu**, and enter your Access Secret (DSH credential `ZHIHU_ACCESS_TOKEN`). Use the full scoped name; the unscoped `dsh-zhihu` package belongs to another maintainer. To uninstall, run `dsh plugin --profile web remove @klarkxy/dsh-zhihu`.
|
|
22
18
|
|
|
23
19
|
## Use
|
|
24
20
|
|
|
@@ -72,7 +68,7 @@ Business errors retain their upstream message and code, including on HTTP error
|
|
|
72
68
|
|
|
73
69
|
## Works with the web search manager
|
|
74
70
|
|
|
75
|
-
When [@klarkxy/dsh-web-search-manager](https://www.npmjs.com/package/@klarkxy/dsh-web-search-manager) is
|
|
71
|
+
When this profile provides `ctx.web`, the plugin registers **Zhihu global search** directly with the host, independently of the search manager. If [@klarkxy/dsh-web-search-manager](https://www.npmjs.com/package/@klarkxy/dsh-web-search-manager) is installed, it discovers the provider for enablement and ordering. Configure the Access Secret in Zhihu settings; the Zhihu plugin resolves it per request and refreshes local availability after credential changes. The dedicated Zhihu tools work independently.
|
|
76
72
|
|
|
77
73
|
## Boundaries and limits
|
|
78
74
|
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -8,17 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
## 安装
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
npm install @klarkxy/dsh-zhihu
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
然后加载:
|
|
11
|
+
把已发布包装进目标 profile:
|
|
16
12
|
|
|
17
13
|
```sh
|
|
18
14
|
dsh plugin --profile web add @klarkxy/dsh-zhihu
|
|
19
15
|
```
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
把 `web` 换成你的 profile 名。重启 DSH Web,从「插件」打开「知乎(@klarkxy/dsh-zhihu)」填写 Access Secret(DSH 凭据 `ZHIHU_ACCESS_TOKEN`)。请使用完整 scoped 包名;无 scope 的 `dsh-zhihu` 属于其他维护者。卸载用 `dsh plugin --profile web remove @klarkxy/dsh-zhihu`。
|
|
22
18
|
|
|
23
19
|
## 使用
|
|
24
20
|
|
|
@@ -72,7 +68,7 @@ RPC 还接受主库风格的下划线别名:`question_recommendations`、`ques
|
|
|
72
68
|
|
|
73
69
|
## 与网络搜索联动
|
|
74
70
|
|
|
75
|
-
同一 profile
|
|
71
|
+
同一 profile 提供 `ctx.web` 时,本插件直接向宿主注册「知乎全网搜索」,无需安装搜索管理器。安装 [@klarkxy/dsh-web-search-manager](https://www.npmjs.com/package/@klarkxy/dsh-web-search-manager) 后,管理器会自动发现它,可在那里启用并排序。Access Secret 仍在「知乎资料」中配置,每次请求由知乎插件读取,凭据变更会刷新本地可用状态。专用知乎工具仍可独立使用。
|
|
76
72
|
|
|
77
73
|
## 边界与限制
|
|
78
74
|
|
package/lib/client.js
CHANGED
|
@@ -1,168 +1,7 @@
|
|
|
1
|
-
function __createDshNodeShims() {
|
|
2
|
-
class EventEmitter {
|
|
3
|
-
constructor() {
|
|
4
|
-
this._e = Object.create(null)
|
|
5
|
-
}
|
|
6
|
-
on(type, fn) {
|
|
7
|
-
(this._e[type] ||= []).push(fn)
|
|
8
|
-
return this
|
|
9
|
-
}
|
|
10
|
-
addListener(type, fn) {
|
|
11
|
-
return this.on(type, fn)
|
|
12
|
-
}
|
|
13
|
-
once(type, fn) {
|
|
14
|
-
const wrap = (...args) => {
|
|
15
|
-
this.off(type, wrap)
|
|
16
|
-
fn(...args)
|
|
17
|
-
}
|
|
18
|
-
return this.on(type, wrap)
|
|
19
|
-
}
|
|
20
|
-
off(type, fn) {
|
|
21
|
-
const list = this._e[type]
|
|
22
|
-
if (list) this._e[type] = list.filter((item) => item !== fn)
|
|
23
|
-
return this
|
|
24
|
-
}
|
|
25
|
-
removeListener(type, fn) {
|
|
26
|
-
return this.off(type, fn)
|
|
27
|
-
}
|
|
28
|
-
emit(type, ...args) {
|
|
29
|
-
const list = this._e[type]
|
|
30
|
-
if (!list) return false
|
|
31
|
-
for (const fn of list.slice()) fn(...args)
|
|
32
|
-
return true
|
|
33
|
-
}
|
|
34
|
-
removeAllListeners(type) {
|
|
35
|
-
if (type) delete this._e[type]
|
|
36
|
-
else this._e = Object.create(null)
|
|
37
|
-
return this
|
|
38
|
-
}
|
|
39
|
-
listeners(type) {
|
|
40
|
-
return (this._e[type] || []).slice()
|
|
41
|
-
}
|
|
42
|
-
setMaxListeners() {
|
|
43
|
-
return this
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
class Buffer extends Uint8Array {
|
|
48
|
-
static from(value, encodingOrOffset, length) {
|
|
49
|
-
if (typeof value === 'string') return new Buffer(new TextEncoder().encode(value))
|
|
50
|
-
if (value instanceof ArrayBuffer) return new Buffer(new Uint8Array(value, encodingOrOffset, length))
|
|
51
|
-
if (ArrayBuffer.isView(value)) return new Buffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
|
|
52
|
-
if (Array.isArray(value) || value && typeof value.length === 'number') return new Buffer(Uint8Array.from(value))
|
|
53
|
-
return new Buffer(0)
|
|
54
|
-
}
|
|
55
|
-
static alloc(size, fill) {
|
|
56
|
-
const buf = new Buffer(size)
|
|
57
|
-
if (fill !== undefined && fill !== 0) {
|
|
58
|
-
if (typeof fill === 'string') {
|
|
59
|
-
const bytes = new TextEncoder().encode(fill)
|
|
60
|
-
for (let i = 0; i < size; i += 1) buf[i] = bytes[i % bytes.length]
|
|
61
|
-
} else buf.fill(fill)
|
|
62
|
-
}
|
|
63
|
-
return buf
|
|
64
|
-
}
|
|
65
|
-
static allocUnsafe(size) {
|
|
66
|
-
return new Buffer(size)
|
|
67
|
-
}
|
|
68
|
-
static allocUnsafeSlow(size) {
|
|
69
|
-
return new Buffer(size)
|
|
70
|
-
}
|
|
71
|
-
static concat(list, totalLength) {
|
|
72
|
-
const length = totalLength ?? list.reduce((sum, item) => sum + item.length, 0)
|
|
73
|
-
const out = new Buffer(length)
|
|
74
|
-
let offset = 0
|
|
75
|
-
for (const item of list) {
|
|
76
|
-
out.set(item, offset)
|
|
77
|
-
offset += item.length
|
|
78
|
-
}
|
|
79
|
-
return out
|
|
80
|
-
}
|
|
81
|
-
static isBuffer(value) {
|
|
82
|
-
return value instanceof Buffer
|
|
83
|
-
}
|
|
84
|
-
static byteLength(value) {
|
|
85
|
-
if (typeof value === 'string') return new TextEncoder().encode(value).length
|
|
86
|
-
return value?.byteLength ?? value?.length ?? 0
|
|
87
|
-
}
|
|
88
|
-
static isEncoding(encoding) {
|
|
89
|
-
return /^(utf8|utf-8|ascii|latin1|binary|hex|base64|base64url)$/i.test(String(encoding || ''))
|
|
90
|
-
}
|
|
91
|
-
toString(encoding) {
|
|
92
|
-
if (encoding === 'hex') return [...this].map((byte) => byte.toString(16).padStart(2, '0')).join('')
|
|
93
|
-
if (encoding === 'base64') {
|
|
94
|
-
let binary = ''
|
|
95
|
-
this.forEach((byte) => {
|
|
96
|
-
binary += String.fromCharCode(byte)
|
|
97
|
-
})
|
|
98
|
-
return btoa(binary)
|
|
99
|
-
}
|
|
100
|
-
return new TextDecoder().decode(this)
|
|
101
|
-
}
|
|
102
|
-
slice(start, end) {
|
|
103
|
-
return Buffer.from(super.subarray(start, end))
|
|
104
|
-
}
|
|
105
|
-
copy(target, targetStart = 0, start = 0, end = this.length) {
|
|
106
|
-
const sliced = this.subarray(start, end)
|
|
107
|
-
target.set(sliced, targetStart)
|
|
108
|
-
return sliced.length
|
|
109
|
-
}
|
|
110
|
-
write(string, offset = 0) {
|
|
111
|
-
const bytes = new TextEncoder().encode(string)
|
|
112
|
-
const written = Math.min(bytes.length, this.length - offset)
|
|
113
|
-
this.set(bytes.subarray(0, written), offset)
|
|
114
|
-
return written
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function inherits(ctor, superCtor) {
|
|
119
|
-
ctor.super_ = superCtor
|
|
120
|
-
ctor.prototype = Object.create(superCtor.prototype, {
|
|
121
|
-
constructor: { value: ctor, writable: true, configurable: true },
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!globalThis.Buffer) globalThis.Buffer = Buffer
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
buffer: { Buffer, SlowBuffer: Buffer, INSPECT_MAX_BYTES: 50, kMaxLength: 0x7fffffff },
|
|
129
|
-
stream: {
|
|
130
|
-
Stream: EventEmitter,
|
|
131
|
-
Readable: EventEmitter,
|
|
132
|
-
Writable: EventEmitter,
|
|
133
|
-
Duplex: EventEmitter,
|
|
134
|
-
Transform: EventEmitter,
|
|
135
|
-
PassThrough: EventEmitter,
|
|
136
|
-
},
|
|
137
|
-
events: { EventEmitter },
|
|
138
|
-
util: {
|
|
139
|
-
deprecate(fn) {
|
|
140
|
-
return fn
|
|
141
|
-
},
|
|
142
|
-
inherits,
|
|
143
|
-
inspect() {
|
|
144
|
-
return ''
|
|
145
|
-
},
|
|
146
|
-
format(value) {
|
|
147
|
-
return String(value)
|
|
148
|
-
},
|
|
149
|
-
types: { isBuffer: Buffer.isBuffer },
|
|
150
|
-
},
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
1
|
window.__ModuleLoader__.load({
|
|
155
2
|
id: "@klarkxy/dsh-zhihu",
|
|
156
3
|
factory: (dshRequire) => {
|
|
157
|
-
var
|
|
158
|
-
var process = globalThis.process || { env: {}, nextTick: function (fn) { var args = [].slice.call(arguments, 1); queueMicrotask(function () { fn.apply(null, args); }); } };
|
|
159
|
-
var require = function (id) {
|
|
160
|
-
if (id === 'buffer' || id === 'stream' || id === 'util' || id === 'events') {
|
|
161
|
-
if (!__nodeShims) __nodeShims = __createDshNodeShims();
|
|
162
|
-
return __nodeShims[id];
|
|
163
|
-
}
|
|
164
|
-
return dshRequire(id);
|
|
165
|
-
};
|
|
4
|
+
var require = dshRequire;
|
|
166
5
|
var module = { exports: {} };
|
|
167
6
|
var exports = module.exports;
|
|
168
7
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
package/lib/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ type ZhihuSearchFetcher = (input: string, init?: {
|
|
|
17
17
|
json(): Promise<unknown>;
|
|
18
18
|
}>;
|
|
19
19
|
type ZhihuSearchResolveOptions = {
|
|
20
|
-
/**
|
|
20
|
+
/** Authoritative Access Secret resolver; absence/invalid values never fall back to env or CLI files. */
|
|
21
21
|
resolveCredential?: () => Promise<string | undefined>;
|
|
22
22
|
};
|
|
23
23
|
/** Metering payload emitted after each zhihu tool execution. */
|
package/lib/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { S as zhihuFetchJson, _ as executeZhihuSearch, a as ZHIHU_ASK_DEFAULT_MO
|
|
|
2
2
|
import { ZHIHU_CREDENTIAL_REF, ZHIHU_RPC_CHANNEL, ZHIHU_SEARCH_EVENT } from "./contracts.js";
|
|
3
3
|
import { createZhihuUsageRecorder, resolveDays, zhihuUsageDomainSpec } from "./usage.js";
|
|
4
4
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
5
|
+
import { registerHostRpcTransport, sendHostRpcResponse } from "@klarkxy/dsh-plugin-kit/host-rpc";
|
|
5
6
|
/** 设置页 RPC 走 JSON/base64 传输,限额压到 20MB(平台上限 100MB)。 */
|
|
6
7
|
const ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES = 20971520;
|
|
7
8
|
const KNOWLEDGE_BASES_PATH = "/api/v1/knowledge/bases";
|
|
@@ -104,105 +105,47 @@ async function uploadZhihuKnowledgeFile(input, options = {}) {
|
|
|
104
105
|
const CHANNEL_PATTERN = /^\/[A-Za-z0-9._~-]+$/;
|
|
105
106
|
const ENDPOINT_SEGMENT = /^[A-Za-z0-9_$.-]+$/;
|
|
106
107
|
const MAX_BODY_BYTES = 67108864;
|
|
107
|
-
|
|
108
|
+
function isEndpoint(endpoint) {
|
|
109
|
+
return endpoint.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".." && ENDPOINT_SEGMENT.test(segment));
|
|
110
|
+
}
|
|
111
|
+
/** Zhihu keeps its gateway-compatible bad-request envelope and upload allowance. */
|
|
108
112
|
function registerHostRpc(ctx, channel, handler) {
|
|
109
113
|
if (!CHANNEL_PATTERN.test(channel) || channel === "/api") throw new Error(`invalid RPC channel ${JSON.stringify(channel)}`);
|
|
110
|
-
return ctx
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
function endpointFromRpcPath(channel, pathname) {
|
|
117
|
-
if (!pathname.startsWith(`${channel}/`)) return void 0;
|
|
118
|
-
const endpoint = pathname.slice(channel.length + 1);
|
|
119
|
-
if (endpoint.split("/").some((segment) => segment === "" || segment === "." || segment === ".." || !ENDPOINT_SEGMENT.test(segment))) return;
|
|
120
|
-
return endpoint;
|
|
121
|
-
}
|
|
122
|
-
async function dispatchHostRpc(ctx, channel, handler, req, res) {
|
|
123
|
-
if (typeof ctx.connection.requestRejection !== "function") {
|
|
124
|
-
res.writeHead(503);
|
|
125
|
-
res.end("host authorization policy is unavailable");
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const rejection = ctx.connection.requestRejection(req);
|
|
129
|
-
if (rejection !== void 0) {
|
|
130
|
-
res.writeHead(rejection);
|
|
131
|
-
res.end(rejection === 401 ? "unauthorized" : "forbidden");
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const abort = new AbortController();
|
|
135
|
-
res.on("close", () => {
|
|
136
|
-
if (!res.writableEnded) abort.abort();
|
|
137
|
-
});
|
|
138
|
-
const endpoint = endpointFromRpcPath(channel, new URL(req.url ?? "/", "http://dsh.internal").pathname);
|
|
139
|
-
if (req.method !== "POST" || endpoint === void 0) {
|
|
140
|
-
res.writeHead(404);
|
|
141
|
-
res.end("not found");
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (String(req.headers["content-type"] ?? "").split(";", 1)[0]?.trim().toLowerCase() !== "application/json") {
|
|
145
|
-
res.writeHead(415);
|
|
146
|
-
res.end("content type must be application/json");
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const chunks = [];
|
|
150
|
-
let received = 0;
|
|
151
|
-
for await (const chunk of req) {
|
|
152
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
153
|
-
received += buffer.byteLength;
|
|
154
|
-
if (received > MAX_BODY_BYTES) {
|
|
155
|
-
res.writeHead(413, { connection: "close" });
|
|
156
|
-
res.end();
|
|
157
|
-
req.destroy();
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
chunks.push(buffer);
|
|
161
|
-
}
|
|
162
|
-
let body;
|
|
163
|
-
try {
|
|
164
|
-
body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "null");
|
|
165
|
-
} catch {
|
|
166
|
-
res.writeHead(400);
|
|
167
|
-
res.end("body is not JSON");
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
const record = body && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
171
|
-
const rpcId = typeof record.rpcId === "string" ? record.rpcId : "invalid-request";
|
|
172
|
-
if (record.type !== "client-request" || record.method !== endpoint) {
|
|
173
|
-
res.writeHead(200, { "content-type": "application/json" });
|
|
174
|
-
res.end(JSON.stringify({
|
|
175
|
-
type: "server-response",
|
|
176
|
-
rpcId,
|
|
177
|
-
result: {
|
|
114
|
+
return registerHostRpcTransport(ctx, channel, async (endpoint, body, signal, res) => {
|
|
115
|
+
const record = body && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
116
|
+
const rpcId = typeof record.rpcId === "string" ? record.rpcId : "invalid-request";
|
|
117
|
+
if (record.type !== "client-request" || record.method !== endpoint) {
|
|
118
|
+
sendHostRpcResponse(res, rpcId, {
|
|
178
119
|
ok: false,
|
|
179
120
|
error: {
|
|
180
121
|
code: "gateway/bad-request",
|
|
181
122
|
message: record.method !== endpoint ? `method ${JSON.stringify(record.method)} does not match endpoint ${JSON.stringify(endpoint)}` : "invalid client-request message",
|
|
182
123
|
details: { issues: [] }
|
|
183
124
|
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
125
|
+
}, false);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
sendHostRpcResponse(res, rpcId, await handler(endpoint, record.payload, signal), false);
|
|
129
|
+
}, {
|
|
130
|
+
maxBodyBytes: MAX_BODY_BYTES,
|
|
131
|
+
isEndpoint,
|
|
132
|
+
caseInsensitiveContentType: true,
|
|
133
|
+
emptyBodyAsNull: true,
|
|
134
|
+
errorBody: (status, error) => {
|
|
135
|
+
if (status === 503) return "host authorization policy is unavailable";
|
|
136
|
+
if (status === 401) return "unauthorized";
|
|
137
|
+
if (status === 403) return "forbidden";
|
|
138
|
+
if (status === 404) return "not found";
|
|
139
|
+
if (status === 415) return "content type must be application/json";
|
|
140
|
+
if (status === 400) return "body is not JSON";
|
|
141
|
+
if (status === 500) return `handler failure: ${String(error)}`;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
200
144
|
}
|
|
201
145
|
//#endregion
|
|
202
146
|
//#region src/web-search-provider.ts
|
|
203
147
|
/**
|
|
204
|
-
*
|
|
205
|
-
* provides `webSearchManager`; Zhihu still works without that plugin.
|
|
148
|
+
* Native ctx.web backend. Configuration and credentials belong to Zhihu.
|
|
206
149
|
* Official model tool remains `web_search`. Dedicated `zhihu_global_search` is unchanged.
|
|
207
150
|
*/
|
|
208
151
|
const ZHIHU_WEB_SEARCH_ID = "zhihu-global";
|
|
@@ -216,7 +159,8 @@ const ZHIHU_WEB_SEARCH_DESCRIPTOR = {
|
|
|
216
159
|
billing: "request",
|
|
217
160
|
pricing: "注册可获 5,000 次/天试用额度;超额价格需向平台咨询。",
|
|
218
161
|
pricingUrl: "https://developer.zhihu.com/",
|
|
219
|
-
signupUrl: "https://developer.zhihu.com"
|
|
162
|
+
signupUrl: "https://developer.zhihu.com",
|
|
163
|
+
configurationOwner: "@klarkxy/dsh-zhihu"
|
|
220
164
|
};
|
|
221
165
|
function sourceFrom(url, title, snippet) {
|
|
222
166
|
let parsed;
|
|
@@ -232,7 +176,7 @@ function sourceFrom(url, title, snippet) {
|
|
|
232
176
|
...snippet ? { snippet: snippet.slice(0, 8e3) } : {}
|
|
233
177
|
};
|
|
234
178
|
}
|
|
235
|
-
function
|
|
179
|
+
function isWebRegistry(value) {
|
|
236
180
|
return !!value && typeof value === "object" && typeof value.registerSearchProvider === "function";
|
|
237
181
|
}
|
|
238
182
|
function createZhihuWebSearchProvider(options) {
|
|
@@ -274,31 +218,57 @@ function createZhihuWebSearchProvider(options) {
|
|
|
274
218
|
}
|
|
275
219
|
};
|
|
276
220
|
}
|
|
277
|
-
function registerZhihuGlobalSearchProvider(
|
|
278
|
-
return
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
221
|
+
function registerZhihuGlobalSearchProvider(web, service, configured) {
|
|
222
|
+
return web.registerSearchProvider({
|
|
223
|
+
id: ZHIHU_WEB_SEARCH_ID,
|
|
224
|
+
dshWebManagement: ZHIHU_WEB_SEARCH_DESCRIPTOR,
|
|
225
|
+
available: configured,
|
|
226
|
+
search: (request, signal) => service.run(async (combined) => {
|
|
227
|
+
combined.throwIfAborted();
|
|
228
|
+
const apiKey = await service.toolOptions.resolveCredential?.();
|
|
229
|
+
combined.throwIfAborted();
|
|
230
|
+
return createZhihuWebSearchProvider({
|
|
231
|
+
...service.toolOptions,
|
|
232
|
+
apiKey
|
|
233
|
+
}).search(request, combined);
|
|
234
|
+
}, signal ?? new AbortController().signal)
|
|
289
235
|
});
|
|
290
236
|
}
|
|
291
|
-
/** Wait for
|
|
237
|
+
/** Wait for the native web service; no manager dependency and no network probe. */
|
|
292
238
|
function bindZhihuWebSearch(ctx, service) {
|
|
293
|
-
ctx.inject(["
|
|
294
|
-
let
|
|
239
|
+
ctx.inject(["web"], (inner) => {
|
|
240
|
+
let web;
|
|
295
241
|
try {
|
|
296
|
-
|
|
242
|
+
web = inner.get("web");
|
|
297
243
|
} catch {
|
|
298
244
|
return;
|
|
299
245
|
}
|
|
300
|
-
if (!
|
|
301
|
-
|
|
246
|
+
if (!isWebRegistry(web)) return;
|
|
247
|
+
let configured = false, alive = true, revision = 0;
|
|
248
|
+
const refresh = async () => {
|
|
249
|
+
const current = ++revision;
|
|
250
|
+
let ready = false;
|
|
251
|
+
try {
|
|
252
|
+
ready = Boolean((await service.toolOptions.resolveCredential?.())?.trim());
|
|
253
|
+
} catch {}
|
|
254
|
+
if (!alive || current !== revision) return;
|
|
255
|
+
if (configured !== ready) {
|
|
256
|
+
configured = ready;
|
|
257
|
+
inner.emit?.("web/provider-availability-updated", "search", ZHIHU_WEB_SEARCH_ID);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
const off = registerZhihuGlobalSearchProvider(web, service, () => configured);
|
|
261
|
+
const unlisten = inner.on?.("credentials/reference-updated", (ref) => {
|
|
262
|
+
if (ref === "ZHIHU_ACCESS_TOKEN") refresh();
|
|
263
|
+
});
|
|
264
|
+
refresh();
|
|
265
|
+
return () => {
|
|
266
|
+
alive = false;
|
|
267
|
+
revision++;
|
|
268
|
+
configured = false;
|
|
269
|
+
unlisten?.();
|
|
270
|
+
off();
|
|
271
|
+
};
|
|
302
272
|
});
|
|
303
273
|
}
|
|
304
274
|
//#endregion
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/zhihu-knowledge.ts","../src/host-rpc.ts","../src/web-search-provider.ts","../src/index.ts"],"sourcesContent":["/**\n * 知乎知识库的管理侧能力:列知识库、上传文件。仅供设置页 RPC 使用,\n * 不暴露为模型工具——上传会把文件送入知乎云端,必须是用户在界面上显式发起。\n *\n * 端点(来源:github.com/klarkxy/zhihu-search,src/zhihu_search/upstream/http_client.py):\n * - 列表:GET /api/v1/knowledge/bases?Scope=all|created|subscribed\n * - 上传:POST /api/v1/knowledge/files(multipart: File + 可选 KnowledgeBaseID),同步解析\n */\nimport {\n parseZhihuEnvelope,\n zhihuFetchJson,\n ZhihuSearchError,\n type ZhihuClientOptions,\n} from './zhihu-client.ts'\n\nexport const ZHIHU_KNOWLEDGE_LIST_VERSION = 1\nexport const ZHIHU_KNOWLEDGE_UPLOAD_VERSION = 1\n/** 设置页 RPC 走 JSON/base64 传输,限额压到 20MB(平台上限 100MB)。 */\nexport const ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES = 20 * 1024 * 1024\nexport const ZHIHU_KNOWLEDGE_UPLOAD_TIMEOUT_MS = 180_000\n\nconst KNOWLEDGE_BASES_PATH = '/api/v1/knowledge/bases'\nconst KNOWLEDGE_FILES_PATH = '/api/v1/knowledge/files'\n\nexport const ZHIHU_KNOWLEDGE_FILE_EXTENSIONS = [\n '.pdf', '.md', '.txt', '.ppt', '.pptx', '.xlsx', '.xls', '.docx', '.doc',\n '.webp', '.png', '.jpg', '.mobi', '.epub', '.csv', '.azw3',\n] as const\n\nconst KNOWLEDGE_CONTENT_TYPES: Record<string, string> = {\n '.pdf': 'application/pdf',\n '.md': 'text/markdown',\n '.txt': 'text/plain',\n '.ppt': 'application/vnd.ms-powerpoint',\n '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n '.xls': 'application/vnd.ms-excel',\n '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n '.doc': 'application/msword',\n '.webp': 'image/webp',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.mobi': 'application/x-mobipocket-ebook',\n '.epub': 'application/epub+zip',\n '.csv': 'text/csv',\n '.azw3': 'application/vnd.amazon.ebook',\n}\n\nexport type ZhihuKnowledgeBase = {\n id: string\n name: string\n relation: string\n visibility: string\n isDefault: boolean\n contentCount: number\n}\n\nexport type ZhihuKnowledgeBaseList = {\n version: typeof ZHIHU_KNOWLEDGE_LIST_VERSION\n bases: ZhihuKnowledgeBase[]\n}\n\nfunction normalizeBase(raw: Record<string, unknown>): ZhihuKnowledgeBase {\n return {\n id: typeof raw.KnowledgeBaseID === 'string' ? raw.KnowledgeBaseID : '',\n name: typeof raw.Name === 'string' && raw.Name.length > 0 ? raw.Name : '(未命名知识库)',\n relation: typeof raw.Relation === 'string' ? raw.Relation : '',\n visibility: typeof raw.Visibility === 'string' ? raw.Visibility : '',\n isDefault: raw.IsDefault === true,\n contentCount: typeof raw.ContentCount === 'number' ? raw.ContentCount : 0,\n }\n}\n\nexport async function listZhihuKnowledgeBases(options: ZhihuClientOptions = {}): Promise<ZhihuKnowledgeBaseList> {\n const body = await zhihuFetchJson(KNOWLEDGE_BASES_PATH, {\n params: { Scope: 'all' },\n }, '知乎知识库列表', options)\n const data = parseZhihuEnvelope(body) as { Items?: unknown }\n const items = Array.isArray(data.Items) ? data.Items : []\n return {\n version: ZHIHU_KNOWLEDGE_LIST_VERSION,\n bases: items\n .filter((item): item is Record<string, unknown> => !!item && typeof item === 'object' && !Array.isArray(item))\n .map(normalizeBase)\n .filter((base) => base.id.length > 0),\n }\n}\n\nexport type ZhihuKnowledgeUploadInput = {\n fileName: string\n data: Uint8Array\n /** 指定目标知识库;缺省进默认库。 */\n knowledgeBaseId?: string\n}\n\nexport type ZhihuKnowledgeUpload = {\n version: typeof ZHIHU_KNOWLEDGE_UPLOAD_VERSION\n knowledgeBaseId: string\n recallContentId: string\n fileName: string\n fileSize: number\n title: string\n abstract: string\n originUrl: string\n}\n\n/** 校验文件名与大小,返回小写扩展名。 */\nexport function checkKnowledgeFile(fileName: string, size: number): string {\n const cleaned = fileName.trim()\n if (!cleaned) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能为空。')\n // 去掉路径成分,只保留文件名\n const base = cleaned.split(/[\\\\/]/).pop() ?? cleaned\n for (const ch of base) {\n const code = ch.codePointAt(0) ?? 0\n if (code < 32 || code === 127) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能包含控制字符。')\n }\n if (new TextEncoder().encode(base).length > 255) {\n throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能超过 255 个 UTF-8 字节。')\n }\n if (size <= 0) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件内容不能为空。')\n if (size > ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES) {\n throw new ZhihuSearchError('BAD_RESPONSE', `知识库文件大小不能超过 ${ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES / 1024 / 1024}MB。`)\n }\n const dot = base.lastIndexOf('.')\n const ext = dot >= 0 ? base.slice(dot).toLowerCase() : ''\n if (!(ZHIHU_KNOWLEDGE_FILE_EXTENSIONS as readonly string[]).includes(ext)) {\n throw new ZhihuSearchError('BAD_RESPONSE', `知识库上传仅支持 ${ZHIHU_KNOWLEDGE_FILE_EXTENSIONS.join('、')}。`)\n }\n return ext\n}\n\nexport async function uploadZhihuKnowledgeFile(\n input: ZhihuKnowledgeUploadInput,\n options: ZhihuClientOptions = {},\n): Promise<ZhihuKnowledgeUpload> {\n const cleaned = (input.fileName.trim().split(/[\\\\/]/).pop() ?? input.fileName.trim())\n const ext = checkKnowledgeFile(input.fileName, input.data.byteLength)\n const form = new FormData()\n if (input.knowledgeBaseId) form.set('KnowledgeBaseID', input.knowledgeBaseId)\n form.set('File', new Blob([input.data.buffer as ArrayBuffer], { type: KNOWLEDGE_CONTENT_TYPES[ext] ?? 'application/octet-stream' }), cleaned)\n const body = await zhihuFetchJson(KNOWLEDGE_FILES_PATH, {\n formData: form,\n }, '知乎知识库上传', { ...options, timeoutMs: options.timeoutMs ?? ZHIHU_KNOWLEDGE_UPLOAD_TIMEOUT_MS })\n const data = parseZhihuEnvelope(body) as Record<string, unknown>\n return {\n version: ZHIHU_KNOWLEDGE_UPLOAD_VERSION,\n knowledgeBaseId: typeof data.KnowledgeBaseID === 'string' ? data.KnowledgeBaseID : '',\n recallContentId: typeof data.RecallContentID === 'string' ? data.RecallContentID : '',\n fileName: typeof data.FileName === 'string' ? data.FileName : cleaned,\n fileSize: typeof data.FileSize === 'number' ? data.FileSize : input.data.byteLength,\n title: typeof data.Title === 'string' ? data.Title : '',\n abstract: typeof data.Abstract === 'string' ? data.Abstract : '',\n originUrl: typeof data.OriginUrl === 'string' ? data.OriginUrl : '',\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\n\nconst CHANNEL_PATTERN = /^\\/[A-Za-z0-9._~-]+$/\nconst ENDPOINT_SEGMENT = /^[A-Za-z0-9_$.-]+$/\nconst MAX_BODY_BYTES = 64 * 1024 * 1024\n\nexport type HostRpcHandler = (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<unknown>\n\nexport type HostRpcContext = {\n webServer: {\n register: (route: {\n kind: 'prefix'\n path: string\n handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>\n }) => () => void\n }\n connection: {\n requestRejection?: (request: IncomingMessage) => number | undefined\n }\n}\n\n/** Standalone copy of manuscript Host RPC registration; this package cannot depend on manuscript. */\nexport function registerHostRpc(ctx: HostRpcContext, channel: string, handler: HostRpcHandler): () => void {\n if (!CHANNEL_PATTERN.test(channel) || channel === '/api') {\n throw new Error(`invalid RPC channel ${JSON.stringify(channel)}`)\n }\n return ctx.webServer.register({\n kind: 'prefix',\n path: channel,\n handler: (req, res) => dispatchHostRpc(ctx, channel, handler, req, res),\n })\n}\n\nfunction endpointFromRpcPath(channel: string, pathname: string): string | undefined {\n if (!pathname.startsWith(`${channel}/`)) return undefined\n const endpoint = pathname.slice(channel.length + 1)\n if (endpoint.split('/').some((segment) => segment === '' || segment === '.' || segment === '..' || !ENDPOINT_SEGMENT.test(segment))) {\n return undefined\n }\n return endpoint\n}\n\nasync function dispatchHostRpc(\n ctx: HostRpcContext,\n channel: string,\n handler: HostRpcHandler,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n if (typeof ctx.connection.requestRejection !== 'function') {\n res.writeHead(503)\n res.end('host authorization policy is unavailable')\n return\n }\n const rejection = ctx.connection.requestRejection(req)\n if (rejection !== undefined) {\n res.writeHead(rejection)\n res.end(rejection === 401 ? 'unauthorized' : 'forbidden')\n return\n }\n const abort = new AbortController()\n res.on('close', () => {\n if (!res.writableEnded) abort.abort()\n })\n const url = new URL(req.url ?? '/', 'http://dsh.internal')\n const endpoint = endpointFromRpcPath(channel, url.pathname)\n if (req.method !== 'POST' || endpoint === undefined) {\n res.writeHead(404)\n res.end('not found')\n return\n }\n const contentType = String(req.headers['content-type'] ?? '').split(';', 1)[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n res.writeHead(415)\n res.end('content type must be application/json')\n return\n }\n const chunks: Buffer[] = []\n let received = 0\n for await (const chunk of req) {\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)\n received += buffer.byteLength\n if (received > MAX_BODY_BYTES) {\n res.writeHead(413, { connection: 'close' })\n res.end()\n req.destroy()\n return\n }\n chunks.push(buffer)\n }\n let body: unknown\n try {\n body = JSON.parse(Buffer.concat(chunks).toString('utf8') || 'null')\n } catch {\n res.writeHead(400)\n res.end('body is not JSON')\n return\n }\n const record = body && typeof body === 'object' && !Array.isArray(body) ? body as Record<string, unknown> : {}\n const rpcId = typeof record.rpcId === 'string' ? record.rpcId : 'invalid-request'\n if (record.type !== 'client-request' || record.method !== endpoint) {\n res.writeHead(200, { 'content-type': 'application/json' })\n res.end(JSON.stringify({\n type: 'server-response',\n rpcId,\n result: {\n ok: false,\n error: {\n code: 'gateway/bad-request',\n message: record.method !== endpoint\n ? `method ${JSON.stringify(record.method)} does not match endpoint ${JSON.stringify(endpoint)}`\n : 'invalid client-request message',\n details: { issues: [] },\n },\n },\n }))\n return\n }\n try {\n const result = await handler(endpoint, record.payload, abort.signal)\n res.writeHead(200, { 'content-type': 'application/json' })\n res.end(JSON.stringify({ type: 'server-response', rpcId, result }))\n } catch (error) {\n res.writeHead(500)\n res.end(`handler failure: ${String(error)}`)\n }\n}\n","/**\n * Optional web-search-manager backend. Registers only when this profile already\n * provides `webSearchManager`; Zhihu still works without that plugin.\n * Official model tool remains `web_search`. Dedicated `zhihu_global_search` is unchanged.\n */\nimport { ZHIHU_CREDENTIAL_REF } from './contracts.ts'\nimport { reportExecuted, type ZhihuClientOptions, type ZhihuSearchExecuted } from './zhihu-client.ts'\nimport {\n executeZhihuGlobalSearch,\n ZHIHU_GLOBAL_SEARCH_MAX_COUNT,\n} from './operations.ts'\n\ntype ZhihuWebSearchHost = {\n run<T>(execute: (signal: AbortSignal) => Promise<T>, signal: AbortSignal): Promise<T>\n toolOptions: ZhihuClientOptions & { onExecuted: (event: ZhihuSearchExecuted) => void }\n}\n\nexport const ZHIHU_WEB_SEARCH_ID = 'zhihu-global'\n\nexport const ZHIHU_WEB_SEARCH_DESCRIPTOR = {\n id: ZHIHU_WEB_SEARCH_ID,\n label: '知乎全网搜索',\n description: '融合知乎问答与全网内容,返回可溯源的搜索结果。',\n credentialRef: ZHIHU_CREDENTIAL_REF,\n credentialShared: true,\n credentialHint: '与「知乎资料」共用 Access Secret',\n billing: 'request' as const,\n pricing: '注册可获 5,000 次/天试用额度;超额价格需向平台咨询。',\n pricingUrl: 'https://developer.zhihu.com/',\n signupUrl: 'https://developer.zhihu.com',\n}\n\nexport type ZhihuWebSearchSource = { url: string; title?: string; snippet?: string }\nexport type ZhihuWebSearchResult = { sources: ZhihuWebSearchSource[]; truncated?: boolean }\nexport type ZhihuWebSearchRequest = { query: string; maxResults?: number }\n\nexport type ZhihuWebSearchProvider = {\n readonly id: string\n available(): boolean\n search(request: ZhihuWebSearchRequest, signal?: AbortSignal): Promise<ZhihuWebSearchResult>\n}\n\nexport type ZhihuWebSearchManager = {\n registerSearchProvider(\n descriptor: typeof ZHIHU_WEB_SEARCH_DESCRIPTOR,\n factory: (options: { apiKey?: string; timeoutMs: number }) => ZhihuWebSearchProvider,\n ): () => void\n}\n\nexport type ZhihuWebSearchProviderOptions = ZhihuClientOptions & {\n apiKey?: string\n onExecuted?: (event: ZhihuSearchExecuted) => void\n}\n\nfunction sourceFrom(url: string, title: string, snippet: string): ZhihuWebSearchSource | undefined {\n let parsed: URL\n try { parsed = new URL(url) } catch { return }\n if (!['https:', 'http:'].includes(parsed.protocol) || parsed.username || parsed.password) return\n return {\n url: parsed.href,\n ...(title ? { title: title.slice(0, 1000) } : {}),\n ...(snippet ? { snippet: snippet.slice(0, 8000) } : {}),\n }\n}\n\nfunction isWebSearchManager(value: unknown): value is ZhihuWebSearchManager {\n return !!value && typeof value === 'object' && typeof (value as ZhihuWebSearchManager).registerSearchProvider === 'function'\n}\n\nexport function createZhihuWebSearchProvider(options: ZhihuWebSearchProviderOptions): ZhihuWebSearchProvider {\n const { apiKey, onExecuted, ...client } = options\n return {\n id: ZHIHU_WEB_SEARCH_ID,\n available() { return Boolean(apiKey?.trim()) },\n async search(request, signal) {\n if (!apiKey?.trim()) throw new Error('知乎全网搜索未配置 Access Secret。')\n if (!request.query?.trim() || request.query.length > 4000) throw new Error('查询无效。')\n const count = request.maxResults ?? 5\n if (!Number.isInteger(count) || count < 1 || count > ZHIHU_GLOBAL_SEARCH_MAX_COUNT) {\n throw new Error(`结果数须为 1 至 ${ZHIHU_GLOBAL_SEARCH_MAX_COUNT}。`)\n }\n try {\n signal?.throwIfAborted()\n const result = await executeZhihuGlobalSearch(request.query.trim(), count, 'all', {\n ...client,\n signal,\n env: {},\n resolveCredential: async () => apiKey,\n })\n reportExecuted(onExecuted, { ok: true, results: result.items.length })\n const sources = result.items.flatMap(item => sourceFrom(item.url, item.title, item.summary) ?? [])\n return { sources: sources.slice(0, count), truncated: sources.length > count }\n } catch (error) {\n reportExecuted(onExecuted, { ok: false, results: 0 })\n throw error\n }\n },\n }\n}\n\nexport function registerZhihuGlobalSearchProvider(\n manager: ZhihuWebSearchManager,\n service: ZhihuWebSearchHost,\n): () => void {\n return manager.registerSearchProvider(ZHIHU_WEB_SEARCH_DESCRIPTOR, options => {\n const provider = createZhihuWebSearchProvider({\n ...service.toolOptions,\n apiKey: options.apiKey,\n timeoutMs: options.timeoutMs,\n })\n return {\n id: provider.id,\n available: () => provider.available(),\n search: (request, signal) => service.run(\n combined => provider.search(request, combined),\n signal ?? new AbortController().signal,\n ),\n }\n })\n}\n\n/** Wait for `webSearchManager` if this profile loaded it; no-op otherwise. */\nexport function bindZhihuWebSearch(\n ctx: { inject(deps: readonly string[], apply: (inner: { get(name: string): unknown }) => (() => void) | void): unknown },\n service: ZhihuWebSearchHost,\n): void {\n ctx.inject(['webSearchManager'], (inner) => {\n let manager: unknown\n try { manager = inner.get('webSearchManager') }\n catch { return }\n if (!isWebSearchManager(manager)) return\n return registerZhihuGlobalSearchProvider(manager, service)\n })\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport { credentialRef } from '@deepseek-ai/dsh-credentials'\nimport { executeZhihuSearch, ZHIHU_SEARCH_DEFAULT_COUNT } from './search-api.ts'\nimport {\n executeZhihuGlobalSearch, executeZhihuHotList, executeZhihuAsk, executeZhihuKnowledgeSearch,\n normalizeRecallScopes, ZHIHU_ASK_MODELS, ZHIHU_ASK_DEFAULT_MODEL,\n} from './operations.ts'\nimport { listZhihuKnowledgeBases, uploadZhihuKnowledgeFile, ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES } from './zhihu-knowledge.ts'\nimport { type ZhihuClientOptions, type ZhihuSearchExecuted, ZhihuSearchError } from './zhihu-client.ts'\nimport { createZhihuUsageRecorder, resolveDays, zhihuUsageDomainSpec, type ZhihuUsageRecorder } from './usage.ts'\nimport { ZHIHU_RPC_CHANNEL, ZHIHU_CREDENTIAL_REF, ZHIHU_SEARCH_EVENT, type ZhihuRpcResult } from './contracts.ts'\nimport { registerHostRpc, type HostRpcContext } from './host-rpc.ts'\nimport { bindZhihuWebSearch } from './web-search-provider.ts'\nimport { executeZhihuOpenPlatform, resolveZhihuOpenPlatformOperation, ZHIHU_OPEN_PLATFORM_APIS, zhihuOpenPlatformResultCount } from './open-platform.ts'\n\nexport const name = '@klarkxy/dsh-zhihu'\nexport const inject = ['connection', 'credentials', 'storageDomain', 'webServer'] as const\nexport type ZhihuService = {\n call(endpoint: string, payload: unknown, signal: AbortSignal): Promise<ZhihuRpcResult>\n run<T>(execute: (signal: AbortSignal) => Promise<T>, signal: AbortSignal): Promise<T>\n dispose(): Promise<void>\n usageSummary(days: unknown): Promise<{ days: unknown[] }>\n toolOptions: ZhihuClientOptions & { onExecuted: (event: ZhihuSearchExecuted) => void }\n}\n\ntype Host = Context & HostRpcContext & {\n credentials: { resolve: (ref: ReturnType<typeof credentialRef>) => Promise<{ value: string } | undefined> }\n}\nconst fail = (code: string, message: string, details: Record<string, unknown> = {}): ZhihuRpcResult => ({ ok: false, error: { code, message, details } })\n\n/** One shared service for direct UI RPC and optional Tool wrappers. */\nexport function createZhihuService(options: ZhihuClientOptions, usage: ZhihuUsageRecorder, notify: (event: ZhihuSearchExecuted) => void = () => {}): ZhihuService {\n // Serialize read-modify-write counters; concurrent tools must not lose increments.\n let pendingUsage = Promise.resolve()\n const lifetime = new AbortController()\n const active = new Set<Promise<unknown>>()\n const run: ZhihuService['run'] = (execute, signal) => {\n const combined = AbortSignal.any([signal, lifetime.signal])\n const request = Promise.resolve().then(() => { combined.throwIfAborted(); return execute(combined) })\n active.add(request)\n return request.finally(() => active.delete(request))\n }\n const record = (event: ZhihuSearchExecuted) => {\n pendingUsage = pendingUsage.then(() => usage.record(event)).catch(() => {})\n try { notify(event) } catch { /* observer failure does not change a request */ }\n }\n const service = {\n toolOptions: { ...options, onExecuted: record },\n async usageSummary(days: unknown) { await pendingUsage; return { days: await usage.read(resolveDays(days)) } },\n async call(endpoint: string, payload: unknown, signal: AbortSignal): Promise<ZhihuRpcResult> {\n if (signal.aborted) return fail('cancelled', '请求已取消')\n const body = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload as Record<string, unknown> : {}\n const client = { ...options, signal }\n let counted = false\n const operation = resolveZhihuOpenPlatformOperation(endpoint)\n const quotaId = operation ? ZHIHU_OPEN_PLATFORM_APIS[operation].quotaId : undefined\n const metering = quotaId ? { quotaId } : {}\n try {\n if (endpoint === 'usage.summary') { await pendingUsage; return { ok: true, value: { days: await usage.read(resolveDays(body.days)) } } }\n const query = typeof body.query === 'string' ? body.query : ''\n if (['search', 'global.search', 'ask', 'knowledge.search'].includes(endpoint) && (!query.trim() || query.length > 4000)) {\n return fail('bad-request', '查询不超过 4000 字符')\n }\n let value: unknown\n let results = 0\n counted = true\n if (operation) {\n counted = operation !== 'quota'\n const result = await executeZhihuOpenPlatform(operation, payload === undefined ? {} : payload, client)\n value = result; results = zhihuOpenPlatformResultCount(result)\n } else if (endpoint === 'search') {\n const result = await executeZhihuSearch(query, typeof body.count === 'number' ? body.count : ZHIHU_SEARCH_DEFAULT_COUNT, client)\n value = result; results = result.items.length\n } else if (endpoint === 'global.search') {\n const result = await executeZhihuGlobalSearch(query, typeof body.count === 'number' ? body.count : 10,\n body.searchDb === 'realtime' || body.searchDb === 'static' ? body.searchDb : 'all', client)\n value = result; results = result.items.length\n } else if (endpoint === 'hot.list') {\n const result = await executeZhihuHotList(typeof body.limit === 'number' ? body.limit : 10, client)\n value = result; results = result.items.length\n } else if (endpoint === 'ask') {\n const model = (ZHIHU_ASK_MODELS as readonly unknown[]).includes(body.model) ? body.model as typeof ZHIHU_ASK_MODELS[number] : ZHIHU_ASK_DEFAULT_MODEL\n value = await executeZhihuAsk(query, model, client); results = 1\n } else if (endpoint === 'knowledge.search') {\n const result = await executeZhihuKnowledgeSearch(query, typeof body.limit === 'number' ? body.limit : 5, normalizeRecallScopes(body.recallScopes), client)\n value = result; results = result.items.length\n } else if (endpoint === 'knowledge.bases') {\n const result = await listZhihuKnowledgeBases(client); value = result; results = result.bases.length\n } else if (endpoint === 'knowledge.upload') {\n const contentBase64 = typeof body.contentBase64 === 'string' ? body.contentBase64 : ''\n if (!contentBase64) throw new Error('缺少文件内容。')\n if (contentBase64.length > Math.ceil(ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(contentBase64)) {\n return fail('bad-request', '文件内容无效或超过 20 MB')\n }\n const upload = await uploadZhihuKnowledgeFile({\n fileName: typeof body.fileName === 'string' ? body.fileName : '',\n data: new Uint8Array(Buffer.from(contentBase64, 'base64')),\n knowledgeBaseId: typeof body.knowledgeBaseId === 'string' && body.knowledgeBaseId.trim() ? body.knowledgeBaseId.trim() : undefined,\n }, client)\n value = { upload }; results = 1\n } else {\n counted = false\n return fail('bad-request', `unknown endpoint ${endpoint}`)\n }\n if (counted) record({ ok: true, results, ...metering })\n return { ok: true, value }\n } catch (error) {\n if (counted) record({ ok: false, results: 0, ...metering })\n if (signal.aborted) return fail('cancelled', '请求已取消')\n const code = error instanceof ZhihuSearchError ? error.code.toLowerCase().replaceAll('_', '-') : 'internal'\n const details: Record<string, unknown> = {}\n if (error instanceof ZhihuSearchError) {\n for (const key of ['status', 'upstreamCode', 'retryAfter', 'retryable'] as const) {\n if (error[key] !== undefined) details[key] = error[key]\n }\n }\n return fail(code, error instanceof Error ? error.message : '知乎请求失败', details)\n }\n },\n }\n return {\n ...service,\n run,\n async call(endpoint, payload, signal) {\n if (signal.aborted || lifetime.signal.aborted) return fail('cancelled', '请求已取消')\n try { return await run(combined => service.call(endpoint, payload, combined), signal) }\n catch (error) {\n if (signal.aborted || lifetime.signal.aborted) return fail('cancelled', '请求已取消')\n throw error\n }\n },\n async dispose() {\n lifetime.abort()\n await Promise.allSettled([...active])\n await pendingUsage\n },\n }\n}\n\nexport async function apply(ctx: Context): Promise<void> {\n const host = ctx as Host\n const domain = await ctx.storageDomain.open(zhihuUsageDomainSpec)\n const service = createZhihuService({\n resolveCredential: async () => (await host.credentials.resolve(credentialRef(ZHIHU_CREDENTIAL_REF)))?.value,\n }, createZhihuUsageRecorder(domain.table('daily')), event => {\n const emit = ctx.emit as unknown as (name: string, event: ZhihuSearchExecuted) => void\n emit.call(ctx, ZHIHU_SEARCH_EVENT, event)\n })\n ctx.effect(() => async () => { await service.dispose(); await domain.close() }, 'zhihu.usageDomainClose')\n ctx.provide('zhihu', service)\n ctx.effect(() => registerHostRpc(host, ZHIHU_RPC_CHANNEL, service.call))\n bindZhihuWebSearch(ctx, service)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/zhihu-knowledge.ts","../src/host-rpc.ts","../src/web-search-provider.ts","../src/index.ts"],"sourcesContent":["/**\n * 知乎知识库的管理侧能力:列知识库、上传文件。仅供设置页 RPC 使用,\n * 不暴露为模型工具——上传会把文件送入知乎云端,必须是用户在界面上显式发起。\n *\n * 端点(来源:github.com/klarkxy/zhihu-search,src/zhihu_search/upstream/http_client.py):\n * - 列表:GET /api/v1/knowledge/bases?Scope=all|created|subscribed\n * - 上传:POST /api/v1/knowledge/files(multipart: File + 可选 KnowledgeBaseID),同步解析\n */\nimport {\n parseZhihuEnvelope,\n zhihuFetchJson,\n ZhihuSearchError,\n type ZhihuClientOptions,\n} from './zhihu-client.ts'\n\nexport const ZHIHU_KNOWLEDGE_LIST_VERSION = 1\nexport const ZHIHU_KNOWLEDGE_UPLOAD_VERSION = 1\n/** 设置页 RPC 走 JSON/base64 传输,限额压到 20MB(平台上限 100MB)。 */\nexport const ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES = 20 * 1024 * 1024\nexport const ZHIHU_KNOWLEDGE_UPLOAD_TIMEOUT_MS = 180_000\n\nconst KNOWLEDGE_BASES_PATH = '/api/v1/knowledge/bases'\nconst KNOWLEDGE_FILES_PATH = '/api/v1/knowledge/files'\n\nexport const ZHIHU_KNOWLEDGE_FILE_EXTENSIONS = [\n '.pdf', '.md', '.txt', '.ppt', '.pptx', '.xlsx', '.xls', '.docx', '.doc',\n '.webp', '.png', '.jpg', '.mobi', '.epub', '.csv', '.azw3',\n] as const\n\nconst KNOWLEDGE_CONTENT_TYPES: Record<string, string> = {\n '.pdf': 'application/pdf',\n '.md': 'text/markdown',\n '.txt': 'text/plain',\n '.ppt': 'application/vnd.ms-powerpoint',\n '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n '.xls': 'application/vnd.ms-excel',\n '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n '.doc': 'application/msword',\n '.webp': 'image/webp',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.mobi': 'application/x-mobipocket-ebook',\n '.epub': 'application/epub+zip',\n '.csv': 'text/csv',\n '.azw3': 'application/vnd.amazon.ebook',\n}\n\nexport type ZhihuKnowledgeBase = {\n id: string\n name: string\n relation: string\n visibility: string\n isDefault: boolean\n contentCount: number\n}\n\nexport type ZhihuKnowledgeBaseList = {\n version: typeof ZHIHU_KNOWLEDGE_LIST_VERSION\n bases: ZhihuKnowledgeBase[]\n}\n\nfunction normalizeBase(raw: Record<string, unknown>): ZhihuKnowledgeBase {\n return {\n id: typeof raw.KnowledgeBaseID === 'string' ? raw.KnowledgeBaseID : '',\n name: typeof raw.Name === 'string' && raw.Name.length > 0 ? raw.Name : '(未命名知识库)',\n relation: typeof raw.Relation === 'string' ? raw.Relation : '',\n visibility: typeof raw.Visibility === 'string' ? raw.Visibility : '',\n isDefault: raw.IsDefault === true,\n contentCount: typeof raw.ContentCount === 'number' ? raw.ContentCount : 0,\n }\n}\n\nexport async function listZhihuKnowledgeBases(options: ZhihuClientOptions = {}): Promise<ZhihuKnowledgeBaseList> {\n const body = await zhihuFetchJson(KNOWLEDGE_BASES_PATH, {\n params: { Scope: 'all' },\n }, '知乎知识库列表', options)\n const data = parseZhihuEnvelope(body) as { Items?: unknown }\n const items = Array.isArray(data.Items) ? data.Items : []\n return {\n version: ZHIHU_KNOWLEDGE_LIST_VERSION,\n bases: items\n .filter((item): item is Record<string, unknown> => !!item && typeof item === 'object' && !Array.isArray(item))\n .map(normalizeBase)\n .filter((base) => base.id.length > 0),\n }\n}\n\nexport type ZhihuKnowledgeUploadInput = {\n fileName: string\n data: Uint8Array\n /** 指定目标知识库;缺省进默认库。 */\n knowledgeBaseId?: string\n}\n\nexport type ZhihuKnowledgeUpload = {\n version: typeof ZHIHU_KNOWLEDGE_UPLOAD_VERSION\n knowledgeBaseId: string\n recallContentId: string\n fileName: string\n fileSize: number\n title: string\n abstract: string\n originUrl: string\n}\n\n/** 校验文件名与大小,返回小写扩展名。 */\nexport function checkKnowledgeFile(fileName: string, size: number): string {\n const cleaned = fileName.trim()\n if (!cleaned) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能为空。')\n // 去掉路径成分,只保留文件名\n const base = cleaned.split(/[\\\\/]/).pop() ?? cleaned\n for (const ch of base) {\n const code = ch.codePointAt(0) ?? 0\n if (code < 32 || code === 127) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能包含控制字符。')\n }\n if (new TextEncoder().encode(base).length > 255) {\n throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件名不能超过 255 个 UTF-8 字节。')\n }\n if (size <= 0) throw new ZhihuSearchError('BAD_RESPONSE', '知识库文件内容不能为空。')\n if (size > ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES) {\n throw new ZhihuSearchError('BAD_RESPONSE', `知识库文件大小不能超过 ${ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES / 1024 / 1024}MB。`)\n }\n const dot = base.lastIndexOf('.')\n const ext = dot >= 0 ? base.slice(dot).toLowerCase() : ''\n if (!(ZHIHU_KNOWLEDGE_FILE_EXTENSIONS as readonly string[]).includes(ext)) {\n throw new ZhihuSearchError('BAD_RESPONSE', `知识库上传仅支持 ${ZHIHU_KNOWLEDGE_FILE_EXTENSIONS.join('、')}。`)\n }\n return ext\n}\n\nexport async function uploadZhihuKnowledgeFile(\n input: ZhihuKnowledgeUploadInput,\n options: ZhihuClientOptions = {},\n): Promise<ZhihuKnowledgeUpload> {\n const cleaned = (input.fileName.trim().split(/[\\\\/]/).pop() ?? input.fileName.trim())\n const ext = checkKnowledgeFile(input.fileName, input.data.byteLength)\n const form = new FormData()\n if (input.knowledgeBaseId) form.set('KnowledgeBaseID', input.knowledgeBaseId)\n form.set('File', new Blob([input.data.buffer as ArrayBuffer], { type: KNOWLEDGE_CONTENT_TYPES[ext] ?? 'application/octet-stream' }), cleaned)\n const body = await zhihuFetchJson(KNOWLEDGE_FILES_PATH, {\n formData: form,\n }, '知乎知识库上传', { ...options, timeoutMs: options.timeoutMs ?? ZHIHU_KNOWLEDGE_UPLOAD_TIMEOUT_MS })\n const data = parseZhihuEnvelope(body) as Record<string, unknown>\n return {\n version: ZHIHU_KNOWLEDGE_UPLOAD_VERSION,\n knowledgeBaseId: typeof data.KnowledgeBaseID === 'string' ? data.KnowledgeBaseID : '',\n recallContentId: typeof data.RecallContentID === 'string' ? data.RecallContentID : '',\n fileName: typeof data.FileName === 'string' ? data.FileName : cleaned,\n fileSize: typeof data.FileSize === 'number' ? data.FileSize : input.data.byteLength,\n title: typeof data.Title === 'string' ? data.Title : '',\n abstract: typeof data.Abstract === 'string' ? data.Abstract : '',\n originUrl: typeof data.OriginUrl === 'string' ? data.OriginUrl : '',\n }\n}\n","import { registerHostRpcTransport, sendHostRpcResponse, type HostRpcContext } from '@klarkxy/dsh-plugin-kit/host-rpc'\n\nconst CHANNEL_PATTERN = /^\\/[A-Za-z0-9._~-]+$/\nconst ENDPOINT_SEGMENT = /^[A-Za-z0-9_$.-]+$/\nconst MAX_BODY_BYTES = 64 * 1024 * 1024\n\nexport type { HostRpcContext }\nexport type HostRpcHandler = (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<unknown>\n\nfunction isEndpoint(endpoint: string): boolean {\n return endpoint.split('/').every(segment => segment !== '' && segment !== '.' && segment !== '..' && ENDPOINT_SEGMENT.test(segment))\n}\n\n/** Zhihu keeps its gateway-compatible bad-request envelope and upload allowance. */\nexport function registerHostRpc(ctx: HostRpcContext, channel: string, handler: HostRpcHandler): () => void {\n if (!CHANNEL_PATTERN.test(channel) || channel === '/api') {\n throw new Error(`invalid RPC channel ${JSON.stringify(channel)}`)\n }\n return registerHostRpcTransport(ctx, channel, async (endpoint, body, signal, res) => {\n const record = body && typeof body === 'object' && !Array.isArray(body) ? body as Record<string, unknown> : {}\n const rpcId = typeof record.rpcId === 'string' ? record.rpcId : 'invalid-request'\n if (record.type !== 'client-request' || record.method !== endpoint) {\n sendHostRpcResponse(res, rpcId, {\n ok: false,\n error: {\n code: 'gateway/bad-request',\n message: record.method !== endpoint\n ? `method ${JSON.stringify(record.method)} does not match endpoint ${JSON.stringify(endpoint)}`\n : 'invalid client-request message',\n details: { issues: [] },\n },\n }, false)\n return\n }\n sendHostRpcResponse(res, rpcId, await handler(endpoint, record.payload, signal), false)\n }, {\n maxBodyBytes: MAX_BODY_BYTES,\n isEndpoint,\n caseInsensitiveContentType: true,\n emptyBodyAsNull: true,\n errorBody: (status, error) => {\n if (status === 503) return 'host authorization policy is unavailable'\n if (status === 401) return 'unauthorized'\n if (status === 403) return 'forbidden'\n if (status === 404) return 'not found'\n if (status === 415) return 'content type must be application/json'\n if (status === 400) return 'body is not JSON'\n if (status === 500) return `handler failure: ${String(error)}`\n return undefined\n },\n })\n}\n","/**\n * Native ctx.web backend. Configuration and credentials belong to Zhihu.\n * Official model tool remains `web_search`. Dedicated `zhihu_global_search` is unchanged.\n */\nimport { ZHIHU_CREDENTIAL_REF } from './contracts.ts'\nimport { reportExecuted, type ZhihuClientOptions, type ZhihuSearchExecuted } from './zhihu-client.ts'\nimport {\n executeZhihuGlobalSearch,\n ZHIHU_GLOBAL_SEARCH_MAX_COUNT,\n} from './operations.ts'\n\ntype ZhihuWebSearchHost = {\n run<T>(execute: (signal: AbortSignal) => Promise<T>, signal: AbortSignal): Promise<T>\n toolOptions: ZhihuClientOptions & { onExecuted: (event: ZhihuSearchExecuted) => void }\n}\n\nexport const ZHIHU_WEB_SEARCH_ID = 'zhihu-global'\n\nexport const ZHIHU_WEB_SEARCH_DESCRIPTOR = {\n id: ZHIHU_WEB_SEARCH_ID,\n label: '知乎全网搜索',\n description: '融合知乎问答与全网内容,返回可溯源的搜索结果。',\n credentialRef: ZHIHU_CREDENTIAL_REF,\n credentialShared: true,\n credentialHint: '与「知乎资料」共用 Access Secret',\n billing: 'request' as const,\n pricing: '注册可获 5,000 次/天试用额度;超额价格需向平台咨询。',\n pricingUrl: 'https://developer.zhihu.com/',\n signupUrl: 'https://developer.zhihu.com',\n configurationOwner: '@klarkxy/dsh-zhihu',\n}\n\nexport type ZhihuWebSearchSource = { url: string; title?: string; snippet?: string }\nexport type ZhihuWebSearchResult = { sources: ZhihuWebSearchSource[]; truncated?: boolean }\nexport type ZhihuWebSearchRequest = { query: string; maxResults?: number }\n\nexport type ZhihuWebSearchProvider = {\n readonly id: string\n available(): boolean\n search(request: ZhihuWebSearchRequest, signal?: AbortSignal): Promise<ZhihuWebSearchResult>\n}\n\nexport type ZhihuWebRegistry = {\n registerSearchProvider(provider: ZhihuWebSearchProvider & {\n dshWebManagement: typeof ZHIHU_WEB_SEARCH_DESCRIPTOR\n }): () => void\n}\n\nexport type ZhihuWebSearchProviderOptions = ZhihuClientOptions & {\n apiKey?: string\n onExecuted?: (event: ZhihuSearchExecuted) => void\n}\n\nfunction sourceFrom(url: string, title: string, snippet: string): ZhihuWebSearchSource | undefined {\n let parsed: URL\n try { parsed = new URL(url) } catch { return }\n if (!['https:', 'http:'].includes(parsed.protocol) || parsed.username || parsed.password) return\n return {\n url: parsed.href,\n ...(title ? { title: title.slice(0, 1000) } : {}),\n ...(snippet ? { snippet: snippet.slice(0, 8000) } : {}),\n }\n}\n\nfunction isWebRegistry(value: unknown): value is ZhihuWebRegistry {\n return !!value && typeof value === 'object' && typeof (value as ZhihuWebRegistry).registerSearchProvider === 'function'\n}\n\nexport function createZhihuWebSearchProvider(options: ZhihuWebSearchProviderOptions): ZhihuWebSearchProvider {\n const { apiKey, onExecuted, ...client } = options\n return {\n id: ZHIHU_WEB_SEARCH_ID,\n available() { return Boolean(apiKey?.trim()) },\n async search(request, signal) {\n if (!apiKey?.trim()) throw new Error('知乎全网搜索未配置 Access Secret。')\n if (!request.query?.trim() || request.query.length > 4000) throw new Error('查询无效。')\n const count = request.maxResults ?? 5\n if (!Number.isInteger(count) || count < 1 || count > ZHIHU_GLOBAL_SEARCH_MAX_COUNT) {\n throw new Error(`结果数须为 1 至 ${ZHIHU_GLOBAL_SEARCH_MAX_COUNT}。`)\n }\n try {\n signal?.throwIfAborted()\n const result = await executeZhihuGlobalSearch(request.query.trim(), count, 'all', {\n ...client,\n signal,\n env: {},\n resolveCredential: async () => apiKey,\n })\n reportExecuted(onExecuted, { ok: true, results: result.items.length })\n const sources = result.items.flatMap(item => sourceFrom(item.url, item.title, item.summary) ?? [])\n return { sources: sources.slice(0, count), truncated: sources.length > count }\n } catch (error) {\n reportExecuted(onExecuted, { ok: false, results: 0 })\n throw error\n }\n },\n }\n}\n\nexport function registerZhihuGlobalSearchProvider(\n web: ZhihuWebRegistry,\n service: ZhihuWebSearchHost,\n configured: () => boolean,\n): () => void {\n return web.registerSearchProvider({\n id: ZHIHU_WEB_SEARCH_ID, dshWebManagement: ZHIHU_WEB_SEARCH_DESCRIPTOR,\n available: configured,\n search: (request, signal) => service.run(\n async combined => {\n combined.throwIfAborted()\n const apiKey = await service.toolOptions.resolveCredential?.()\n combined.throwIfAborted()\n const provider = createZhihuWebSearchProvider({ ...service.toolOptions, apiKey })\n return provider.search(request, combined)\n },\n signal ?? new AbortController().signal,\n ),\n })\n}\n\ntype BindingContext = {\n get(name: string): unknown\n on?(event: 'credentials/reference-updated', handler: (ref: string) => void): () => void\n emit?(event: 'web/provider-availability-updated', kind: 'search', id: string): void\n}\n/** Wait for the native web service; no manager dependency and no network probe. */\nexport function bindZhihuWebSearch(\n ctx: { inject(deps: readonly string[], apply: (inner: BindingContext) => (() => void) | void): unknown },\n service: ZhihuWebSearchHost,\n): void {\n ctx.inject(['web'], (inner) => {\n let web: unknown\n try { web = inner.get('web') }\n catch { return }\n if (!isWebRegistry(web)) return\n let configured = false, alive = true, revision = 0\n const refresh = async () => {\n const current = ++revision\n let ready = false\n try { ready = Boolean((await service.toolOptions.resolveCredential?.())?.trim()) } catch { /* unavailable */ }\n if (!alive || current !== revision) return\n if (configured !== ready) {\n configured = ready\n inner.emit?.('web/provider-availability-updated', 'search', ZHIHU_WEB_SEARCH_ID)\n }\n }\n const off = registerZhihuGlobalSearchProvider(web, service, () => configured)\n const unlisten = inner.on?.('credentials/reference-updated', ref => {\n if (ref === ZHIHU_CREDENTIAL_REF) { void refresh() }\n })\n void refresh()\n return () => { alive = false; revision++; configured = false; unlisten?.(); off() }\n })\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport { credentialRef } from '@deepseek-ai/dsh-credentials'\nimport { executeZhihuSearch, ZHIHU_SEARCH_DEFAULT_COUNT } from './search-api.ts'\nimport {\n executeZhihuGlobalSearch, executeZhihuHotList, executeZhihuAsk, executeZhihuKnowledgeSearch,\n normalizeRecallScopes, ZHIHU_ASK_MODELS, ZHIHU_ASK_DEFAULT_MODEL,\n} from './operations.ts'\nimport { listZhihuKnowledgeBases, uploadZhihuKnowledgeFile, ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES } from './zhihu-knowledge.ts'\nimport { type ZhihuClientOptions, type ZhihuSearchExecuted, ZhihuSearchError } from './zhihu-client.ts'\nimport { createZhihuUsageRecorder, resolveDays, zhihuUsageDomainSpec, type ZhihuUsageRecorder } from './usage.ts'\nimport { ZHIHU_RPC_CHANNEL, ZHIHU_CREDENTIAL_REF, ZHIHU_SEARCH_EVENT, type ZhihuRpcResult } from './contracts.ts'\nimport { registerHostRpc, type HostRpcContext } from './host-rpc.ts'\nimport { bindZhihuWebSearch } from './web-search-provider.ts'\nimport { executeZhihuOpenPlatform, resolveZhihuOpenPlatformOperation, ZHIHU_OPEN_PLATFORM_APIS, zhihuOpenPlatformResultCount } from './open-platform.ts'\n\nexport const name = '@klarkxy/dsh-zhihu'\nexport const inject = ['connection', 'credentials', 'storageDomain', 'webServer'] as const\nexport type ZhihuService = {\n call(endpoint: string, payload: unknown, signal: AbortSignal): Promise<ZhihuRpcResult>\n run<T>(execute: (signal: AbortSignal) => Promise<T>, signal: AbortSignal): Promise<T>\n dispose(): Promise<void>\n usageSummary(days: unknown): Promise<{ days: unknown[] }>\n toolOptions: ZhihuClientOptions & { onExecuted: (event: ZhihuSearchExecuted) => void }\n}\n\ntype Host = Context & HostRpcContext & {\n credentials: { resolve: (ref: ReturnType<typeof credentialRef>) => Promise<{ value: string } | undefined> }\n}\nconst fail = (code: string, message: string, details: Record<string, unknown> = {}): ZhihuRpcResult => ({ ok: false, error: { code, message, details } })\n\n/** One shared service for direct UI RPC and optional Tool wrappers. */\nexport function createZhihuService(options: ZhihuClientOptions, usage: ZhihuUsageRecorder, notify: (event: ZhihuSearchExecuted) => void = () => {}): ZhihuService {\n // Serialize read-modify-write counters; concurrent tools must not lose increments.\n let pendingUsage = Promise.resolve()\n const lifetime = new AbortController()\n const active = new Set<Promise<unknown>>()\n const run: ZhihuService['run'] = (execute, signal) => {\n const combined = AbortSignal.any([signal, lifetime.signal])\n const request = Promise.resolve().then(() => { combined.throwIfAborted(); return execute(combined) })\n active.add(request)\n return request.finally(() => active.delete(request))\n }\n const record = (event: ZhihuSearchExecuted) => {\n pendingUsage = pendingUsage.then(() => usage.record(event)).catch(() => {})\n try { notify(event) } catch { /* observer failure does not change a request */ }\n }\n const service = {\n toolOptions: { ...options, onExecuted: record },\n async usageSummary(days: unknown) { await pendingUsage; return { days: await usage.read(resolveDays(days)) } },\n async call(endpoint: string, payload: unknown, signal: AbortSignal): Promise<ZhihuRpcResult> {\n if (signal.aborted) return fail('cancelled', '请求已取消')\n const body = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload as Record<string, unknown> : {}\n const client = { ...options, signal }\n let counted = false\n const operation = resolveZhihuOpenPlatformOperation(endpoint)\n const quotaId = operation ? ZHIHU_OPEN_PLATFORM_APIS[operation].quotaId : undefined\n const metering = quotaId ? { quotaId } : {}\n try {\n if (endpoint === 'usage.summary') { await pendingUsage; return { ok: true, value: { days: await usage.read(resolveDays(body.days)) } } }\n const query = typeof body.query === 'string' ? body.query : ''\n if (['search', 'global.search', 'ask', 'knowledge.search'].includes(endpoint) && (!query.trim() || query.length > 4000)) {\n return fail('bad-request', '查询不超过 4000 字符')\n }\n let value: unknown\n let results = 0\n counted = true\n if (operation) {\n counted = operation !== 'quota'\n const result = await executeZhihuOpenPlatform(operation, payload === undefined ? {} : payload, client)\n value = result; results = zhihuOpenPlatformResultCount(result)\n } else if (endpoint === 'search') {\n const result = await executeZhihuSearch(query, typeof body.count === 'number' ? body.count : ZHIHU_SEARCH_DEFAULT_COUNT, client)\n value = result; results = result.items.length\n } else if (endpoint === 'global.search') {\n const result = await executeZhihuGlobalSearch(query, typeof body.count === 'number' ? body.count : 10,\n body.searchDb === 'realtime' || body.searchDb === 'static' ? body.searchDb : 'all', client)\n value = result; results = result.items.length\n } else if (endpoint === 'hot.list') {\n const result = await executeZhihuHotList(typeof body.limit === 'number' ? body.limit : 10, client)\n value = result; results = result.items.length\n } else if (endpoint === 'ask') {\n const model = (ZHIHU_ASK_MODELS as readonly unknown[]).includes(body.model) ? body.model as typeof ZHIHU_ASK_MODELS[number] : ZHIHU_ASK_DEFAULT_MODEL\n value = await executeZhihuAsk(query, model, client); results = 1\n } else if (endpoint === 'knowledge.search') {\n const result = await executeZhihuKnowledgeSearch(query, typeof body.limit === 'number' ? body.limit : 5, normalizeRecallScopes(body.recallScopes), client)\n value = result; results = result.items.length\n } else if (endpoint === 'knowledge.bases') {\n const result = await listZhihuKnowledgeBases(client); value = result; results = result.bases.length\n } else if (endpoint === 'knowledge.upload') {\n const contentBase64 = typeof body.contentBase64 === 'string' ? body.contentBase64 : ''\n if (!contentBase64) throw new Error('缺少文件内容。')\n if (contentBase64.length > Math.ceil(ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES / 3) * 4 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(contentBase64)) {\n return fail('bad-request', '文件内容无效或超过 20 MB')\n }\n const upload = await uploadZhihuKnowledgeFile({\n fileName: typeof body.fileName === 'string' ? body.fileName : '',\n data: new Uint8Array(Buffer.from(contentBase64, 'base64')),\n knowledgeBaseId: typeof body.knowledgeBaseId === 'string' && body.knowledgeBaseId.trim() ? body.knowledgeBaseId.trim() : undefined,\n }, client)\n value = { upload }; results = 1\n } else {\n counted = false\n return fail('bad-request', `unknown endpoint ${endpoint}`)\n }\n if (counted) record({ ok: true, results, ...metering })\n return { ok: true, value }\n } catch (error) {\n if (counted) record({ ok: false, results: 0, ...metering })\n if (signal.aborted) return fail('cancelled', '请求已取消')\n const code = error instanceof ZhihuSearchError ? error.code.toLowerCase().replaceAll('_', '-') : 'internal'\n const details: Record<string, unknown> = {}\n if (error instanceof ZhihuSearchError) {\n for (const key of ['status', 'upstreamCode', 'retryAfter', 'retryable'] as const) {\n if (error[key] !== undefined) details[key] = error[key]\n }\n }\n return fail(code, error instanceof Error ? error.message : '知乎请求失败', details)\n }\n },\n }\n return {\n ...service,\n run,\n async call(endpoint, payload, signal) {\n if (signal.aborted || lifetime.signal.aborted) return fail('cancelled', '请求已取消')\n try { return await run(combined => service.call(endpoint, payload, combined), signal) }\n catch (error) {\n if (signal.aborted || lifetime.signal.aborted) return fail('cancelled', '请求已取消')\n throw error\n }\n },\n async dispose() {\n lifetime.abort()\n await Promise.allSettled([...active])\n await pendingUsage\n },\n }\n}\n\nexport async function apply(ctx: Context): Promise<void> {\n const host = ctx as Host\n const domain = await ctx.storageDomain.open(zhihuUsageDomainSpec)\n const service = createZhihuService({\n resolveCredential: async () => (await host.credentials.resolve(credentialRef(ZHIHU_CREDENTIAL_REF)))?.value,\n }, createZhihuUsageRecorder(domain.table('daily')), event => {\n const emit = ctx.emit as unknown as (name: string, event: ZhihuSearchExecuted) => void\n emit.call(ctx, ZHIHU_SEARCH_EVENT, event)\n })\n ctx.effect(() => async () => { await service.dispose(); await domain.close() }, 'zhihu.usageDomainClose')\n ctx.provide('zhihu', service)\n ctx.effect(() => registerHostRpc(host, ZHIHU_RPC_CHANNEL, service.call))\n bindZhihuWebSearch(ctx, service)\n}\n"],"mappings":"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"}
|
package/lib/open-platform.js
CHANGED
|
@@ -11,13 +11,11 @@ import { join } from "node:path";
|
|
|
11
11
|
* - 公共 Headers: Authorization: Bearer <access_secret>、X-Request-Timestamp、Accept
|
|
12
12
|
* - 除直答外的 REST 接口返回 {Code, Message, Data} 信封,Code 非 0 表示业务错误。
|
|
13
13
|
*
|
|
14
|
-
* Token
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* 4. ~/.config/zhihu-search/credentials.json 的 access_secret 字段
|
|
20
|
-
* 都没有则抛出带中文配置指引的 TOKEN_MISSING。
|
|
14
|
+
* Token 解析(按契约,**不**把 token 放进 schema 参数):
|
|
15
|
+
* - 提供 credential 解析器时只使用它,缺失或无效时抛出 TOKEN_MISSING。
|
|
16
|
+
* 宿主通过 `ctx.credentials.resolve` 统一管理凭据来源和清除操作。
|
|
17
|
+
* - 未提供解析器的独立客户端依次兼容 ZHIHU_ACCESS_TOKEN、
|
|
18
|
+
* ZHIHU_ACCESS_SECRET 和 ~/.config/zhihu-search/credentials.json。
|
|
21
19
|
*/
|
|
22
20
|
const ZHIHU_BASE_URL = "https://developer.zhihu.com";
|
|
23
21
|
const ZHIHU_DEFAULT_TIMEOUT_MS = 15e3;
|
|
@@ -41,11 +39,13 @@ async function readCredentialsFile(path) {
|
|
|
41
39
|
}
|
|
42
40
|
async function resolveZhihuToken(env = process.env, options = {}) {
|
|
43
41
|
if (options.resolveCredential) {
|
|
44
|
-
const
|
|
42
|
+
const value = await options.resolveCredential();
|
|
43
|
+
const fromCredential = typeof value === "string" ? value.trim() : void 0;
|
|
45
44
|
if (fromCredential && looksLikeToken(fromCredential)) return {
|
|
46
45
|
token: fromCredential,
|
|
47
46
|
source: "credential"
|
|
48
47
|
};
|
|
48
|
+
throw new ZhihuSearchError("TOKEN_MISSING", "未配置有效的知乎 Access Secret。请在「插件 → 知乎 → 设置」中填写。");
|
|
49
49
|
}
|
|
50
50
|
const primary = env[ZHIHU_CREDENTIALS_PRIMARY]?.trim();
|
|
51
51
|
if (primary && looksLikeToken(primary)) return {
|
package/lib/open-platform.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-platform.js","names":[],"sources":["../src/zhihu-client.ts","../src/search-api.ts","../src/operations.ts","../src/open-platform.ts"],"sourcesContent":["/**\n * 知乎开放平台共享请求层:Access Secret 解析、超时封装、信封解析与执行计量。\n *\n * 端点规范(来源:github.com/klarkxy/zhihu-search,src/zhihu_search/upstream/http_client.py):\n * - Base URL: https://developer.zhihu.com\n * - 公共 Headers: Authorization: Bearer <access_secret>、X-Request-Timestamp、Accept\n * - 除直答外的 REST 接口返回 {Code, Message, Data} 信封,Code 非 0 表示业务错误。\n *\n * Token 解析顺序(按契约,**不**把 token 放进 schema 参数):\n * 1. 可选的 credential 解析器(应用设置界面写入 `ZHIHU_ACCESS_TOKEN` 后,\n * 通过 `ctx.credentials.resolve` 取回);命中即返回,source 标记为 `credential`。\n * 2. 环境变量 ZHIHU_ACCESS_TOKEN\n * 3. 环境变量 ZHIHU_ACCESS_SECRET(zhihu-search CLI 同名变量)\n * 4. ~/.config/zhihu-search/credentials.json 的 access_secret 字段\n * 都没有则抛出带中文配置指引的 TOKEN_MISSING。\n */\nimport { readFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { ZhihuQuotaId } from './contracts.ts'\n\nexport const ZHIHU_BASE_URL = 'https://developer.zhihu.com'\nexport const ZHIHU_DEFAULT_TIMEOUT_MS = 15_000\nexport const ZHIHU_SUMMARY_MAX_CHARS = 200\n\nconst ZHIHU_CREDENTIALS_PRIMARY = 'ZHIHU_ACCESS_TOKEN'\nconst ZHIHU_CREDENTIALS_FALLBACK = 'ZHIHU_ACCESS_SECRET'\nconst ZHIHU_CREDENTIALS_FILE = join(homedir(), '.config', 'zhihu-search', 'credentials.json')\n\ntype TokenSource = 'credential' | 'env-primary' | 'env-fallback' | 'file'\n\nexport type ZhihuSearchToken = { token: string; source: TokenSource }\n\nexport type ZhihuSearchFetcher = (input: string, init?: { method?: string; headers?: Record<string, string>; body?: string | FormData; signal?: AbortSignal }) => Promise<{\n ok: boolean\n status: number\n headers?: { get(name: string): string | null }\n text(): Promise<string>\n json(): Promise<unknown>\n}>\n\nconst globalFetch: ZhihuSearchFetcher | undefined = typeof globalThis !== 'undefined' && typeof (globalThis as { fetch?: unknown }).fetch === 'function'\n ? (input, init) => {\n const f = (globalThis as { fetch: typeof globalThis.fetch }).fetch\n return f(input as Parameters<typeof f>[0], init as Parameters<typeof f>[1] | undefined) as unknown as ReturnType<ZhihuSearchFetcher>\n }\n : undefined\n\nfunction looksLikeToken(value: string): boolean {\n const trimmed = value.trim()\n return trimmed.length >= 8 && trimmed.length <= 256\n}\n\nasync function readCredentialsFile(path: string): Promise<string | undefined> {\n try {\n const raw = await readFile(path, 'utf8')\n const parsed = JSON.parse(raw) as { access_secret?: unknown } | null\n if (parsed && typeof parsed === 'object' && typeof parsed.access_secret === 'string' && looksLikeToken(parsed.access_secret)) {\n return parsed.access_secret.trim()\n }\n } catch {\n // 文件不存在或 JSON 解析失败都不算硬错误,让上层回退到 isError 提示\n }\n return undefined\n}\n\nexport type ZhihuSearchResolveOptions = {\n /** Resolve the Access Secret via host-managed credentials (e.g. the settings UI). */\n resolveCredential?: () => Promise<string | undefined>\n}\n\nexport async function resolveZhihuToken(\n env: NodeJS.ProcessEnv = process.env,\n options: ZhihuSearchResolveOptions = {},\n): Promise<ZhihuSearchToken> {\n if (options.resolveCredential) {\n const fromCredential = (await options.resolveCredential())?.trim()\n if (fromCredential && looksLikeToken(fromCredential)) {\n return { token: fromCredential, source: 'credential' }\n }\n }\n const primary = env[ZHIHU_CREDENTIALS_PRIMARY]?.trim()\n if (primary && looksLikeToken(primary)) return { token: primary, source: 'env-primary' }\n const fallback = env[ZHIHU_CREDENTIALS_FALLBACK]?.trim()\n if (fallback && looksLikeToken(fallback)) return { token: fallback, source: 'env-fallback' }\n const fromFile = await readCredentialsFile(ZHIHU_CREDENTIALS_FILE)\n if (fromFile) return { token: fromFile, source: 'file' }\n throw new ZhihuSearchError('TOKEN_MISSING', [\n '未找到知乎 Access Secret。请在「设置 → 知乎」中填写;',\n `也可设置环境变量 ${ZHIHU_CREDENTIALS_PRIMARY} 或写入 ~/.config/zhihu-search/credentials.json。`,\n ].join(''))\n}\n\nexport type ZhihuErrorDetails = {\n upstreamCode?: number\n retryAfter?: number\n retryable?: boolean\n}\n\nexport class ZhihuSearchError extends Error {\n readonly code: 'TOKEN_MISSING' | 'HTTP_ERROR' | 'TIMEOUT' | 'BAD_RESPONSE'\n | 'INVALID_ARGUMENTS' | 'TOKEN_INVALID' | 'RATE_LIMITED' | 'UPSTREAM_UNAVAILABLE' | 'CANCELLED'\n readonly status?: number\n readonly upstreamCode?: number\n readonly retryAfter?: number\n readonly retryable?: boolean\n constructor(code: ZhihuSearchError['code'], message: string, status?: number, details: ZhihuErrorDetails = {}) {\n super(message)\n this.name = 'ZhihuSearchError'\n this.code = code\n if (status !== undefined) this.status = status\n if (details.upstreamCode !== undefined) this.upstreamCode = details.upstreamCode\n if (details.retryAfter !== undefined) this.retryAfter = details.retryAfter\n if (details.retryable !== undefined) this.retryable = details.retryable\n }\n}\n\n/** Metering payload emitted after each zhihu tool execution. */\nexport type ZhihuSearchExecuted = { ok: boolean; results: number; quotaId?: ZhihuQuotaId }\n\n/** Metering must never break the tool itself. */\nexport function reportExecuted(hook: ((event: ZhihuSearchExecuted) => void) | undefined, event: ZhihuSearchExecuted): void {\n if (!hook) return\n try {\n hook(event)\n } catch {\n // Best-effort metering; swallow hook failures.\n }\n}\n\nexport type ZhihuClientOptions = ZhihuSearchResolveOptions & {\n fetcher?: ZhihuSearchFetcher\n /** Allows tests to bypass process env; defaults to process.env. */\n env?: NodeJS.ProcessEnv\n signal?: AbortSignal\n /** Override the request timeout in ms. Defaults to {@link ZHIHU_DEFAULT_TIMEOUT_MS}. */\n timeoutMs?: number\n}\n\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof Error && error.name === 'AbortError') return true\n return typeof error === 'object' && error !== null && (error as { name?: unknown }).name === 'AbortError'\n}\n\n/** Parse Retry-After seconds or an HTTP date; never invent a quota reset time. */\nexport function parseZhihuRetryAfter(value: string | null | undefined, now = Date.now()): number | undefined {\n if (!value?.trim()) return undefined\n const text = value.trim()\n if (/^\\d+(?:\\.\\d+)?$/.test(text)) {\n const seconds = Number(text)\n return Number.isFinite(seconds) ? Math.ceil(seconds) : undefined\n }\n // Do not let Date.parse interpret negative numbers as calendar dates.\n if (!/[a-z]/i.test(text)) return undefined\n const date = Date.parse(text)\n return Number.isFinite(date) ? Math.max(0, Math.ceil((date - now) / 1000)) : undefined\n}\n\n/**\n * Shared authenticated request. envelope=true opts into official error semantics\n * and returns Data. Legacy callers still receive the full JSON / chat response.\n * The timeout and cancellation cover BOTH headers and response-body consumption.\n * No requests are retried, including 429, exhausted quota, or risk-control denial.\n */\nexport async function zhihuFetchJson(\n path: string,\n init: { method?: 'GET' | 'POST'; params?: Record<string, string>; body?: unknown; formData?: FormData; envelope?: boolean },\n label: string,\n options: ZhihuClientOptions = {},\n): Promise<unknown> {\n const linked = options.signal\n if (linked?.aborted) throw new ZhihuSearchError('CANCELLED', '请求已取消')\n const url = new URL(path, ZHIHU_BASE_URL)\n if (url.origin !== ZHIHU_BASE_URL || url.username || url.password) {\n throw new ZhihuSearchError('INVALID_ARGUMENTS', '只允许请求知乎开放平台。')\n }\n for (const [key, value] of Object.entries(init.params ?? {})) url.searchParams.set(key, value)\n const token = await resolveZhihuToken(options.env, { resolveCredential: options.resolveCredential })\n if (linked?.aborted) throw new ZhihuSearchError('CANCELLED', '请求已取消')\n const fetcher = options.fetcher ?? globalFetch\n if (!fetcher) throw new ZhihuSearchError('BAD_RESPONSE', '当前环境没有可用的 fetch。')\n const method = init.method ?? (init.formData || init.body !== undefined ? 'POST' : 'GET')\n const timeout = options.timeoutMs !== undefined && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0\n ? options.timeoutMs : ZHIHU_DEFAULT_TIMEOUT_MS\n const controller = new AbortController()\n const onAbort = () => controller.abort(linked?.reason)\n if (linked?.aborted) onAbort()\n else linked?.addEventListener('abort', onAbort, { once: true })\n const timer = setTimeout(() => controller.abort(new Error('zhihu request timeout')), timeout)\n let stopWaiting = () => {}\n const aborted = new Promise<never>((_resolve, reject) => {\n const rejectAbort = () => reject(controller.signal.reason)\n if (controller.signal.aborted) rejectAbort()\n else {\n controller.signal.addEventListener('abort', rejectAbort, { once: true })\n stopWaiting = () => controller.signal.removeEventListener('abort', rejectAbort)\n }\n })\n try {\n const request = async () => {\n controller.signal.throwIfAborted()\n const response = await fetcher(url.toString(), {\n method,\n headers: {\n Authorization: `Bearer ${token.token}`,\n 'X-Request-Timestamp': String(Math.floor(Date.now() / 1000)),\n Accept: 'application/json',\n // Let fetch supply the multipart boundary.\n ...(method === 'POST' && !init.formData ? { 'Content-Type': 'application/json' } : {}),\n },\n signal: controller.signal,\n ...(init.formData ? { body: init.formData } : init.body !== undefined ? { body: JSON.stringify(init.body) } : {}),\n })\n const retryAfter = parseZhihuRetryAfter(response.headers?.get('Retry-After'))\n const httpError = () => {\n const code = response.status === 429 ? 'RATE_LIMITED'\n : init.envelope && (response.status === 401 || response.status === 403) ? 'TOKEN_INVALID'\n : 'HTTP_ERROR'\n return new ZhihuSearchError(code, `${label}返回 HTTP ${response.status}。`, response.status,\n code === 'RATE_LIMITED' ? { retryAfter, retryable: true } : {})\n }\n let body: unknown\n try { body = await response.json() }\n catch (error) {\n if (controller.signal.aborted) throw error\n if (!response.ok) throw httpError()\n throw new ZhihuSearchError('BAD_RESPONSE', `${label}响应不是合法 JSON。`)\n }\n controller.signal.throwIfAborted()\n // A business error remains meaningful even when the HTTP status is 400.\n const isEnvelope = body !== null && typeof body === 'object' && !Array.isArray(body) && 'Code' in body\n if (!response.ok && !isEnvelope) throw httpError()\n if (init.envelope || (isEnvelope && (body as { Code: unknown }).Code !== 0 && (body as { Code: unknown }).Code !== '0')) {\n const data = parseZhihuEnvelope(body, { status: response.status, retryAfter, officialErrors: init.envelope })\n if (!response.ok) throw httpError()\n if (init.envelope) return data\n }\n if (!response.ok) throw httpError()\n return body\n }\n return await Promise.race([request(), aborted])\n } catch (error) {\n if (linked?.aborted) throw new ZhihuSearchError('CANCELLED', '请求已取消')\n if (controller.signal.aborted || isAbortError(error)) {\n throw new ZhihuSearchError('TIMEOUT', `${label}超时(${Math.round(timeout / 1000)} 秒),请稍后再试。`)\n }\n if (error instanceof ZhihuSearchError) {\n error.message = error.message.replaceAll(token.token, '[REDACTED]')\n throw error\n }\n const message = (error instanceof Error ? error.message : String(error)).replaceAll(token.token, '[REDACTED]')\n throw new ZhihuSearchError('BAD_RESPONSE', `${label}失败:${message}`)\n } finally {\n clearTimeout(timer)\n stopWaiting()\n linked?.removeEventListener('abort', onAbort)\n }\n}\n\nconst ZHIHU_BUSINESS_ERRORS: Record<number, ZhihuSearchError['code']> = {\n 10001: 'INVALID_ARGUMENTS', 20001: 'TOKEN_INVALID',\n 30001: 'RATE_LIMITED', 30002: 'RATE_LIMITED',\n 40001: 'INVALID_ARGUMENTS', 40002: 'INVALID_ARGUMENTS', 40003: 'RATE_LIMITED',\n 40004: 'INVALID_ARGUMENTS', 40005: 'INVALID_ARGUMENTS', 40006: 'INVALID_ARGUMENTS',\n 50002: 'UPSTREAM_UNAVAILABLE', 90001: 'UPSTREAM_UNAVAILABLE',\n}\n\n/** Parse the official envelope without coercing null/false/empty Code into success. */\nexport function parseZhihuEnvelope(body: unknown, meta: { status?: number; retryAfter?: number; officialErrors?: boolean } = {}): unknown {\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n throw new ZhihuSearchError('BAD_RESPONSE', '知乎响应缺少 {Code,Message,Data} 信封。', meta.status)\n }\n const envelope = body as { Code?: unknown; Message?: unknown; Data?: unknown }\n const rawCode = envelope.Code\n if (!((typeof rawCode === 'number' && Number.isSafeInteger(rawCode))\n || (typeof rawCode === 'string' && /^\\d+$/.test(rawCode) && Number.isSafeInteger(Number(rawCode))))) {\n throw new ZhihuSearchError('BAD_RESPONSE', '知乎响应缺少有效的 Code 字段。', meta.status)\n }\n const code = Number(rawCode)\n if (code !== 0) {\n const message = typeof envelope.Message === 'string' && envelope.Message ? envelope.Message : '未知错误'\n if (code === 30003) {\n throw new ZhihuSearchError('UPSTREAM_UNAVAILABLE', `知乎开放平台被风控拒绝:${message}。请不要立即重试。`,\n meta.status, { upstreamCode: code, retryable: false })\n }\n const kind = ZHIHU_BUSINESS_ERRORS[code] ?? (meta.officialErrors ? 'UPSTREAM_UNAVAILABLE' : 'HTTP_ERROR')\n throw new ZhihuSearchError(kind, `知乎开放平台返回错误(code=${code}):${message}`, meta.status,\n { upstreamCode: code, ...(kind === 'RATE_LIMITED' ? { retryAfter: meta.retryAfter, retryable: true } : {}) })\n }\n return envelope.Data ?? {}\n}\n\nfunction truncateSummary(text: string, limit: number): string {\n if (text.length <= limit) return text\n return `${text.slice(0, limit)}…`\n}\n\n/** 搜索类接口(站内/全网)共用的条目结构。 */\nexport type ZhihuSearchItem = {\n title: string\n type: string\n url: string\n summary: string\n votes: number\n comments: number\n author: string\n authority: string\n editTime: string\n}\n\nexport function normalizeSearchItem(raw: Record<string, unknown>): ZhihuSearchItem {\n const summary = typeof raw.ContentText === 'string' ? raw.ContentText : ''\n return {\n title: typeof raw.Title === 'string' && raw.Title.length > 0 ? raw.Title : '(无标题)',\n type: typeof raw.ContentType === 'string' && raw.ContentType.length > 0 ? raw.ContentType : '内容',\n url: typeof raw.Url === 'string' ? raw.Url : '',\n summary: truncateSummary(summary.trim(), ZHIHU_SUMMARY_MAX_CHARS),\n votes: typeof raw.VoteUpCount === 'number' ? raw.VoteUpCount : 0,\n comments: typeof raw.CommentCount === 'number' ? raw.CommentCount : 0,\n author: typeof raw.AuthorName === 'string' && raw.AuthorName.length > 0 ? raw.AuthorName : '匿名',\n authority: typeof raw.AuthorityLevel === 'string' && raw.AuthorityLevel.length > 0 ? raw.AuthorityLevel : '?',\n editTime: typeof raw.EditTime === 'number' ? String(raw.EditTime) : '',\n }\n}\n","/**\n * 知乎开放平台站内搜索工具。\n *\n * 端点调研结论(来源:github.com/klarkxy/zhihu-search,src/zhihu_search/upstream/http_client.py):\n * - 知乎站内搜索:GET /api/v1/content/zhihu_search?Query=<q>&Count=<n>\n * - count 上限 10\n * - 响应信封:{ Code, Message, Data };Code 非 0 表示业务错误。\n * - Data.Items: 每条 { Title, ContentType, Url, ContentText, VoteUpCount,\n * CommentCount, AuthorName, AuthorityLevel, EditTime }。\n *\n * 鉴权、超时、信封解析与计量走 ./zhihu-client.ts 的共享实现;此处只保留\n * 站内搜索的参数收敛、结果规整与工具定义。\n */\nimport { ZHIHU_SEARCH_TOOL_NAME } from './contracts.ts'\nimport {\n normalizeSearchItem,\n parseZhihuEnvelope,\n reportExecuted,\n zhihuFetchJson,\n ZhihuSearchError,\n type ZhihuClientOptions,\n type ZhihuSearchItem,\n} from './zhihu-client.ts'\n\nexport { ZHIHU_SEARCH_TOOL_NAME } from './contracts.ts'\nexport {\n resolveZhihuToken,\n ZhihuSearchError,\n type ZhihuSearchExecuted,\n type ZhihuSearchFetcher,\n type ZhihuSearchItem,\n type ZhihuSearchResolveOptions,\n type ZhihuSearchToken,\n} from './zhihu-client.ts'\nexport const ZHIHU_SEARCH_VERSION = 1\nexport const ZHIHU_SEARCH_DEFAULT_COUNT = 5\nexport const ZHIHU_SEARCH_MAX_COUNT = 10\nexport const ZHIHU_SEARCH_TIMEOUT_MS = 15_000\nexport const ZHIHU_SUMMARY_MAX_CHARS = 200\n\nconst ZHIHU_SEARCH_PATH = '/api/v1/content/zhihu_search'\n\nexport type ZhihuSearchResult = {\n version: typeof ZHIHU_SEARCH_VERSION\n query: string\n count: number\n items: ZhihuSearchItem[]\n emptyReason?: string\n}\n\nexport type ZhihuSearchExecuteOptions = ZhihuClientOptions\n\nfunction clampCount(value: unknown): number {\n const n = typeof value === 'number' && Number.isFinite(value) ? Math.trunc(value) : ZHIHU_SEARCH_DEFAULT_COUNT\n if (n < 1) return 1\n if (n > ZHIHU_SEARCH_MAX_COUNT) return ZHIHU_SEARCH_MAX_COUNT\n return n\n}\n\nfunction filterItems(data: unknown): { items: Record<string, unknown>[]; emptyReason?: string } {\n const row = (data ?? {}) as { Items?: unknown; EmptyReason?: unknown }\n const items = Array.isArray(row.Items) ? row.Items.filter((item): item is Record<string, unknown> => !!item && typeof item === 'object' && !Array.isArray(item)) : []\n const emptyReason = typeof row.EmptyReason === 'string' ? row.EmptyReason : undefined\n return { items, emptyReason }\n}\n\nexport async function executeZhihuSearch(\n query: string,\n count: number,\n options: ZhihuSearchExecuteOptions = {},\n): Promise<ZhihuSearchResult> {\n const trimmed = query.trim()\n if (!trimmed) throw new ZhihuSearchError('BAD_RESPONSE', '搜索词不能为空。')\n const resolvedCount = clampCount(count)\n const body = await zhihuFetchJson(ZHIHU_SEARCH_PATH, {\n params: { Query: trimmed, Count: String(resolvedCount) },\n }, '知乎搜索', options)\n const { items, emptyReason } = filterItems(parseZhihuEnvelope(body))\n return {\n version: ZHIHU_SEARCH_VERSION,\n query: trimmed,\n count: resolvedCount,\n items: items.map(normalizeSearchItem),\n emptyReason,\n }\n}\n\nexport function renderZhihuSearch(result: ZhihuSearchResult): string {\n if (result.items.length === 0) {\n return `知乎站内搜索「${result.query}」无结果${result.emptyReason ? `(${result.emptyReason})` : ''}。`\n }\n const lines: string[] = [`知乎站内搜索「${result.query}」共 ${result.items.length} 条:`, '']\n result.items.forEach((item, index) => {\n lines.push(`${index + 1}. [${item.title}](${item.url || '(无链接)'})`)\n lines.push(` - 类型:${item.type} 作者:${item.author} 权威:${item.authority}`)\n if (item.editTime) lines.push(` - 时间:${item.editTime}`)\n lines.push(` - 赞同 ${item.votes} 评论 ${item.comments}`)\n if (item.summary) lines.push(` - 摘要:${item.summary}`)\n lines.push('')\n })\n return lines.join('\\n').trimEnd()\n}\n\n","/**\n * 知乎开放平台的其余写作向能力:全网搜索、热榜、直答、公开知识库检索。\n * 共享 ./zhihu-client.ts 的鉴权、超时、信封解析与计量;每个工具只做参数收敛、\n * 结果规整与渲染。结果一律只作社区参考,不构成 canon,也不写入项目文件。\n *\n * 端点(来源:github.com/klarkxy/zhihu-search,src/zhihu_search/upstream/http_client.py):\n * - 全网搜索:GET /api/v1/content/global_search?Query&Count(<=20)&SearchDB(all|realtime|static)\n * - 热榜:GET /api/v1/content/hot_list?Limit(<=30)\n * - 直答:POST /v1/chat/completions(OpenAI 兼容,非信封;模型 zhida-fast-1p5 / zhida-thinking-1p5)\n * - 知识库检索:POST /api/v1/knowledge/search { Query, Limit(<=10), RecallScopes }\n */\nimport {\n ZHIHU_ASK_TOOL_NAME,\n ZHIHU_GLOBAL_SEARCH_TOOL_NAME,\n ZHIHU_HOT_LIST_TOOL_NAME,\n ZHIHU_KNOWLEDGE_SEARCH_TOOL_NAME,\n} from './contracts.ts'\nimport {\n normalizeSearchItem,\n parseZhihuEnvelope,\n reportExecuted,\n zhihuFetchJson,\n ZhihuSearchError,\n type ZhihuClientOptions,\n type ZhihuSearchExecuted,\n type ZhihuSearchItem,\n} from './zhihu-client.ts'\n\nexport const ZHIHU_GLOBAL_SEARCH_VERSION = 1\nexport const ZHIHU_GLOBAL_SEARCH_DEFAULT_COUNT = 10\nexport const ZHIHU_GLOBAL_SEARCH_MAX_COUNT = 20\nexport const ZHIHU_HOT_LIST_VERSION = 1\nexport const ZHIHU_HOT_LIST_DEFAULT_LIMIT = 10\nexport const ZHIHU_HOT_LIST_MAX_LIMIT = 30\nexport const ZHIHU_ASK_VERSION = 1\nexport const ZHIHU_ASK_TIMEOUT_MS = 120_000\nexport const ZHIHU_KNOWLEDGE_SEARCH_VERSION = 1\nexport const ZHIHU_KNOWLEDGE_SEARCH_DEFAULT_LIMIT = 5\nexport const ZHIHU_KNOWLEDGE_SEARCH_MAX_LIMIT = 10\n\nconst GLOBAL_SEARCH_PATH = '/api/v1/content/global_search'\nconst HOT_LIST_PATH = '/api/v1/content/hot_list'\nconst ASK_PATH = '/v1/chat/completions'\nconst KNOWLEDGE_SEARCH_PATH = '/api/v1/knowledge/search'\n\nexport const ZHIHU_ASK_MODELS = ['zhida-fast-1p5', 'zhida-thinking-1p5', 'zhida-agent'] as const\nexport type ZhihuAskModel = (typeof ZHIHU_ASK_MODELS)[number]\n/** 直答的价值在于比本机模型更强的综合考据,默认用思考档;fast 只配简单事实查询。 */\nexport const ZHIHU_ASK_DEFAULT_MODEL: ZhihuAskModel = 'zhida-thinking-1p5'\n\nexport type ZhihuToolOptions = ZhihuClientOptions & {\n /** Best-effort metering hook; invoked after every execution, failures included. */\n onExecuted?: (event: ZhihuSearchExecuted) => void\n}\n\nfunction clampInt(value: unknown, fallback: number, max: number): number {\n const n = typeof value === 'number' && Number.isFinite(value) ? Math.trunc(value) : fallback\n if (n < 1) return 1\n if (n > max) return max\n return n\n}\n\nfunction requireQuery(query: string, hint: string): string {\n const trimmed = query.trim()\n if (!trimmed) throw new ZhihuSearchError('BAD_RESPONSE', hint)\n return trimmed\n}\n\nfunction objectItems(data: unknown, key = 'Items'): Record<string, unknown>[] {\n const row = (data ?? {}) as Record<string, unknown>\n const items = row[key]\n return Array.isArray(items) ? items.filter((item): item is Record<string, unknown> => !!item && typeof item === 'object' && !Array.isArray(item)) : []\n}\n\n// ---------------------------------------------------------------------------\n// 全网搜索\n// ---------------------------------------------------------------------------\n\nexport type ZhihuGlobalSearchDb = 'all' | 'realtime' | 'static'\n\nexport type ZhihuGlobalSearchResult = {\n version: typeof ZHIHU_GLOBAL_SEARCH_VERSION\n query: string\n count: number\n searchDb: ZhihuGlobalSearchDb\n items: ZhihuSearchItem[]\n emptyReason?: string\n}\n\nexport async function executeZhihuGlobalSearch(\n query: string,\n count: number,\n searchDb: ZhihuGlobalSearchDb,\n options: ZhihuClientOptions = {},\n): Promise<ZhihuGlobalSearchResult> {\n const trimmed = requireQuery(query, '搜索词不能为空。')\n const resolvedCount = clampInt(count, ZHIHU_GLOBAL_SEARCH_DEFAULT_COUNT, ZHIHU_GLOBAL_SEARCH_MAX_COUNT)\n const body = await zhihuFetchJson(GLOBAL_SEARCH_PATH, {\n params: { Query: trimmed, Count: String(resolvedCount), SearchDB: searchDb },\n }, '知乎全网搜索', options)\n const data = parseZhihuEnvelope(body) as { Items?: unknown; EmptyReason?: unknown }\n return {\n version: ZHIHU_GLOBAL_SEARCH_VERSION,\n query: trimmed,\n count: resolvedCount,\n searchDb,\n items: objectItems(data).map(normalizeSearchItem),\n emptyReason: typeof data.EmptyReason === 'string' ? data.EmptyReason : undefined,\n }\n}\n\nexport function renderZhihuGlobalSearch(result: ZhihuGlobalSearchResult): string {\n if (result.items.length === 0) {\n return `全网搜索「${result.query}」无结果${result.emptyReason ? `(${result.emptyReason})` : ''}。`\n }\n const lines: string[] = [`全网搜索「${result.query}」共 ${result.items.length} 条:`, '']\n result.items.forEach((item, index) => {\n lines.push(`${index + 1}. [${item.title}](${item.url || '(无链接)'})`)\n lines.push(` - 类型:${item.type} 作者:${item.author}`)\n if (item.summary) lines.push(` - 摘要:${item.summary}`)\n lines.push('')\n })\n return lines.join('\\n').trimEnd()\n}\n\n// ---------------------------------------------------------------------------\n// 热榜\n// ---------------------------------------------------------------------------\n\nexport type ZhihuHotListItem = {\n title: string\n url: string\n summary: string\n}\n\nexport type ZhihuHotListResult = {\n version: typeof ZHIHU_HOT_LIST_VERSION\n limit: number\n items: ZhihuHotListItem[]\n}\n\nconst ZHIHU_HOT_LIST_SUMMARY_MAX_CHARS = 200\n\nfunction normalizeHotItem(raw: Record<string, unknown>): ZhihuHotListItem {\n const summary = typeof raw.Summary === 'string' ? raw.Summary.trim() : ''\n return {\n title: typeof raw.Title === 'string' && raw.Title.length > 0 ? raw.Title : '(无标题)',\n url: typeof raw.Url === 'string' ? raw.Url : '',\n summary: summary.length > ZHIHU_HOT_LIST_SUMMARY_MAX_CHARS ? `${summary.slice(0, ZHIHU_HOT_LIST_SUMMARY_MAX_CHARS)}…` : summary,\n }\n}\n\nexport async function executeZhihuHotList(limit: number, options: ZhihuClientOptions = {}): Promise<ZhihuHotListResult> {\n const resolvedLimit = clampInt(limit, ZHIHU_HOT_LIST_DEFAULT_LIMIT, ZHIHU_HOT_LIST_MAX_LIMIT)\n const body = await zhihuFetchJson(HOT_LIST_PATH, {\n params: { Limit: String(resolvedLimit) },\n }, '知乎热榜', options)\n const items = objectItems(parseZhihuEnvelope(body)).map(normalizeHotItem)\n return { version: ZHIHU_HOT_LIST_VERSION, limit: resolvedLimit, items }\n}\n\nexport function renderZhihuHotList(result: ZhihuHotListResult): string {\n if (result.items.length === 0) return '知乎热榜为空。'\n const lines: string[] = ['知乎热榜:', '']\n result.items.forEach((item, index) => {\n lines.push(`${index + 1}. [${item.title}](${item.url || '(无链接)'})`)\n if (item.summary) lines.push(` - ${item.summary}`)\n lines.push('')\n })\n return lines.join('\\n').trimEnd()\n}\n\n// ---------------------------------------------------------------------------\n// 直答(OpenAI 兼容,非信封)\n// ---------------------------------------------------------------------------\n\nexport type ZhihuAskResult = {\n version: typeof ZHIHU_ASK_VERSION\n query: string\n model: ZhihuAskModel\n content: string\n reasoning: string\n}\n\nexport async function executeZhihuAsk(\n query: string,\n model: ZhihuAskModel,\n options: ZhihuClientOptions = {},\n): Promise<ZhihuAskResult> {\n const trimmed = requireQuery(query, '直答问题不能为空。')\n const body = await zhihuFetchJson(ASK_PATH, {\n method: 'POST',\n body: { model, messages: [{ role: 'user', content: trimmed }], stream: false },\n }, '知乎直答', { ...options, timeoutMs: options.timeoutMs ?? ZHIHU_ASK_TIMEOUT_MS })\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n throw new ZhihuSearchError('BAD_RESPONSE', '知乎直答响应不是 OpenAI 兼容格式。')\n }\n const payload = body as {\n error?: { message?: unknown }\n choices?: Array<{ message?: { content?: unknown; reasoning_content?: unknown } }>\n }\n if (payload.error) {\n const msg = typeof payload.error.message === 'string' ? payload.error.message : '未知错误'\n throw new ZhihuSearchError('HTTP_ERROR', `知乎直答返回错误:${msg}`)\n }\n const choice = Array.isArray(payload.choices) ? payload.choices[0] : undefined\n const message = choice?.message ?? {}\n const content = typeof message.content === 'string' ? message.content.trim() : ''\n const reasoning = typeof message.reasoning_content === 'string' ? message.reasoning_content.trim() : ''\n if (!content) throw new ZhihuSearchError('BAD_RESPONSE', '知乎直答返回为空。')\n return { version: ZHIHU_ASK_VERSION, query: trimmed, model, content, reasoning }\n}\n\nexport function renderZhihuAsk(result: ZhihuAskResult): string {\n const parts: string[] = []\n if (result.reasoning) parts.push(`【思考过程】\\n${result.reasoning}`)\n parts.push(result.content)\n return `知乎直答「${result.query}」:\\n\\n${parts.join('\\n\\n')}`\n}\n\n// ---------------------------------------------------------------------------\n// 公开知识库检索\n// ---------------------------------------------------------------------------\n\nexport type ZhihuKnowledgeItem = {\n docName: string\n originUrl: string\n snippets: string[]\n}\n\nexport type ZhihuRecallScope = 'personal' | 'subscription' | 'public'\nexport const ZHIHU_RECALL_SCOPES: readonly ZhihuRecallScope[] = ['personal', 'subscription', 'public']\n\nexport type ZhihuKnowledgeSearchResult = {\n version: typeof ZHIHU_KNOWLEDGE_SEARCH_VERSION\n query: string\n limit: number\n recallScopes: ZhihuRecallScope[]\n items: ZhihuKnowledgeItem[]\n}\n\nfunction normalizeKnowledgeItem(raw: Record<string, unknown>): ZhihuKnowledgeItem {\n const content = raw.Content\n const snippets = Array.isArray(content)\n ? content.map((snippet) => String(snippet).trim()).filter((snippet) => snippet.length > 0)\n : []\n return {\n docName: typeof raw.DocName === 'string' && raw.DocName.length > 0 ? raw.DocName : '(未命名文档)',\n originUrl: typeof raw.OriginUrl === 'string' ? raw.OriginUrl : '',\n snippets,\n }\n}\n\n/** 收敛召回范围:只保留合法档位、去重;为空或未提供时回退公开库。 */\nexport function normalizeRecallScopes(value: unknown): ZhihuRecallScope[] {\n if (!Array.isArray(value)) return ['public']\n const scopes = [...new Set(value.filter((scope): scope is ZhihuRecallScope => typeof scope === 'string' && (ZHIHU_RECALL_SCOPES as readonly string[]).includes(scope)))]\n return scopes.length > 0 ? scopes : ['public']\n}\n\nexport async function executeZhihuKnowledgeSearch(\n query: string,\n limit: number,\n recallScopes: ZhihuRecallScope[],\n options: ZhihuClientOptions = {},\n): Promise<ZhihuKnowledgeSearchResult> {\n const trimmed = requireQuery(query, '知识库检索词不能为空。')\n const resolvedLimit = clampInt(limit, ZHIHU_KNOWLEDGE_SEARCH_DEFAULT_LIMIT, ZHIHU_KNOWLEDGE_SEARCH_MAX_LIMIT)\n const body = await zhihuFetchJson(KNOWLEDGE_SEARCH_PATH, {\n method: 'POST',\n body: { Query: trimmed, Limit: resolvedLimit, RecallScopes: recallScopes },\n }, '知乎知识库检索', { ...options, timeoutMs: options.timeoutMs ?? 30_000 })\n const items = objectItems(parseZhihuEnvelope(body)).map(normalizeKnowledgeItem)\n return { version: ZHIHU_KNOWLEDGE_SEARCH_VERSION, query: trimmed, limit: resolvedLimit, recallScopes, items }\n}\n\nexport function renderZhihuKnowledgeSearch(result: ZhihuKnowledgeSearchResult): string {\n if (result.items.length === 0) return `知乎知识库检索「${result.query}」无匹配结果。`\n const lines: string[] = [`知乎知识库检索「${result.query}」共 ${result.items.length} 条:`, '']\n result.items.forEach((item, index) => {\n lines.push(`${index + 1}. ${item.docName}`)\n if (item.originUrl) lines.push(` - 来源:${item.originUrl}`)\n for (const snippet of item.snippets) lines.push(` - ${snippet}`)\n lines.push('')\n })\n return lines.join('\\n').trimEnd()\n}\n\n// ---------------------------------------------------------------------------\n// 工具定义\n// ---------------------------------------------------------------------------\n\n","/**\n * Contract source: klarkxy/zhihu-search@62fd3ce5, upstream/http_client.py.\n * Six question/creator APIs use the current Access Secret only, never OAuth.\n * This module has no DSH/UI dependencies; RPC and tools share these validators.\n */\nimport { zhihuFetchJson, ZhihuSearchError, type ZhihuClientOptions } from './zhihu-client.ts'\nimport { ZHIHU_QUOTA_IDS, type ZhihuQuotaId } from './contracts.ts'\n\nexport const ZHIHU_OPEN_PLATFORM_APIS = {\n 'question.recommendations': { path: '/api/v1/user/question_recommendations', label: '适合回答的问题', quotaId: 'creator' },\n 'question.answers': { path: '/api/v1/content/question_answers', label: '问题回答摘要', quotaId: 'question_answers' },\n 'content.detail': { path: '/api/v1/user/content_detail', label: '本人创作全文', quotaId: 'creator' },\n 'content.comments': { path: '/api/v1/user/content_comments', label: '本人创作评论', quotaId: 'creator' },\n 'creator.account.stats': { path: '/api/v1/user/creator_account_stats', label: '账号创作数据', quotaId: 'creator' },\n 'creator.content.stats': { path: '/api/v1/user/creator_content_stats', label: '单篇创作数据', quotaId: 'creator' },\n quota: { path: '/api/v1/quota', label: '官方每日额度', quotaId: undefined },\n} as const\nexport type ZhihuOpenPlatformOperation = keyof typeof ZHIHU_OPEN_PLATFORM_APIS\n\nconst ENDPOINT_ALIASES: Record<string, ZhihuOpenPlatformOperation> = {\n question_recommendations: 'question.recommendations', question_answers: 'question.answers',\n content_detail: 'content.detail', user_content_detail: 'content.detail', 'user.content.detail': 'content.detail',\n content_comments: 'content.comments', user_content_comments: 'content.comments', 'user.content.comments': 'content.comments',\n creator_account_stats: 'creator.account.stats', creator_content_stats: 'creator.content.stats',\n}\nexport function resolveZhihuOpenPlatformOperation(endpoint: string): ZhihuOpenPlatformOperation | undefined {\n if (Object.hasOwn(ZHIHU_OPEN_PLATFORM_APIS, endpoint)) return endpoint as ZhihuOpenPlatformOperation\n return Object.hasOwn(ENDPOINT_ALIASES, endpoint) ? ENDPOINT_ALIASES[endpoint] : undefined\n}\n\nexport type ZhihuPaging = {\n canContinue: boolean\n isEnd?: boolean\n /** Decimal Int64 string. Never synthesize this from the number of items. */\n nextOffset?: string\n warning?: string\n}\nexport type ZhihuJsonValue = string | number | boolean | null | ZhihuJsonValue[] | { [key: string]: ZhihuJsonValue }\nexport type ZhihuOpenPlatformResult = {\n version: 1\n operation: ZhihuOpenPlatformOperation\n quotaId?: ZhihuQuotaId\n /** Original Data fields. Quota's top-level array is wrapped in { Items }. */\n data: Record<string, ZhihuJsonValue>\n paging?: ZhihuPaging\n}\nexport type ZhihuPageInput = { offset?: number | string; limit?: number }\nexport type ZhihuDateInput = { startDate?: string; endDate?: string }\nexport type ZhihuCreatorContentType = 'all' | 'answer' | 'article' | 'pin' | 'zvideo'\nexport type ZhihuCommentOrder = 'score' | 'reverse' | 'ascending'\n\nconst record = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object' && !Array.isArray(value)\n// Keep the free-form tool result compatible with the SDK's JSON value contract.\nfunction jsonValue(value: unknown): value is ZhihuJsonValue {\n if (value === null || typeof value === 'string' || typeof value === 'boolean') return true\n if (typeof value === 'number') return Number.isFinite(value)\n if (Array.isArray(value)) return value.every(jsonValue)\n return record(value) && Object.values(value).every(jsonValue)\n}\nfunction jsonRecord(value: unknown): value is Record<string, ZhihuJsonValue> {\n return record(value) && Object.values(value).every(jsonValue)\n}\nfunction invalid(message: string): never { throw new ZhihuSearchError('INVALID_ARGUMENTS', message) }\n\nfunction inputObject(input: unknown): Record<string, unknown> {\n if (!record(input)) return invalid('参数必须是对象。')\n // Reject misleading identity overrides, even over raw RPC. Credentials belong to the host.\n for (const key of ['token', 'accessSecret', 'access_secret', 'accessToken', 'access_token', 'oauthToken', 'oauth_token', 'useConfiguredOauthUser', 'use_configured_oauth_user']) {\n if (Object.hasOwn(input, key)) invalid('这些接口仅使用当前 Access Secret,不接受 token 参数或 OAuth 身份切换。')\n }\n return input\n}\nfunction limit(value: unknown, fallback: number, maximum: number, name = 'limit'): string {\n const count = value === undefined ? fallback : value\n if (typeof count !== 'number' || !Number.isInteger(count) || count < 1 || count > maximum) {\n return invalid(`${name} 必须是 1-${maximum} 的整数。`)\n }\n return String(count)\n}\n\n/** Accept all nonnegative Int64 values as strings, but never accept an unsafe JS number. */\nexport function normalizeZhihuOffset(value: unknown): string {\n let decimal: string\n if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) decimal = String(value)\n else if (typeof value === 'string' && /^\\d+$/.test(value)) decimal = value.replace(/^0+(?=\\d)/, '')\n else return invalid('offset 必须是非负 Int64 整数;超过 JavaScript 安全整数范围时请用十进制字符串,不能使用不透明游标。')\n if (decimal.length > 19 || BigInt(decimal) > 9223372036854775807n) return invalid('offset 超出 Int64 范围。')\n return decimal\n}\n\nfunction zhihuUrl(value: unknown, question: boolean): string {\n if (typeof value !== 'string' || !value.trim()) return invalid(question ? 'questionUrl 不能为空。' : 'contentUrl 不能为空。')\n const cleaned = value.trim()\n if (/[\\u0000-\\u0020\\u007f\\\\]/.test(cleaned)) return invalid('知乎链接不能包含空白、控制字符或反斜线。')\n let url: URL\n try { url = new URL(cleaned) } catch { return invalid('不是有效的知乎 URL。') }\n if (url.protocol !== 'https:' || url.username || url.password || url.port) return invalid('必须使用无账号密码、无非标准端口的 HTTPS 知乎链接。')\n const valid = question\n ? url.hostname === 'www.zhihu.com' && /^\\/question\\/\\d+\\/?$/.test(url.pathname)\n : (url.hostname === 'www.zhihu.com' && /^\\/(?:answer\\/\\d+|question\\/\\d+\\/answer\\/\\d+|pin\\/\\d+|zvideo\\/\\d+)\\/?$/.test(url.pathname))\n || (url.hostname === 'zhuanlan.zhihu.com' && /^\\/p\\/\\d+\\/?$/.test(url.pathname))\n if (!valid) return invalid(question ? 'questionUrl 仅支持 https://www.zhihu.com/question/{id}。' : 'contentUrl 仅支持知乎回答、专栏文章、想法或视频链接。')\n return cleaned\n}\n\nfunction dates(input: Record<string, unknown>, params: Record<string, string>): void {\n const { startDate, endDate } = input\n if (startDate === undefined && endDate === undefined) return\n if (startDate === undefined || endDate === undefined) invalid('startDate 与 endDate 必须同时提供或同时省略。')\n for (const [key, value] of [['StartDate', startDate], ['EndDate', endDate]] as const) {\n if (typeof value !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value) || value.slice(0, 4) === '0000') invalid(`${key} 必须是有效的 YYYY-MM-DD 公历日期。`)\n const time = new Date(`${value}T00:00:00Z`)\n if (!Number.isFinite(time.getTime()) || time.toISOString().slice(0, 10) !== value) invalid(`${key} 不是有效公历日期。`)\n params[key] = value\n }\n if (params.EndDate! < params.StartDate!) invalid('endDate 不得早于 startDate。')\n}\n\nfunction paramsFor(operation: ZhihuOpenPlatformOperation, input: Record<string, unknown>): Record<string, string> {\n const params: Record<string, string> = {}\n if (operation === 'question.recommendations') {\n // Explicit null/blank is not the same as omitting Query.\n if (input.query !== undefined) {\n if (typeof input.query !== 'string' || !input.query.trim()) invalid('主题关键词不能为空;省略 query 才会按画像推荐。')\n params.Query = input.query.trim()\n }\n params.Count = limit(input.count, 5, 20, 'count')\n } else if (operation === 'question.answers') {\n params.QuestionUrl = zhihuUrl(input.questionUrl, true)\n } else if (operation === 'content.detail' || operation === 'content.comments' || operation === 'creator.content.stats') {\n params.ContentUrl = zhihuUrl(input.contentUrl, false)\n } else if (operation === 'creator.account.stats') {\n const type = input.contentType === undefined ? 'all' : input.contentType\n if (typeof type !== 'string' || !['all', 'answer', 'article', 'pin', 'zvideo'].includes(type)) invalid('contentType 必须是 all、answer、article、pin 或 zvideo。')\n params.ContentType = type\n } else if (operation === 'quota' && input.apiIds !== undefined) {\n if (!Array.isArray(input.apiIds) || input.apiIds.some(id => typeof id !== 'string' || !(ZHIHU_QUOTA_IDS as readonly string[]).includes(id))) invalid('apiIds 包含未知官方额度项。')\n if (input.apiIds.length) params.APIIDs = [...new Set(input.apiIds)].join(',')\n }\n if (operation === 'question.answers' || operation === 'content.comments') {\n params.Offset = normalizeZhihuOffset(input.offset === undefined ? 0 : input.offset)\n params.Limit = limit(input.limit, 20, 50)\n }\n if (operation === 'content.comments') {\n const order = input.order === undefined ? 'score' : input.order\n if (typeof order !== 'string' || !['score', 'reverse', 'ascending'].includes(order)) invalid('order 必须是 score、reverse 或 ascending。')\n params.Order = order\n }\n if (operation === 'creator.account.stats' || operation === 'creator.content.stats') dates(input, params)\n return params\n}\n\nexport function normalizeZhihuPaging(data: Record<string, unknown>, offset: string): ZhihuPaging {\n const paging = data.Paging\n if (!record(paging) || typeof paging.IsEnd !== 'boolean') {\n return { canContinue: false, warning: '分页信息不完整:缺少有效的 Paging.IsEnd,请停止翻页。' }\n }\n if (paging.IsEnd) return { isEnd: true, canContinue: false }\n try {\n const nextOffset = normalizeZhihuOffset(paging.NextOffset)\n if (BigInt(nextOffset) <= BigInt(offset)) throw new Error('non-advancing offset')\n return { isEnd: false, canContinue: true, nextOffset }\n } catch {\n return { isEnd: false, canContinue: false, warning: '分页信息不完整:NextOffset 缺失、无效或未前进,请停止翻页;不要按条数计算偏移。' }\n }\n}\n\nexport async function executeZhihuOpenPlatform(\n operation: ZhihuOpenPlatformOperation,\n input: unknown = {},\n options: ZhihuClientOptions = {},\n): Promise<ZhihuOpenPlatformResult> {\n const args = inputObject(input)\n const params = paramsFor(operation, args)\n const api = ZHIHU_OPEN_PLATFORM_APIS[operation]\n const raw = await zhihuFetchJson(api.path, { params, envelope: true }, api.label, options)\n const data = operation === 'quota' && Array.isArray(raw) ? { Items: raw } : raw\n if (!jsonRecord(data)) throw new ZhihuSearchError('BAD_RESPONSE', `${api.label}的 Data 必须是 JSON 对象。`)\n return {\n version: 1, operation, data,\n ...(api.quotaId ? { quotaId: api.quotaId } : {}),\n ...(operation === 'question.answers' || operation === 'content.comments'\n ? { paging: normalizeZhihuPaging(data, params.Offset!) } : {}),\n }\n}\n\n// Typed client entry points. Authentication stays in ZhihuClientOptions, never input.\nexport const executeZhihuQuestionRecommendations = (args: { query?: string; count?: number } = {}, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('question.recommendations', args, options)\nexport const executeZhihuQuestionAnswers = (args: { questionUrl: string } & ZhihuPageInput, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('question.answers', args, options)\nexport const executeZhihuUserContentDetail = (args: { contentUrl: string }, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('content.detail', args, options)\nexport const executeZhihuUserContentComments = (args: { contentUrl: string; order?: ZhihuCommentOrder } & ZhihuPageInput, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('content.comments', args, options)\nexport const executeZhihuCreatorAccountStats = (args: { contentType?: ZhihuCreatorContentType } & ZhihuDateInput = {}, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('creator.account.stats', args, options)\nexport const executeZhihuCreatorContentStats = (args: { contentUrl: string } & ZhihuDateInput, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('creator.content.stats', args, options)\nexport const executeZhihuQuota = (args: { apiIds?: ZhihuQuotaId[] } = {}, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('quota', args, options)\n\nexport function zhihuOpenPlatformResultCount(result: ZhihuOpenPlatformResult): number {\n if (result.operation === 'quota') return 0\n if (result.operation === 'content.detail') return typeof result.data.Body === 'string' && result.data.Body.trim() ? 1 : 0\n if (result.operation === 'creator.account.stats') return Object.keys(result.data).length ? 1 : 0\n return Array.isArray(result.data.Items) ? result.data.Items.filter(record).length : 0\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"open-platform.js","names":[],"sources":["../src/zhihu-client.ts","../src/search-api.ts","../src/operations.ts","../src/open-platform.ts"],"sourcesContent":["/**\n * 知乎开放平台共享请求层:Access Secret 解析、超时封装、信封解析与执行计量。\n *\n * 端点规范(来源:github.com/klarkxy/zhihu-search,src/zhihu_search/upstream/http_client.py):\n * - Base URL: https://developer.zhihu.com\n * - 公共 Headers: Authorization: Bearer <access_secret>、X-Request-Timestamp、Accept\n * - 除直答外的 REST 接口返回 {Code, Message, Data} 信封,Code 非 0 表示业务错误。\n *\n * Token 解析(按契约,**不**把 token 放进 schema 参数):\n * - 提供 credential 解析器时只使用它,缺失或无效时抛出 TOKEN_MISSING。\n * 宿主通过 `ctx.credentials.resolve` 统一管理凭据来源和清除操作。\n * - 未提供解析器的独立客户端依次兼容 ZHIHU_ACCESS_TOKEN、\n * ZHIHU_ACCESS_SECRET 和 ~/.config/zhihu-search/credentials.json。\n */\nimport { readFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { ZhihuQuotaId } from './contracts.ts'\n\nexport const ZHIHU_BASE_URL = 'https://developer.zhihu.com'\nexport const ZHIHU_DEFAULT_TIMEOUT_MS = 15_000\nexport const ZHIHU_SUMMARY_MAX_CHARS = 200\n\nconst ZHIHU_CREDENTIALS_PRIMARY = 'ZHIHU_ACCESS_TOKEN'\nconst ZHIHU_CREDENTIALS_FALLBACK = 'ZHIHU_ACCESS_SECRET'\nconst ZHIHU_CREDENTIALS_FILE = join(homedir(), '.config', 'zhihu-search', 'credentials.json')\n\ntype TokenSource = 'credential' | 'env-primary' | 'env-fallback' | 'file'\n\nexport type ZhihuSearchToken = { token: string; source: TokenSource }\n\nexport type ZhihuSearchFetcher = (input: string, init?: { method?: string; headers?: Record<string, string>; body?: string | FormData; signal?: AbortSignal }) => Promise<{\n ok: boolean\n status: number\n headers?: { get(name: string): string | null }\n text(): Promise<string>\n json(): Promise<unknown>\n}>\n\nconst globalFetch: ZhihuSearchFetcher | undefined = typeof globalThis !== 'undefined' && typeof (globalThis as { fetch?: unknown }).fetch === 'function'\n ? (input, init) => {\n const f = (globalThis as { fetch: typeof globalThis.fetch }).fetch\n return f(input as Parameters<typeof f>[0], init as Parameters<typeof f>[1] | undefined) as unknown as ReturnType<ZhihuSearchFetcher>\n }\n : undefined\n\nfunction looksLikeToken(value: string): boolean {\n const trimmed = value.trim()\n return trimmed.length >= 8 && trimmed.length <= 256\n}\n\nasync function readCredentialsFile(path: string): Promise<string | undefined> {\n try {\n const raw = await readFile(path, 'utf8')\n const parsed = JSON.parse(raw) as { access_secret?: unknown } | null\n if (parsed && typeof parsed === 'object' && typeof parsed.access_secret === 'string' && looksLikeToken(parsed.access_secret)) {\n return parsed.access_secret.trim()\n }\n } catch {\n // 文件不存在或 JSON 解析失败都不算硬错误,让上层回退到 isError 提示\n }\n return undefined\n}\n\nexport type ZhihuSearchResolveOptions = {\n /** Authoritative Access Secret resolver; absence/invalid values never fall back to env or CLI files. */\n resolveCredential?: () => Promise<string | undefined>\n}\n\nexport async function resolveZhihuToken(\n env: NodeJS.ProcessEnv = process.env,\n options: ZhihuSearchResolveOptions = {},\n): Promise<ZhihuSearchToken> {\n if (options.resolveCredential) {\n const value = await options.resolveCredential()\n const fromCredential = typeof value === 'string' ? value.trim() : undefined\n if (fromCredential && looksLikeToken(fromCredential)) {\n return { token: fromCredential, source: 'credential' }\n }\n throw new ZhihuSearchError('TOKEN_MISSING', '未配置有效的知乎 Access Secret。请在「插件 → 知乎 → 设置」中填写。')\n }\n const primary = env[ZHIHU_CREDENTIALS_PRIMARY]?.trim()\n if (primary && looksLikeToken(primary)) return { token: primary, source: 'env-primary' }\n const fallback = env[ZHIHU_CREDENTIALS_FALLBACK]?.trim()\n if (fallback && looksLikeToken(fallback)) return { token: fallback, source: 'env-fallback' }\n const fromFile = await readCredentialsFile(ZHIHU_CREDENTIALS_FILE)\n if (fromFile) return { token: fromFile, source: 'file' }\n throw new ZhihuSearchError('TOKEN_MISSING', [\n '未找到知乎 Access Secret。请在「设置 → 知乎」中填写;',\n `也可设置环境变量 ${ZHIHU_CREDENTIALS_PRIMARY} 或写入 ~/.config/zhihu-search/credentials.json。`,\n ].join(''))\n}\n\nexport type ZhihuErrorDetails = {\n upstreamCode?: number\n retryAfter?: number\n retryable?: boolean\n}\n\nexport class ZhihuSearchError extends Error {\n readonly code: 'TOKEN_MISSING' | 'HTTP_ERROR' | 'TIMEOUT' | 'BAD_RESPONSE'\n | 'INVALID_ARGUMENTS' | 'TOKEN_INVALID' | 'RATE_LIMITED' | 'UPSTREAM_UNAVAILABLE' | 'CANCELLED'\n readonly status?: number\n readonly upstreamCode?: number\n readonly retryAfter?: number\n readonly retryable?: boolean\n constructor(code: ZhihuSearchError['code'], message: string, status?: number, details: ZhihuErrorDetails = {}) {\n super(message)\n this.name = 'ZhihuSearchError'\n this.code = code\n if (status !== undefined) this.status = status\n if (details.upstreamCode !== undefined) this.upstreamCode = details.upstreamCode\n if (details.retryAfter !== undefined) this.retryAfter = details.retryAfter\n if (details.retryable !== undefined) this.retryable = details.retryable\n }\n}\n\n/** Metering payload emitted after each zhihu tool execution. */\nexport type ZhihuSearchExecuted = { ok: boolean; results: number; quotaId?: ZhihuQuotaId }\n\n/** Metering must never break the tool itself. */\nexport function reportExecuted(hook: ((event: ZhihuSearchExecuted) => void) | undefined, event: ZhihuSearchExecuted): void {\n if (!hook) return\n try {\n hook(event)\n } catch {\n // Best-effort metering; swallow hook failures.\n }\n}\n\nexport type ZhihuClientOptions = ZhihuSearchResolveOptions & {\n fetcher?: ZhihuSearchFetcher\n /** Allows tests to bypass process env; defaults to process.env. */\n env?: NodeJS.ProcessEnv\n signal?: AbortSignal\n /** Override the request timeout in ms. Defaults to {@link ZHIHU_DEFAULT_TIMEOUT_MS}. */\n timeoutMs?: number\n}\n\nfunction isAbortError(error: unknown): boolean {\n if (error instanceof Error && error.name === 'AbortError') return true\n return typeof error === 'object' && error !== null && (error as { name?: unknown }).name === 'AbortError'\n}\n\n/** Parse Retry-After seconds or an HTTP date; never invent a quota reset time. */\nexport function parseZhihuRetryAfter(value: string | null | undefined, now = Date.now()): number | undefined {\n if (!value?.trim()) return undefined\n const text = value.trim()\n if (/^\\d+(?:\\.\\d+)?$/.test(text)) {\n const seconds = Number(text)\n return Number.isFinite(seconds) ? Math.ceil(seconds) : undefined\n }\n // Do not let Date.parse interpret negative numbers as calendar dates.\n if (!/[a-z]/i.test(text)) return undefined\n const date = Date.parse(text)\n return Number.isFinite(date) ? Math.max(0, Math.ceil((date - now) / 1000)) : undefined\n}\n\n/**\n * Shared authenticated request. envelope=true opts into official error semantics\n * and returns Data. Legacy callers still receive the full JSON / chat response.\n * The timeout and cancellation cover BOTH headers and response-body consumption.\n * No requests are retried, including 429, exhausted quota, or risk-control denial.\n */\nexport async function zhihuFetchJson(\n path: string,\n init: { method?: 'GET' | 'POST'; params?: Record<string, string>; body?: unknown; formData?: FormData; envelope?: boolean },\n label: string,\n options: ZhihuClientOptions = {},\n): Promise<unknown> {\n const linked = options.signal\n if (linked?.aborted) throw new ZhihuSearchError('CANCELLED', '请求已取消')\n const url = new URL(path, ZHIHU_BASE_URL)\n if (url.origin !== ZHIHU_BASE_URL || url.username || url.password) {\n throw new ZhihuSearchError('INVALID_ARGUMENTS', '只允许请求知乎开放平台。')\n }\n for (const [key, value] of Object.entries(init.params ?? {})) url.searchParams.set(key, value)\n const token = await resolveZhihuToken(options.env, { resolveCredential: options.resolveCredential })\n if (linked?.aborted) throw new ZhihuSearchError('CANCELLED', '请求已取消')\n const fetcher = options.fetcher ?? globalFetch\n if (!fetcher) throw new ZhihuSearchError('BAD_RESPONSE', '当前环境没有可用的 fetch。')\n const method = init.method ?? (init.formData || init.body !== undefined ? 'POST' : 'GET')\n const timeout = options.timeoutMs !== undefined && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0\n ? options.timeoutMs : ZHIHU_DEFAULT_TIMEOUT_MS\n const controller = new AbortController()\n const onAbort = () => controller.abort(linked?.reason)\n if (linked?.aborted) onAbort()\n else linked?.addEventListener('abort', onAbort, { once: true })\n const timer = setTimeout(() => controller.abort(new Error('zhihu request timeout')), timeout)\n let stopWaiting = () => {}\n const aborted = new Promise<never>((_resolve, reject) => {\n const rejectAbort = () => reject(controller.signal.reason)\n if (controller.signal.aborted) rejectAbort()\n else {\n controller.signal.addEventListener('abort', rejectAbort, { once: true })\n stopWaiting = () => controller.signal.removeEventListener('abort', rejectAbort)\n }\n })\n try {\n const request = async () => {\n controller.signal.throwIfAborted()\n const response = await fetcher(url.toString(), {\n method,\n headers: {\n Authorization: `Bearer ${token.token}`,\n 'X-Request-Timestamp': String(Math.floor(Date.now() / 1000)),\n Accept: 'application/json',\n // Let fetch supply the multipart boundary.\n ...(method === 'POST' && !init.formData ? { 'Content-Type': 'application/json' } : {}),\n },\n signal: controller.signal,\n ...(init.formData ? { body: init.formData } : init.body !== undefined ? { body: JSON.stringify(init.body) } : {}),\n })\n const retryAfter = parseZhihuRetryAfter(response.headers?.get('Retry-After'))\n const httpError = () => {\n const code = response.status === 429 ? 'RATE_LIMITED'\n : init.envelope && (response.status === 401 || response.status === 403) ? 'TOKEN_INVALID'\n : 'HTTP_ERROR'\n return new ZhihuSearchError(code, `${label}返回 HTTP ${response.status}。`, response.status,\n code === 'RATE_LIMITED' ? { retryAfter, retryable: true } : {})\n }\n let body: unknown\n try { body = await response.json() }\n catch (error) {\n if (controller.signal.aborted) throw error\n if (!response.ok) throw httpError()\n throw new ZhihuSearchError('BAD_RESPONSE', `${label}响应不是合法 JSON。`)\n }\n controller.signal.throwIfAborted()\n // A business error remains meaningful even when the HTTP status is 400.\n const isEnvelope = body !== null && typeof body === 'object' && !Array.isArray(body) && 'Code' in body\n if (!response.ok && !isEnvelope) throw httpError()\n if (init.envelope || (isEnvelope && (body as { Code: unknown }).Code !== 0 && (body as { Code: unknown }).Code !== '0')) {\n const data = parseZhihuEnvelope(body, { status: response.status, retryAfter, officialErrors: init.envelope })\n if (!response.ok) throw httpError()\n if (init.envelope) return data\n }\n if (!response.ok) throw httpError()\n return body\n }\n return await Promise.race([request(), aborted])\n } catch (error) {\n if (linked?.aborted) throw new ZhihuSearchError('CANCELLED', '请求已取消')\n if (controller.signal.aborted || isAbortError(error)) {\n throw new ZhihuSearchError('TIMEOUT', `${label}超时(${Math.round(timeout / 1000)} 秒),请稍后再试。`)\n }\n if (error instanceof ZhihuSearchError) {\n error.message = error.message.replaceAll(token.token, '[REDACTED]')\n throw error\n }\n const message = (error instanceof Error ? error.message : String(error)).replaceAll(token.token, '[REDACTED]')\n throw new ZhihuSearchError('BAD_RESPONSE', `${label}失败:${message}`)\n } finally {\n clearTimeout(timer)\n stopWaiting()\n linked?.removeEventListener('abort', onAbort)\n }\n}\n\nconst ZHIHU_BUSINESS_ERRORS: Record<number, ZhihuSearchError['code']> = {\n 10001: 'INVALID_ARGUMENTS', 20001: 'TOKEN_INVALID',\n 30001: 'RATE_LIMITED', 30002: 'RATE_LIMITED',\n 40001: 'INVALID_ARGUMENTS', 40002: 'INVALID_ARGUMENTS', 40003: 'RATE_LIMITED',\n 40004: 'INVALID_ARGUMENTS', 40005: 'INVALID_ARGUMENTS', 40006: 'INVALID_ARGUMENTS',\n 50002: 'UPSTREAM_UNAVAILABLE', 90001: 'UPSTREAM_UNAVAILABLE',\n}\n\n/** Parse the official envelope without coercing null/false/empty Code into success. */\nexport function parseZhihuEnvelope(body: unknown, meta: { status?: number; retryAfter?: number; officialErrors?: boolean } = {}): unknown {\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n throw new ZhihuSearchError('BAD_RESPONSE', '知乎响应缺少 {Code,Message,Data} 信封。', meta.status)\n }\n const envelope = body as { Code?: unknown; Message?: unknown; Data?: unknown }\n const rawCode = envelope.Code\n if (!((typeof rawCode === 'number' && Number.isSafeInteger(rawCode))\n || (typeof rawCode === 'string' && /^\\d+$/.test(rawCode) && Number.isSafeInteger(Number(rawCode))))) {\n throw new ZhihuSearchError('BAD_RESPONSE', '知乎响应缺少有效的 Code 字段。', meta.status)\n }\n const code = Number(rawCode)\n if (code !== 0) {\n const message = typeof envelope.Message === 'string' && envelope.Message ? envelope.Message : '未知错误'\n if (code === 30003) {\n throw new ZhihuSearchError('UPSTREAM_UNAVAILABLE', `知乎开放平台被风控拒绝:${message}。请不要立即重试。`,\n meta.status, { upstreamCode: code, retryable: false })\n }\n const kind = ZHIHU_BUSINESS_ERRORS[code] ?? (meta.officialErrors ? 'UPSTREAM_UNAVAILABLE' : 'HTTP_ERROR')\n throw new ZhihuSearchError(kind, `知乎开放平台返回错误(code=${code}):${message}`, meta.status,\n { upstreamCode: code, ...(kind === 'RATE_LIMITED' ? { retryAfter: meta.retryAfter, retryable: true } : {}) })\n }\n return envelope.Data ?? {}\n}\n\nfunction truncateSummary(text: string, limit: number): string {\n if (text.length <= limit) return text\n return `${text.slice(0, limit)}…`\n}\n\n/** 搜索类接口(站内/全网)共用的条目结构。 */\nexport type ZhihuSearchItem = {\n title: string\n type: string\n url: string\n summary: string\n votes: number\n comments: number\n author: string\n authority: string\n editTime: string\n}\n\nexport function normalizeSearchItem(raw: Record<string, unknown>): ZhihuSearchItem {\n const summary = typeof raw.ContentText === 'string' ? raw.ContentText : ''\n return {\n title: typeof raw.Title === 'string' && raw.Title.length > 0 ? raw.Title : '(无标题)',\n type: typeof raw.ContentType === 'string' && raw.ContentType.length > 0 ? raw.ContentType : '内容',\n url: typeof raw.Url === 'string' ? raw.Url : '',\n summary: truncateSummary(summary.trim(), ZHIHU_SUMMARY_MAX_CHARS),\n votes: typeof raw.VoteUpCount === 'number' ? raw.VoteUpCount : 0,\n comments: typeof raw.CommentCount === 'number' ? raw.CommentCount : 0,\n author: typeof raw.AuthorName === 'string' && raw.AuthorName.length > 0 ? raw.AuthorName : '匿名',\n authority: typeof raw.AuthorityLevel === 'string' && raw.AuthorityLevel.length > 0 ? raw.AuthorityLevel : '?',\n editTime: typeof raw.EditTime === 'number' ? String(raw.EditTime) : '',\n }\n}\n","/**\n * 知乎开放平台站内搜索工具。\n *\n * 端点调研结论(来源:github.com/klarkxy/zhihu-search,src/zhihu_search/upstream/http_client.py):\n * - 知乎站内搜索:GET /api/v1/content/zhihu_search?Query=<q>&Count=<n>\n * - count 上限 10\n * - 响应信封:{ Code, Message, Data };Code 非 0 表示业务错误。\n * - Data.Items: 每条 { Title, ContentType, Url, ContentText, VoteUpCount,\n * CommentCount, AuthorName, AuthorityLevel, EditTime }。\n *\n * 鉴权、超时、信封解析与计量走 ./zhihu-client.ts 的共享实现;此处只保留\n * 站内搜索的参数收敛、结果规整与工具定义。\n */\nimport { ZHIHU_SEARCH_TOOL_NAME } from './contracts.ts'\nimport {\n normalizeSearchItem,\n parseZhihuEnvelope,\n reportExecuted,\n zhihuFetchJson,\n ZhihuSearchError,\n type ZhihuClientOptions,\n type ZhihuSearchItem,\n} from './zhihu-client.ts'\n\nexport { ZHIHU_SEARCH_TOOL_NAME } from './contracts.ts'\nexport {\n resolveZhihuToken,\n ZhihuSearchError,\n type ZhihuSearchExecuted,\n type ZhihuSearchFetcher,\n type ZhihuSearchItem,\n type ZhihuSearchResolveOptions,\n type ZhihuSearchToken,\n} from './zhihu-client.ts'\nexport const ZHIHU_SEARCH_VERSION = 1\nexport const ZHIHU_SEARCH_DEFAULT_COUNT = 5\nexport const ZHIHU_SEARCH_MAX_COUNT = 10\nexport const ZHIHU_SEARCH_TIMEOUT_MS = 15_000\nexport const ZHIHU_SUMMARY_MAX_CHARS = 200\n\nconst ZHIHU_SEARCH_PATH = '/api/v1/content/zhihu_search'\n\nexport type ZhihuSearchResult = {\n version: typeof ZHIHU_SEARCH_VERSION\n query: string\n count: number\n items: ZhihuSearchItem[]\n emptyReason?: string\n}\n\nexport type ZhihuSearchExecuteOptions = ZhihuClientOptions\n\nfunction clampCount(value: unknown): number {\n const n = typeof value === 'number' && Number.isFinite(value) ? Math.trunc(value) : ZHIHU_SEARCH_DEFAULT_COUNT\n if (n < 1) return 1\n if (n > ZHIHU_SEARCH_MAX_COUNT) return ZHIHU_SEARCH_MAX_COUNT\n return n\n}\n\nfunction filterItems(data: unknown): { items: Record<string, unknown>[]; emptyReason?: string } {\n const row = (data ?? {}) as { Items?: unknown; EmptyReason?: unknown }\n const items = Array.isArray(row.Items) ? row.Items.filter((item): item is Record<string, unknown> => !!item && typeof item === 'object' && !Array.isArray(item)) : []\n const emptyReason = typeof row.EmptyReason === 'string' ? row.EmptyReason : undefined\n return { items, emptyReason }\n}\n\nexport async function executeZhihuSearch(\n query: string,\n count: number,\n options: ZhihuSearchExecuteOptions = {},\n): Promise<ZhihuSearchResult> {\n const trimmed = query.trim()\n if (!trimmed) throw new ZhihuSearchError('BAD_RESPONSE', '搜索词不能为空。')\n const resolvedCount = clampCount(count)\n const body = await zhihuFetchJson(ZHIHU_SEARCH_PATH, {\n params: { Query: trimmed, Count: String(resolvedCount) },\n }, '知乎搜索', options)\n const { items, emptyReason } = filterItems(parseZhihuEnvelope(body))\n return {\n version: ZHIHU_SEARCH_VERSION,\n query: trimmed,\n count: resolvedCount,\n items: items.map(normalizeSearchItem),\n emptyReason,\n }\n}\n\nexport function renderZhihuSearch(result: ZhihuSearchResult): string {\n if (result.items.length === 0) {\n return `知乎站内搜索「${result.query}」无结果${result.emptyReason ? `(${result.emptyReason})` : ''}。`\n }\n const lines: string[] = [`知乎站内搜索「${result.query}」共 ${result.items.length} 条:`, '']\n result.items.forEach((item, index) => {\n lines.push(`${index + 1}. [${item.title}](${item.url || '(无链接)'})`)\n lines.push(` - 类型:${item.type} 作者:${item.author} 权威:${item.authority}`)\n if (item.editTime) lines.push(` - 时间:${item.editTime}`)\n lines.push(` - 赞同 ${item.votes} 评论 ${item.comments}`)\n if (item.summary) lines.push(` - 摘要:${item.summary}`)\n lines.push('')\n })\n return lines.join('\\n').trimEnd()\n}\n\n","/**\n * 知乎开放平台的其余写作向能力:全网搜索、热榜、直答、公开知识库检索。\n * 共享 ./zhihu-client.ts 的鉴权、超时、信封解析与计量;每个工具只做参数收敛、\n * 结果规整与渲染。结果一律只作社区参考,不构成 canon,也不写入项目文件。\n *\n * 端点(来源:github.com/klarkxy/zhihu-search,src/zhihu_search/upstream/http_client.py):\n * - 全网搜索:GET /api/v1/content/global_search?Query&Count(<=20)&SearchDB(all|realtime|static)\n * - 热榜:GET /api/v1/content/hot_list?Limit(<=30)\n * - 直答:POST /v1/chat/completions(OpenAI 兼容,非信封;模型 zhida-fast-1p5 / zhida-thinking-1p5)\n * - 知识库检索:POST /api/v1/knowledge/search { Query, Limit(<=10), RecallScopes }\n */\nimport {\n ZHIHU_ASK_TOOL_NAME,\n ZHIHU_GLOBAL_SEARCH_TOOL_NAME,\n ZHIHU_HOT_LIST_TOOL_NAME,\n ZHIHU_KNOWLEDGE_SEARCH_TOOL_NAME,\n} from './contracts.ts'\nimport {\n normalizeSearchItem,\n parseZhihuEnvelope,\n reportExecuted,\n zhihuFetchJson,\n ZhihuSearchError,\n type ZhihuClientOptions,\n type ZhihuSearchExecuted,\n type ZhihuSearchItem,\n} from './zhihu-client.ts'\n\nexport const ZHIHU_GLOBAL_SEARCH_VERSION = 1\nexport const ZHIHU_GLOBAL_SEARCH_DEFAULT_COUNT = 10\nexport const ZHIHU_GLOBAL_SEARCH_MAX_COUNT = 20\nexport const ZHIHU_HOT_LIST_VERSION = 1\nexport const ZHIHU_HOT_LIST_DEFAULT_LIMIT = 10\nexport const ZHIHU_HOT_LIST_MAX_LIMIT = 30\nexport const ZHIHU_ASK_VERSION = 1\nexport const ZHIHU_ASK_TIMEOUT_MS = 120_000\nexport const ZHIHU_KNOWLEDGE_SEARCH_VERSION = 1\nexport const ZHIHU_KNOWLEDGE_SEARCH_DEFAULT_LIMIT = 5\nexport const ZHIHU_KNOWLEDGE_SEARCH_MAX_LIMIT = 10\n\nconst GLOBAL_SEARCH_PATH = '/api/v1/content/global_search'\nconst HOT_LIST_PATH = '/api/v1/content/hot_list'\nconst ASK_PATH = '/v1/chat/completions'\nconst KNOWLEDGE_SEARCH_PATH = '/api/v1/knowledge/search'\n\nexport const ZHIHU_ASK_MODELS = ['zhida-fast-1p5', 'zhida-thinking-1p5', 'zhida-agent'] as const\nexport type ZhihuAskModel = (typeof ZHIHU_ASK_MODELS)[number]\n/** 直答的价值在于比本机模型更强的综合考据,默认用思考档;fast 只配简单事实查询。 */\nexport const ZHIHU_ASK_DEFAULT_MODEL: ZhihuAskModel = 'zhida-thinking-1p5'\n\nexport type ZhihuToolOptions = ZhihuClientOptions & {\n /** Best-effort metering hook; invoked after every execution, failures included. */\n onExecuted?: (event: ZhihuSearchExecuted) => void\n}\n\nfunction clampInt(value: unknown, fallback: number, max: number): number {\n const n = typeof value === 'number' && Number.isFinite(value) ? Math.trunc(value) : fallback\n if (n < 1) return 1\n if (n > max) return max\n return n\n}\n\nfunction requireQuery(query: string, hint: string): string {\n const trimmed = query.trim()\n if (!trimmed) throw new ZhihuSearchError('BAD_RESPONSE', hint)\n return trimmed\n}\n\nfunction objectItems(data: unknown, key = 'Items'): Record<string, unknown>[] {\n const row = (data ?? {}) as Record<string, unknown>\n const items = row[key]\n return Array.isArray(items) ? items.filter((item): item is Record<string, unknown> => !!item && typeof item === 'object' && !Array.isArray(item)) : []\n}\n\n// ---------------------------------------------------------------------------\n// 全网搜索\n// ---------------------------------------------------------------------------\n\nexport type ZhihuGlobalSearchDb = 'all' | 'realtime' | 'static'\n\nexport type ZhihuGlobalSearchResult = {\n version: typeof ZHIHU_GLOBAL_SEARCH_VERSION\n query: string\n count: number\n searchDb: ZhihuGlobalSearchDb\n items: ZhihuSearchItem[]\n emptyReason?: string\n}\n\nexport async function executeZhihuGlobalSearch(\n query: string,\n count: number,\n searchDb: ZhihuGlobalSearchDb,\n options: ZhihuClientOptions = {},\n): Promise<ZhihuGlobalSearchResult> {\n const trimmed = requireQuery(query, '搜索词不能为空。')\n const resolvedCount = clampInt(count, ZHIHU_GLOBAL_SEARCH_DEFAULT_COUNT, ZHIHU_GLOBAL_SEARCH_MAX_COUNT)\n const body = await zhihuFetchJson(GLOBAL_SEARCH_PATH, {\n params: { Query: trimmed, Count: String(resolvedCount), SearchDB: searchDb },\n }, '知乎全网搜索', options)\n const data = parseZhihuEnvelope(body) as { Items?: unknown; EmptyReason?: unknown }\n return {\n version: ZHIHU_GLOBAL_SEARCH_VERSION,\n query: trimmed,\n count: resolvedCount,\n searchDb,\n items: objectItems(data).map(normalizeSearchItem),\n emptyReason: typeof data.EmptyReason === 'string' ? data.EmptyReason : undefined,\n }\n}\n\nexport function renderZhihuGlobalSearch(result: ZhihuGlobalSearchResult): string {\n if (result.items.length === 0) {\n return `全网搜索「${result.query}」无结果${result.emptyReason ? `(${result.emptyReason})` : ''}。`\n }\n const lines: string[] = [`全网搜索「${result.query}」共 ${result.items.length} 条:`, '']\n result.items.forEach((item, index) => {\n lines.push(`${index + 1}. [${item.title}](${item.url || '(无链接)'})`)\n lines.push(` - 类型:${item.type} 作者:${item.author}`)\n if (item.summary) lines.push(` - 摘要:${item.summary}`)\n lines.push('')\n })\n return lines.join('\\n').trimEnd()\n}\n\n// ---------------------------------------------------------------------------\n// 热榜\n// ---------------------------------------------------------------------------\n\nexport type ZhihuHotListItem = {\n title: string\n url: string\n summary: string\n}\n\nexport type ZhihuHotListResult = {\n version: typeof ZHIHU_HOT_LIST_VERSION\n limit: number\n items: ZhihuHotListItem[]\n}\n\nconst ZHIHU_HOT_LIST_SUMMARY_MAX_CHARS = 200\n\nfunction normalizeHotItem(raw: Record<string, unknown>): ZhihuHotListItem {\n const summary = typeof raw.Summary === 'string' ? raw.Summary.trim() : ''\n return {\n title: typeof raw.Title === 'string' && raw.Title.length > 0 ? raw.Title : '(无标题)',\n url: typeof raw.Url === 'string' ? raw.Url : '',\n summary: summary.length > ZHIHU_HOT_LIST_SUMMARY_MAX_CHARS ? `${summary.slice(0, ZHIHU_HOT_LIST_SUMMARY_MAX_CHARS)}…` : summary,\n }\n}\n\nexport async function executeZhihuHotList(limit: number, options: ZhihuClientOptions = {}): Promise<ZhihuHotListResult> {\n const resolvedLimit = clampInt(limit, ZHIHU_HOT_LIST_DEFAULT_LIMIT, ZHIHU_HOT_LIST_MAX_LIMIT)\n const body = await zhihuFetchJson(HOT_LIST_PATH, {\n params: { Limit: String(resolvedLimit) },\n }, '知乎热榜', options)\n const items = objectItems(parseZhihuEnvelope(body)).map(normalizeHotItem)\n return { version: ZHIHU_HOT_LIST_VERSION, limit: resolvedLimit, items }\n}\n\nexport function renderZhihuHotList(result: ZhihuHotListResult): string {\n if (result.items.length === 0) return '知乎热榜为空。'\n const lines: string[] = ['知乎热榜:', '']\n result.items.forEach((item, index) => {\n lines.push(`${index + 1}. [${item.title}](${item.url || '(无链接)'})`)\n if (item.summary) lines.push(` - ${item.summary}`)\n lines.push('')\n })\n return lines.join('\\n').trimEnd()\n}\n\n// ---------------------------------------------------------------------------\n// 直答(OpenAI 兼容,非信封)\n// ---------------------------------------------------------------------------\n\nexport type ZhihuAskResult = {\n version: typeof ZHIHU_ASK_VERSION\n query: string\n model: ZhihuAskModel\n content: string\n reasoning: string\n}\n\nexport async function executeZhihuAsk(\n query: string,\n model: ZhihuAskModel,\n options: ZhihuClientOptions = {},\n): Promise<ZhihuAskResult> {\n const trimmed = requireQuery(query, '直答问题不能为空。')\n const body = await zhihuFetchJson(ASK_PATH, {\n method: 'POST',\n body: { model, messages: [{ role: 'user', content: trimmed }], stream: false },\n }, '知乎直答', { ...options, timeoutMs: options.timeoutMs ?? ZHIHU_ASK_TIMEOUT_MS })\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n throw new ZhihuSearchError('BAD_RESPONSE', '知乎直答响应不是 OpenAI 兼容格式。')\n }\n const payload = body as {\n error?: { message?: unknown }\n choices?: Array<{ message?: { content?: unknown; reasoning_content?: unknown } }>\n }\n if (payload.error) {\n const msg = typeof payload.error.message === 'string' ? payload.error.message : '未知错误'\n throw new ZhihuSearchError('HTTP_ERROR', `知乎直答返回错误:${msg}`)\n }\n const choice = Array.isArray(payload.choices) ? payload.choices[0] : undefined\n const message = choice?.message ?? {}\n const content = typeof message.content === 'string' ? message.content.trim() : ''\n const reasoning = typeof message.reasoning_content === 'string' ? message.reasoning_content.trim() : ''\n if (!content) throw new ZhihuSearchError('BAD_RESPONSE', '知乎直答返回为空。')\n return { version: ZHIHU_ASK_VERSION, query: trimmed, model, content, reasoning }\n}\n\nexport function renderZhihuAsk(result: ZhihuAskResult): string {\n const parts: string[] = []\n if (result.reasoning) parts.push(`【思考过程】\\n${result.reasoning}`)\n parts.push(result.content)\n return `知乎直答「${result.query}」:\\n\\n${parts.join('\\n\\n')}`\n}\n\n// ---------------------------------------------------------------------------\n// 公开知识库检索\n// ---------------------------------------------------------------------------\n\nexport type ZhihuKnowledgeItem = {\n docName: string\n originUrl: string\n snippets: string[]\n}\n\nexport type ZhihuRecallScope = 'personal' | 'subscription' | 'public'\nexport const ZHIHU_RECALL_SCOPES: readonly ZhihuRecallScope[] = ['personal', 'subscription', 'public']\n\nexport type ZhihuKnowledgeSearchResult = {\n version: typeof ZHIHU_KNOWLEDGE_SEARCH_VERSION\n query: string\n limit: number\n recallScopes: ZhihuRecallScope[]\n items: ZhihuKnowledgeItem[]\n}\n\nfunction normalizeKnowledgeItem(raw: Record<string, unknown>): ZhihuKnowledgeItem {\n const content = raw.Content\n const snippets = Array.isArray(content)\n ? content.map((snippet) => String(snippet).trim()).filter((snippet) => snippet.length > 0)\n : []\n return {\n docName: typeof raw.DocName === 'string' && raw.DocName.length > 0 ? raw.DocName : '(未命名文档)',\n originUrl: typeof raw.OriginUrl === 'string' ? raw.OriginUrl : '',\n snippets,\n }\n}\n\n/** 收敛召回范围:只保留合法档位、去重;为空或未提供时回退公开库。 */\nexport function normalizeRecallScopes(value: unknown): ZhihuRecallScope[] {\n if (!Array.isArray(value)) return ['public']\n const scopes = [...new Set(value.filter((scope): scope is ZhihuRecallScope => typeof scope === 'string' && (ZHIHU_RECALL_SCOPES as readonly string[]).includes(scope)))]\n return scopes.length > 0 ? scopes : ['public']\n}\n\nexport async function executeZhihuKnowledgeSearch(\n query: string,\n limit: number,\n recallScopes: ZhihuRecallScope[],\n options: ZhihuClientOptions = {},\n): Promise<ZhihuKnowledgeSearchResult> {\n const trimmed = requireQuery(query, '知识库检索词不能为空。')\n const resolvedLimit = clampInt(limit, ZHIHU_KNOWLEDGE_SEARCH_DEFAULT_LIMIT, ZHIHU_KNOWLEDGE_SEARCH_MAX_LIMIT)\n const body = await zhihuFetchJson(KNOWLEDGE_SEARCH_PATH, {\n method: 'POST',\n body: { Query: trimmed, Limit: resolvedLimit, RecallScopes: recallScopes },\n }, '知乎知识库检索', { ...options, timeoutMs: options.timeoutMs ?? 30_000 })\n const items = objectItems(parseZhihuEnvelope(body)).map(normalizeKnowledgeItem)\n return { version: ZHIHU_KNOWLEDGE_SEARCH_VERSION, query: trimmed, limit: resolvedLimit, recallScopes, items }\n}\n\nexport function renderZhihuKnowledgeSearch(result: ZhihuKnowledgeSearchResult): string {\n if (result.items.length === 0) return `知乎知识库检索「${result.query}」无匹配结果。`\n const lines: string[] = [`知乎知识库检索「${result.query}」共 ${result.items.length} 条:`, '']\n result.items.forEach((item, index) => {\n lines.push(`${index + 1}. ${item.docName}`)\n if (item.originUrl) lines.push(` - 来源:${item.originUrl}`)\n for (const snippet of item.snippets) lines.push(` - ${snippet}`)\n lines.push('')\n })\n return lines.join('\\n').trimEnd()\n}\n\n// ---------------------------------------------------------------------------\n// 工具定义\n// ---------------------------------------------------------------------------\n\n","/**\n * Contract source: klarkxy/zhihu-search@62fd3ce5, upstream/http_client.py.\n * Six question/creator APIs use the current Access Secret only, never OAuth.\n * This module has no DSH/UI dependencies; RPC and tools share these validators.\n */\nimport { zhihuFetchJson, ZhihuSearchError, type ZhihuClientOptions } from './zhihu-client.ts'\nimport { ZHIHU_QUOTA_IDS, type ZhihuQuotaId } from './contracts.ts'\n\nexport const ZHIHU_OPEN_PLATFORM_APIS = {\n 'question.recommendations': { path: '/api/v1/user/question_recommendations', label: '适合回答的问题', quotaId: 'creator' },\n 'question.answers': { path: '/api/v1/content/question_answers', label: '问题回答摘要', quotaId: 'question_answers' },\n 'content.detail': { path: '/api/v1/user/content_detail', label: '本人创作全文', quotaId: 'creator' },\n 'content.comments': { path: '/api/v1/user/content_comments', label: '本人创作评论', quotaId: 'creator' },\n 'creator.account.stats': { path: '/api/v1/user/creator_account_stats', label: '账号创作数据', quotaId: 'creator' },\n 'creator.content.stats': { path: '/api/v1/user/creator_content_stats', label: '单篇创作数据', quotaId: 'creator' },\n quota: { path: '/api/v1/quota', label: '官方每日额度', quotaId: undefined },\n} as const\nexport type ZhihuOpenPlatformOperation = keyof typeof ZHIHU_OPEN_PLATFORM_APIS\n\nconst ENDPOINT_ALIASES: Record<string, ZhihuOpenPlatformOperation> = {\n question_recommendations: 'question.recommendations', question_answers: 'question.answers',\n content_detail: 'content.detail', user_content_detail: 'content.detail', 'user.content.detail': 'content.detail',\n content_comments: 'content.comments', user_content_comments: 'content.comments', 'user.content.comments': 'content.comments',\n creator_account_stats: 'creator.account.stats', creator_content_stats: 'creator.content.stats',\n}\nexport function resolveZhihuOpenPlatformOperation(endpoint: string): ZhihuOpenPlatformOperation | undefined {\n if (Object.hasOwn(ZHIHU_OPEN_PLATFORM_APIS, endpoint)) return endpoint as ZhihuOpenPlatformOperation\n return Object.hasOwn(ENDPOINT_ALIASES, endpoint) ? ENDPOINT_ALIASES[endpoint] : undefined\n}\n\nexport type ZhihuPaging = {\n canContinue: boolean\n isEnd?: boolean\n /** Decimal Int64 string. Never synthesize this from the number of items. */\n nextOffset?: string\n warning?: string\n}\nexport type ZhihuJsonValue = string | number | boolean | null | ZhihuJsonValue[] | { [key: string]: ZhihuJsonValue }\nexport type ZhihuOpenPlatformResult = {\n version: 1\n operation: ZhihuOpenPlatformOperation\n quotaId?: ZhihuQuotaId\n /** Original Data fields. Quota's top-level array is wrapped in { Items }. */\n data: Record<string, ZhihuJsonValue>\n paging?: ZhihuPaging\n}\nexport type ZhihuPageInput = { offset?: number | string; limit?: number }\nexport type ZhihuDateInput = { startDate?: string; endDate?: string }\nexport type ZhihuCreatorContentType = 'all' | 'answer' | 'article' | 'pin' | 'zvideo'\nexport type ZhihuCommentOrder = 'score' | 'reverse' | 'ascending'\n\nconst record = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object' && !Array.isArray(value)\n// Keep the free-form tool result compatible with the SDK's JSON value contract.\nfunction jsonValue(value: unknown): value is ZhihuJsonValue {\n if (value === null || typeof value === 'string' || typeof value === 'boolean') return true\n if (typeof value === 'number') return Number.isFinite(value)\n if (Array.isArray(value)) return value.every(jsonValue)\n return record(value) && Object.values(value).every(jsonValue)\n}\nfunction jsonRecord(value: unknown): value is Record<string, ZhihuJsonValue> {\n return record(value) && Object.values(value).every(jsonValue)\n}\nfunction invalid(message: string): never { throw new ZhihuSearchError('INVALID_ARGUMENTS', message) }\n\nfunction inputObject(input: unknown): Record<string, unknown> {\n if (!record(input)) return invalid('参数必须是对象。')\n // Reject misleading identity overrides, even over raw RPC. Credentials belong to the host.\n for (const key of ['token', 'accessSecret', 'access_secret', 'accessToken', 'access_token', 'oauthToken', 'oauth_token', 'useConfiguredOauthUser', 'use_configured_oauth_user']) {\n if (Object.hasOwn(input, key)) invalid('这些接口仅使用当前 Access Secret,不接受 token 参数或 OAuth 身份切换。')\n }\n return input\n}\nfunction limit(value: unknown, fallback: number, maximum: number, name = 'limit'): string {\n const count = value === undefined ? fallback : value\n if (typeof count !== 'number' || !Number.isInteger(count) || count < 1 || count > maximum) {\n return invalid(`${name} 必须是 1-${maximum} 的整数。`)\n }\n return String(count)\n}\n\n/** Accept all nonnegative Int64 values as strings, but never accept an unsafe JS number. */\nexport function normalizeZhihuOffset(value: unknown): string {\n let decimal: string\n if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) decimal = String(value)\n else if (typeof value === 'string' && /^\\d+$/.test(value)) decimal = value.replace(/^0+(?=\\d)/, '')\n else return invalid('offset 必须是非负 Int64 整数;超过 JavaScript 安全整数范围时请用十进制字符串,不能使用不透明游标。')\n if (decimal.length > 19 || BigInt(decimal) > 9223372036854775807n) return invalid('offset 超出 Int64 范围。')\n return decimal\n}\n\nfunction zhihuUrl(value: unknown, question: boolean): string {\n if (typeof value !== 'string' || !value.trim()) return invalid(question ? 'questionUrl 不能为空。' : 'contentUrl 不能为空。')\n const cleaned = value.trim()\n if (/[\\u0000-\\u0020\\u007f\\\\]/.test(cleaned)) return invalid('知乎链接不能包含空白、控制字符或反斜线。')\n let url: URL\n try { url = new URL(cleaned) } catch { return invalid('不是有效的知乎 URL。') }\n if (url.protocol !== 'https:' || url.username || url.password || url.port) return invalid('必须使用无账号密码、无非标准端口的 HTTPS 知乎链接。')\n const valid = question\n ? url.hostname === 'www.zhihu.com' && /^\\/question\\/\\d+\\/?$/.test(url.pathname)\n : (url.hostname === 'www.zhihu.com' && /^\\/(?:answer\\/\\d+|question\\/\\d+\\/answer\\/\\d+|pin\\/\\d+|zvideo\\/\\d+)\\/?$/.test(url.pathname))\n || (url.hostname === 'zhuanlan.zhihu.com' && /^\\/p\\/\\d+\\/?$/.test(url.pathname))\n if (!valid) return invalid(question ? 'questionUrl 仅支持 https://www.zhihu.com/question/{id}。' : 'contentUrl 仅支持知乎回答、专栏文章、想法或视频链接。')\n return cleaned\n}\n\nfunction dates(input: Record<string, unknown>, params: Record<string, string>): void {\n const { startDate, endDate } = input\n if (startDate === undefined && endDate === undefined) return\n if (startDate === undefined || endDate === undefined) invalid('startDate 与 endDate 必须同时提供或同时省略。')\n for (const [key, value] of [['StartDate', startDate], ['EndDate', endDate]] as const) {\n if (typeof value !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value) || value.slice(0, 4) === '0000') invalid(`${key} 必须是有效的 YYYY-MM-DD 公历日期。`)\n const time = new Date(`${value}T00:00:00Z`)\n if (!Number.isFinite(time.getTime()) || time.toISOString().slice(0, 10) !== value) invalid(`${key} 不是有效公历日期。`)\n params[key] = value\n }\n if (params.EndDate! < params.StartDate!) invalid('endDate 不得早于 startDate。')\n}\n\nfunction paramsFor(operation: ZhihuOpenPlatformOperation, input: Record<string, unknown>): Record<string, string> {\n const params: Record<string, string> = {}\n if (operation === 'question.recommendations') {\n // Explicit null/blank is not the same as omitting Query.\n if (input.query !== undefined) {\n if (typeof input.query !== 'string' || !input.query.trim()) invalid('主题关键词不能为空;省略 query 才会按画像推荐。')\n params.Query = input.query.trim()\n }\n params.Count = limit(input.count, 5, 20, 'count')\n } else if (operation === 'question.answers') {\n params.QuestionUrl = zhihuUrl(input.questionUrl, true)\n } else if (operation === 'content.detail' || operation === 'content.comments' || operation === 'creator.content.stats') {\n params.ContentUrl = zhihuUrl(input.contentUrl, false)\n } else if (operation === 'creator.account.stats') {\n const type = input.contentType === undefined ? 'all' : input.contentType\n if (typeof type !== 'string' || !['all', 'answer', 'article', 'pin', 'zvideo'].includes(type)) invalid('contentType 必须是 all、answer、article、pin 或 zvideo。')\n params.ContentType = type\n } else if (operation === 'quota' && input.apiIds !== undefined) {\n if (!Array.isArray(input.apiIds) || input.apiIds.some(id => typeof id !== 'string' || !(ZHIHU_QUOTA_IDS as readonly string[]).includes(id))) invalid('apiIds 包含未知官方额度项。')\n if (input.apiIds.length) params.APIIDs = [...new Set(input.apiIds)].join(',')\n }\n if (operation === 'question.answers' || operation === 'content.comments') {\n params.Offset = normalizeZhihuOffset(input.offset === undefined ? 0 : input.offset)\n params.Limit = limit(input.limit, 20, 50)\n }\n if (operation === 'content.comments') {\n const order = input.order === undefined ? 'score' : input.order\n if (typeof order !== 'string' || !['score', 'reverse', 'ascending'].includes(order)) invalid('order 必须是 score、reverse 或 ascending。')\n params.Order = order\n }\n if (operation === 'creator.account.stats' || operation === 'creator.content.stats') dates(input, params)\n return params\n}\n\nexport function normalizeZhihuPaging(data: Record<string, unknown>, offset: string): ZhihuPaging {\n const paging = data.Paging\n if (!record(paging) || typeof paging.IsEnd !== 'boolean') {\n return { canContinue: false, warning: '分页信息不完整:缺少有效的 Paging.IsEnd,请停止翻页。' }\n }\n if (paging.IsEnd) return { isEnd: true, canContinue: false }\n try {\n const nextOffset = normalizeZhihuOffset(paging.NextOffset)\n if (BigInt(nextOffset) <= BigInt(offset)) throw new Error('non-advancing offset')\n return { isEnd: false, canContinue: true, nextOffset }\n } catch {\n return { isEnd: false, canContinue: false, warning: '分页信息不完整:NextOffset 缺失、无效或未前进,请停止翻页;不要按条数计算偏移。' }\n }\n}\n\nexport async function executeZhihuOpenPlatform(\n operation: ZhihuOpenPlatformOperation,\n input: unknown = {},\n options: ZhihuClientOptions = {},\n): Promise<ZhihuOpenPlatformResult> {\n const args = inputObject(input)\n const params = paramsFor(operation, args)\n const api = ZHIHU_OPEN_PLATFORM_APIS[operation]\n const raw = await zhihuFetchJson(api.path, { params, envelope: true }, api.label, options)\n const data = operation === 'quota' && Array.isArray(raw) ? { Items: raw } : raw\n if (!jsonRecord(data)) throw new ZhihuSearchError('BAD_RESPONSE', `${api.label}的 Data 必须是 JSON 对象。`)\n return {\n version: 1, operation, data,\n ...(api.quotaId ? { quotaId: api.quotaId } : {}),\n ...(operation === 'question.answers' || operation === 'content.comments'\n ? { paging: normalizeZhihuPaging(data, params.Offset!) } : {}),\n }\n}\n\n// Typed client entry points. Authentication stays in ZhihuClientOptions, never input.\nexport const executeZhihuQuestionRecommendations = (args: { query?: string; count?: number } = {}, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('question.recommendations', args, options)\nexport const executeZhihuQuestionAnswers = (args: { questionUrl: string } & ZhihuPageInput, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('question.answers', args, options)\nexport const executeZhihuUserContentDetail = (args: { contentUrl: string }, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('content.detail', args, options)\nexport const executeZhihuUserContentComments = (args: { contentUrl: string; order?: ZhihuCommentOrder } & ZhihuPageInput, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('content.comments', args, options)\nexport const executeZhihuCreatorAccountStats = (args: { contentType?: ZhihuCreatorContentType } & ZhihuDateInput = {}, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('creator.account.stats', args, options)\nexport const executeZhihuCreatorContentStats = (args: { contentUrl: string } & ZhihuDateInput, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('creator.content.stats', args, options)\nexport const executeZhihuQuota = (args: { apiIds?: ZhihuQuotaId[] } = {}, options?: ZhihuClientOptions) => executeZhihuOpenPlatform('quota', args, options)\n\nexport function zhihuOpenPlatformResultCount(result: ZhihuOpenPlatformResult): number {\n if (result.operation === 'quota') return 0\n if (result.operation === 'content.detail') return typeof result.data.Body === 'string' && result.data.Body.trim() ? 1 : 0\n if (result.operation === 'creator.account.stats') return Object.keys(result.data).length ? 1 : 0\n return Array.isArray(result.data.Items) ? result.data.Items.filter(record).length : 0\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,MAAa,iBAAiB;AAC9B,MAAa,2BAA2B;AAGxC,MAAM,4BAA4B;AAClC,MAAM,6BAA6B;AACnC,MAAM,yBAAyB,KAAK,QAAQ,GAAG,WAAW,gBAAgB,kBAAkB;AAc5F,MAAM,cAA8C,OAAO,eAAe,eAAe,OAAQ,WAAmC,UAAU,cACzI,OAAO,SAAS;CACf,MAAM,IAAK,WAAkD;CAC7D,OAAO,EAAE,OAAkC,IAA2C;AACxF,IACA,KAAA;AAEJ,SAAS,eAAe,OAAwB;CAC9C,MAAM,UAAU,MAAM,KAAK;CAC3B,OAAO,QAAQ,UAAU,KAAK,QAAQ,UAAU;AAClD;AAEA,eAAe,oBAAoB,MAA2C;CAC5E,IAAI;EACF,MAAM,MAAM,MAAM,SAAS,MAAM,MAAM;EACvC,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,IAAI,UAAU,OAAO,WAAW,YAAY,OAAO,OAAO,kBAAkB,YAAY,eAAe,OAAO,aAAa,GACzH,OAAO,OAAO,cAAc,KAAK;CAErC,QAAQ,CAER;AAEF;AAOA,eAAsB,kBACpB,MAAyB,QAAQ,KACjC,UAAqC,CAAC,GACX;CAC3B,IAAI,QAAQ,mBAAmB;EAC7B,MAAM,QAAQ,MAAM,QAAQ,kBAAkB;EAC9C,MAAM,iBAAiB,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI,KAAA;EAClE,IAAI,kBAAkB,eAAe,cAAc,GACjD,OAAO;GAAE,OAAO;GAAgB,QAAQ;EAAa;EAEvD,MAAM,IAAI,iBAAiB,iBAAiB,6CAA6C;CAC3F;CACA,MAAM,UAAU,IAAI,0BAA0B,EAAE,KAAK;CACrD,IAAI,WAAW,eAAe,OAAO,GAAG,OAAO;EAAE,OAAO;EAAS,QAAQ;CAAc;CACvF,MAAM,WAAW,IAAI,2BAA2B,EAAE,KAAK;CACvD,IAAI,YAAY,eAAe,QAAQ,GAAG,OAAO;EAAE,OAAO;EAAU,QAAQ;CAAe;CAC3F,MAAM,WAAW,MAAM,oBAAoB,sBAAsB;CACjE,IAAI,UAAU,OAAO;EAAE,OAAO;EAAU,QAAQ;CAAO;CACvD,MAAM,IAAI,iBAAiB,iBAAiB,CAC1C,uCACA,YAAY,0BAA0B,8CACxC,CAAC,CAAC,KAAK,EAAE,CAAC;AACZ;AAQA,IAAa,mBAAb,cAAsC,MAAM;CAC1C;CAEA;CACA;CACA;CACA;CACA,YAAY,MAAgC,SAAiB,QAAiB,UAA6B,CAAC,GAAG;EAC7G,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,IAAI,WAAW,KAAA,GAAW,KAAK,SAAS;EACxC,IAAI,QAAQ,iBAAiB,KAAA,GAAW,KAAK,eAAe,QAAQ;EACpE,IAAI,QAAQ,eAAe,KAAA,GAAW,KAAK,aAAa,QAAQ;EAChE,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;CAChE;AACF;;AAMA,SAAgB,eAAe,MAA0D,OAAkC;CACzH,IAAI,CAAC,MAAM;CACX,IAAI;EACF,KAAK,KAAK;CACZ,QAAQ,CAER;AACF;AAWA,SAAS,aAAa,OAAyB;CAC7C,IAAI,iBAAiB,SAAS,MAAM,SAAS,cAAc,OAAO;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAS,MAA6B,SAAS;AAC/F;;AAGA,SAAgB,qBAAqB,OAAkC,MAAM,KAAK,IAAI,GAAuB;CAC3G,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO,KAAA;CAC3B,MAAM,OAAO,MAAM,KAAK;CACxB,IAAI,kBAAkB,KAAK,IAAI,GAAG;EAChC,MAAM,UAAU,OAAO,IAAI;EAC3B,OAAO,OAAO,SAAS,OAAO,IAAI,KAAK,KAAK,OAAO,IAAI,KAAA;CACzD;CAEA,IAAI,CAAC,SAAS,KAAK,IAAI,GAAG,OAAO,KAAA;CACjC,MAAM,OAAO,KAAK,MAAM,IAAI;CAC5B,OAAO,OAAO,SAAS,IAAI,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,OAAO,GAAI,CAAC,IAAI,KAAA;AAC/E;;;;;;;AAQA,eAAsB,eACpB,MACA,MACA,OACA,UAA8B,CAAC,GACb;CAClB,MAAM,SAAS,QAAQ;CACvB,IAAI,QAAQ,SAAS,MAAM,IAAI,iBAAiB,aAAa,OAAO;CACpE,MAAM,MAAM,IAAI,IAAI,MAAM,cAAc;CACxC,IAAI,IAAI,WAAA,iCAA6B,IAAI,YAAY,IAAI,UACvD,MAAM,IAAI,iBAAiB,qBAAqB,cAAc;CAEhE,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,GAAG,IAAI,aAAa,IAAI,KAAK,KAAK;CAC7F,MAAM,QAAQ,MAAM,kBAAkB,QAAQ,KAAK,EAAE,mBAAmB,QAAQ,kBAAkB,CAAC;CACnG,IAAI,QAAQ,SAAS,MAAM,IAAI,iBAAiB,aAAa,OAAO;CACpE,MAAM,UAAU,QAAQ,WAAW;CACnC,IAAI,CAAC,SAAS,MAAM,IAAI,iBAAiB,gBAAgB,kBAAkB;CAC3E,MAAM,SAAS,KAAK,WAAW,KAAK,YAAY,KAAK,SAAS,KAAA,IAAY,SAAS;CACnF,MAAM,UAAU,QAAQ,cAAc,KAAA,KAAa,OAAO,SAAS,QAAQ,SAAS,KAAK,QAAQ,YAAY,IACzG,QAAQ,YAAY;CACxB,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,gBAAgB,WAAW,MAAM,QAAQ,MAAM;CACrD,IAAI,QAAQ,SAAS,QAAQ;MACxB,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC9D,MAAM,QAAQ,iBAAiB,WAAW,sBAAM,IAAI,MAAM,uBAAuB,CAAC,GAAG,OAAO;CAC5F,IAAI,oBAAoB,CAAC;CACzB,MAAM,UAAU,IAAI,SAAgB,UAAU,WAAW;EACvD,MAAM,oBAAoB,OAAO,WAAW,OAAO,MAAM;EACzD,IAAI,WAAW,OAAO,SAAS,YAAY;OACtC;GACH,WAAW,OAAO,iBAAiB,SAAS,aAAa,EAAE,MAAM,KAAK,CAAC;GACvE,oBAAoB,WAAW,OAAO,oBAAoB,SAAS,WAAW;EAChF;CACF,CAAC;CACD,IAAI;EACF,MAAM,UAAU,YAAY;GAC1B,WAAW,OAAO,eAAe;GACjC,MAAM,WAAW,MAAM,QAAQ,IAAI,SAAS,GAAG;IAC7C;IACA,SAAS;KACP,eAAe,UAAU,MAAM;KAC/B,uBAAuB,OAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,CAAC;KAC3D,QAAQ;KAER,GAAI,WAAW,UAAU,CAAC,KAAK,WAAW,EAAE,gBAAgB,mBAAmB,IAAI,CAAC;IACtF;IACA,QAAQ,WAAW;IACnB,GAAI,KAAK,WAAW,EAAE,MAAM,KAAK,SAAS,IAAI,KAAK,SAAS,KAAA,IAAY,EAAE,MAAM,KAAK,UAAU,KAAK,IAAI,EAAE,IAAI,CAAC;GACjH,CAAC;GACD,MAAM,aAAa,qBAAqB,SAAS,SAAS,IAAI,aAAa,CAAC;GAC5E,MAAM,kBAAkB;IACtB,MAAM,OAAO,SAAS,WAAW,MAAM,iBACnC,KAAK,aAAa,SAAS,WAAW,OAAO,SAAS,WAAW,OAAO,kBACxE;IACJ,OAAO,IAAI,iBAAiB,MAAM,GAAG,MAAM,UAAU,SAAS,OAAO,IAAI,SAAS,QAChF,SAAS,iBAAiB;KAAE;KAAY,WAAW;IAAK,IAAI,CAAC,CAAC;GAClE;GACA,IAAI;GACJ,IAAI;IAAE,OAAO,MAAM,SAAS,KAAK;GAAE,SAC5B,OAAO;IACZ,IAAI,WAAW,OAAO,SAAS,MAAM;IACrC,IAAI,CAAC,SAAS,IAAI,MAAM,UAAU;IAClC,MAAM,IAAI,iBAAiB,gBAAgB,GAAG,MAAM,aAAa;GACnE;GACA,WAAW,OAAO,eAAe;GAEjC,MAAM,aAAa,SAAS,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,KAAK,UAAU;GAClG,IAAI,CAAC,SAAS,MAAM,CAAC,YAAY,MAAM,UAAU;GACjD,IAAI,KAAK,YAAa,cAAe,KAA2B,SAAS,KAAM,KAA2B,SAAS,KAAM;IACvH,MAAM,OAAO,mBAAmB,MAAM;KAAE,QAAQ,SAAS;KAAQ;KAAY,gBAAgB,KAAK;IAAS,CAAC;IAC5G,IAAI,CAAC,SAAS,IAAI,MAAM,UAAU;IAClC,IAAI,KAAK,UAAU,OAAO;GAC5B;GACA,IAAI,CAAC,SAAS,IAAI,MAAM,UAAU;GAClC,OAAO;EACT;EACA,OAAO,MAAM,QAAQ,KAAK,CAAC,QAAQ,GAAG,OAAO,CAAC;CAChD,SAAS,OAAO;EACd,IAAI,QAAQ,SAAS,MAAM,IAAI,iBAAiB,aAAa,OAAO;EACpE,IAAI,WAAW,OAAO,WAAW,aAAa,KAAK,GACjD,MAAM,IAAI,iBAAiB,WAAW,GAAG,MAAM,KAAK,KAAK,MAAM,UAAU,GAAI,EAAE,WAAW;EAE5F,IAAI,iBAAiB,kBAAkB;GACrC,MAAM,UAAU,MAAM,QAAQ,WAAW,MAAM,OAAO,YAAY;GAClE,MAAM;EACR;EAEA,MAAM,IAAI,iBAAiB,gBAAgB,GAAG,MAAM,MADnC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAA,CAAG,WAAW,MAAM,OAAO,YACxC,GAAS;CACpE,UAAU;EACR,aAAa,KAAK;EAClB,YAAY;EACZ,QAAQ,oBAAoB,SAAS,OAAO;CAC9C;AACF;AAEA,MAAM,wBAAkE;CACtE,OAAO;CAAqB,OAAO;CACnC,OAAO;CAAgB,OAAO;CAC9B,OAAO;CAAqB,OAAO;CAAqB,OAAO;CAC/D,OAAO;CAAqB,OAAO;CAAqB,OAAO;CAC/D,OAAO;CAAwB,OAAO;AACxC;;AAGA,SAAgB,mBAAmB,MAAe,OAA2E,CAAC,GAAY;CACxI,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACzD,MAAM,IAAI,iBAAiB,gBAAgB,kCAAkC,KAAK,MAAM;CAE1F,MAAM,WAAW;CACjB,MAAM,UAAU,SAAS;CACzB,IAAI,EAAG,OAAO,YAAY,YAAY,OAAO,cAAc,OAAO,KAC5D,OAAO,YAAY,YAAY,QAAQ,KAAK,OAAO,KAAK,OAAO,cAAc,OAAO,OAAO,CAAC,IAChG,MAAM,IAAI,iBAAiB,gBAAgB,sBAAsB,KAAK,MAAM;CAE9E,MAAM,OAAO,OAAO,OAAO;CAC3B,IAAI,SAAS,GAAG;EACd,MAAM,UAAU,OAAO,SAAS,YAAY,YAAY,SAAS,UAAU,SAAS,UAAU;EAC9F,IAAI,SAAS,OACX,MAAM,IAAI,iBAAiB,wBAAwB,eAAe,QAAQ,YACxE,KAAK,QAAQ;GAAE,cAAc;GAAM,WAAW;EAAM,CAAC;EAEzD,MAAM,OAAO,sBAAsB,UAAU,KAAK,iBAAiB,yBAAyB;EAC5F,MAAM,IAAI,iBAAiB,MAAM,mBAAmB,KAAK,IAAI,WAAW,KAAK,QAC3E;GAAE,cAAc;GAAM,GAAI,SAAS,iBAAiB;IAAE,YAAY,KAAK;IAAY,WAAW;GAAK,IAAI,CAAC;EAAG,CAAC;CAChH;CACA,OAAO,SAAS,QAAQ,CAAC;AAC3B;AAEA,SAAS,gBAAgB,MAAc,OAAuB;CAC5D,IAAI,KAAK,UAAU,OAAO,OAAO;CACjC,OAAO,GAAG,KAAK,MAAM,GAAG,KAAK,EAAE;AACjC;AAeA,SAAgB,oBAAoB,KAA+C;CACjF,MAAM,UAAU,OAAO,IAAI,gBAAgB,WAAW,IAAI,cAAc;CACxE,OAAO;EACL,OAAO,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,SAAS,IAAI,IAAI,QAAQ;EAC3E,MAAM,OAAO,IAAI,gBAAgB,YAAY,IAAI,YAAY,SAAS,IAAI,IAAI,cAAc;EAC5F,KAAK,OAAO,IAAI,QAAQ,WAAW,IAAI,MAAM;EAC7C,SAAS,gBAAgB,QAAQ,KAAK,GAAA,GAA0B;EAChE,OAAO,OAAO,IAAI,gBAAgB,WAAW,IAAI,cAAc;EAC/D,UAAU,OAAO,IAAI,iBAAiB,WAAW,IAAI,eAAe;EACpE,QAAQ,OAAO,IAAI,eAAe,YAAY,IAAI,WAAW,SAAS,IAAI,IAAI,aAAa;EAC3F,WAAW,OAAO,IAAI,mBAAmB,YAAY,IAAI,eAAe,SAAS,IAAI,IAAI,iBAAiB;EAC1G,UAAU,OAAO,IAAI,aAAa,WAAW,OAAO,IAAI,QAAQ,IAAI;CACtE;AACF;AC3RA,MAAM,oBAAoB;AAY1B,SAAS,WAAW,OAAwB;CAC1C,MAAM,IAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,IAAA;CAChF,IAAI,IAAI,GAAG,OAAO;CAClB,IAAI,IAAA,IAA4B,OAAA;CAChC,OAAO;AACT;AAEA,SAAS,YAAY,MAA2E;CAC9F,MAAM,MAAO,QAAQ,CAAC;CAGtB,OAAO;EAAE,OAFK,MAAM,QAAQ,IAAI,KAAK,IAAI,IAAI,MAAM,QAAQ,SAA0C,CAAC,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,CAAC,IAAI,CAAC;EAEpJ,aADI,OAAO,IAAI,gBAAgB,WAAW,IAAI,cAAc,KAAA;CAChD;AAC9B;AAEA,eAAsB,mBACpB,OACA,OACA,UAAqC,CAAC,GACV;CAC5B,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,CAAC,SAAS,MAAM,IAAI,iBAAiB,gBAAgB,UAAU;CACnE,MAAM,gBAAgB,WAAW,KAAK;CAItC,MAAM,EAAE,OAAO,gBAAgB,YAAY,mBAAmB,MAH3C,eAAe,mBAAmB,EACnD,QAAQ;EAAE,OAAO;EAAS,OAAO,OAAO,aAAa;CAAE,EACzD,GAAG,QAAQ,OAAO,CACgD,CAAC;CACnE,OAAO;EACL,SAAA;EACA,OAAO;EACP,OAAO;EACP,OAAO,MAAM,IAAI,mBAAmB;EACpC;CACF;AACF;AAEA,SAAgB,kBAAkB,QAAmC;CACnE,IAAI,OAAO,MAAM,WAAW,GAC1B,OAAO,UAAU,OAAO,MAAM,MAAM,OAAO,cAAc,IAAI,OAAO,YAAY,KAAK,GAAG;CAE1F,MAAM,QAAkB,CAAC,UAAU,OAAO,MAAM,KAAK,OAAO,MAAM,OAAO,MAAM,EAAE;CACjF,OAAO,MAAM,SAAS,MAAM,UAAU;EACpC,MAAM,KAAK,GAAG,QAAQ,EAAE,KAAK,KAAK,MAAM,IAAI,KAAK,OAAO,QAAQ,EAAE;EAClE,MAAM,KAAK,WAAW,KAAK,KAAK,MAAM,KAAK,OAAO,MAAM,KAAK,WAAW;EACxE,IAAI,KAAK,UAAU,MAAM,KAAK,WAAW,KAAK,UAAU;EACxD,MAAM,KAAK,WAAW,KAAK,MAAM,MAAM,KAAK,UAAU;EACtD,IAAI,KAAK,SAAS,MAAM,KAAK,WAAW,KAAK,SAAS;EACtD,MAAM,KAAK,EAAE;CACf,CAAC;CACD,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,QAAQ;AAClC;AC7DA,MAAM,qBAAqB;AAC3B,MAAM,gBAAgB;AACtB,MAAM,WAAW;AACjB,MAAM,wBAAwB;AAE9B,MAAa,mBAAmB;CAAC;CAAkB;CAAsB;AAAa;;AAGtF,MAAa,0BAAyC;AAOtD,SAAS,SAAS,OAAgB,UAAkB,KAAqB;CACvE,MAAM,IAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI;CACpF,IAAI,IAAI,GAAG,OAAO;CAClB,IAAI,IAAI,KAAK,OAAO;CACpB,OAAO;AACT;AAEA,SAAS,aAAa,OAAe,MAAsB;CACzD,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,CAAC,SAAS,MAAM,IAAI,iBAAiB,gBAAgB,IAAI;CAC7D,OAAO;AACT;AAEA,SAAS,YAAY,MAAe,MAAM,SAAoC;CAE5E,MAAM,SADO,QAAQ,CAAC,EAAA,CACJ;CAClB,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,QAAQ,SAA0C,CAAC,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,CAAC,IAAI,CAAC;AACvJ;AAiBA,eAAsB,yBACpB,OACA,OACA,UACA,UAA8B,CAAC,GACG;CAClC,MAAM,UAAU,aAAa,OAAO,UAAU;CAC9C,MAAM,gBAAgB,SAAS,OAAA,IAAA,EAAuE;CAItG,MAAM,OAAO,mBAAmB,MAHb,eAAe,oBAAoB,EACpD,QAAQ;EAAE,OAAO;EAAS,OAAO,OAAO,aAAa;EAAG,UAAU;CAAS,EAC7E,GAAG,UAAU,OAAO,CACgB;CACpC,OAAO;EACL,SAAA;EACA,OAAO;EACP,OAAO;EACP;EACA,OAAO,YAAY,IAAI,CAAC,CAAC,IAAI,mBAAmB;EAChD,aAAa,OAAO,KAAK,gBAAgB,WAAW,KAAK,cAAc,KAAA;CACzE;AACF;AAEA,SAAgB,wBAAwB,QAAyC;CAC/E,IAAI,OAAO,MAAM,WAAW,GAC1B,OAAO,QAAQ,OAAO,MAAM,MAAM,OAAO,cAAc,IAAI,OAAO,YAAY,KAAK,GAAG;CAExF,MAAM,QAAkB,CAAC,QAAQ,OAAO,MAAM,KAAK,OAAO,MAAM,OAAO,MAAM,EAAE;CAC/E,OAAO,MAAM,SAAS,MAAM,UAAU;EACpC,MAAM,KAAK,GAAG,QAAQ,EAAE,KAAK,KAAK,MAAM,IAAI,KAAK,OAAO,QAAQ,EAAE;EAClE,MAAM,KAAK,WAAW,KAAK,KAAK,MAAM,KAAK,QAAQ;EACnD,IAAI,KAAK,SAAS,MAAM,KAAK,WAAW,KAAK,SAAS;EACtD,MAAM,KAAK,EAAE;CACf,CAAC;CACD,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,QAAQ;AAClC;AAkBA,MAAM,mCAAmC;AAEzC,SAAS,iBAAiB,KAAgD;CACxE,MAAM,UAAU,OAAO,IAAI,YAAY,WAAW,IAAI,QAAQ,KAAK,IAAI;CACvE,OAAO;EACL,OAAO,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,SAAS,IAAI,IAAI,QAAQ;EAC3E,KAAK,OAAO,IAAI,QAAQ,WAAW,IAAI,MAAM;EAC7C,SAAS,QAAQ,SAAS,mCAAmC,GAAG,QAAQ,MAAM,GAAG,gCAAgC,EAAE,KAAK;CAC1H;AACF;AAEA,eAAsB,oBAAoB,OAAe,UAA8B,CAAC,GAAgC;CACtH,MAAM,gBAAgB,SAAS,OAAA,IAAA,EAA6D;CAK5F,OAAO;EAAE,SAAA;EAAiC,OAAO;EAAe,OADlD,YAAY,mBAAmB,MAH1B,eAAe,eAAe,EAC/C,QAAQ,EAAE,OAAO,OAAO,aAAa,EAAE,EACzC,GAAG,QAAQ,OAAO,CAC+B,CAAC,CAAC,CAAC,IAAI,gBACQ;CAAM;AACxE;AAEA,SAAgB,mBAAmB,QAAoC;CACrE,IAAI,OAAO,MAAM,WAAW,GAAG,OAAO;CACtC,MAAM,QAAkB,CAAC,SAAS,EAAE;CACpC,OAAO,MAAM,SAAS,MAAM,UAAU;EACpC,MAAM,KAAK,GAAG,QAAQ,EAAE,KAAK,KAAK,MAAM,IAAI,KAAK,OAAO,QAAQ,EAAE;EAClE,IAAI,KAAK,SAAS,MAAM,KAAK,QAAQ,KAAK,SAAS;EACnD,MAAM,KAAK,EAAE;CACf,CAAC;CACD,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,QAAQ;AAClC;AAcA,eAAsB,gBACpB,OACA,OACA,UAA8B,CAAC,GACN;CACzB,MAAM,UAAU,aAAa,OAAO,WAAW;CAC/C,MAAM,OAAO,MAAM,eAAe,UAAU;EAC1C,QAAQ;EACR,MAAM;GAAE;GAAO,UAAU,CAAC;IAAE,MAAM;IAAQ,SAAS;GAAQ,CAAC;GAAG,QAAQ;EAAM;CAC/E,GAAG,QAAQ;EAAE,GAAG;EAAS,WAAW,QAAQ,aAAA;CAAkC,CAAC;CAC/E,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACzD,MAAM,IAAI,iBAAiB,gBAAgB,uBAAuB;CAEpE,MAAM,UAAU;CAIhB,IAAI,QAAQ,OAEV,MAAM,IAAI,iBAAiB,cAAc,YAD7B,OAAO,QAAQ,MAAM,YAAY,WAAW,QAAQ,MAAM,UAAU,QACtB;CAG5D,MAAM,WADS,MAAM,QAAQ,QAAQ,OAAO,IAAI,QAAQ,QAAQ,KAAK,KAAA,EAAA,EAC7C,WAAW,CAAC;CACpC,MAAM,UAAU,OAAO,QAAQ,YAAY,WAAW,QAAQ,QAAQ,KAAK,IAAI;CAC/E,MAAM,YAAY,OAAO,QAAQ,sBAAsB,WAAW,QAAQ,kBAAkB,KAAK,IAAI;CACrG,IAAI,CAAC,SAAS,MAAM,IAAI,iBAAiB,gBAAgB,WAAW;CACpE,OAAO;EAAE,SAAA;EAA4B,OAAO;EAAS;EAAO;EAAS;CAAU;AACjF;AAEA,SAAgB,eAAe,QAAgC;CAC7D,MAAM,QAAkB,CAAC;CACzB,IAAI,OAAO,WAAW,MAAM,KAAK,WAAW,OAAO,WAAW;CAC9D,MAAM,KAAK,OAAO,OAAO;CACzB,OAAO,QAAQ,OAAO,MAAM,QAAQ,MAAM,KAAK,MAAM;AACvD;AAaA,MAAa,sBAAmD;CAAC;CAAY;CAAgB;AAAQ;AAUrG,SAAS,uBAAuB,KAAkD;CAChF,MAAM,UAAU,IAAI;CACpB,MAAM,WAAW,MAAM,QAAQ,OAAO,IAClC,QAAQ,KAAK,YAAY,OAAO,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,YAAY,QAAQ,SAAS,CAAC,IACvF,CAAC;CACL,OAAO;EACL,SAAS,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,SAAS,IAAI,IAAI,UAAU;EACnF,WAAW,OAAO,IAAI,cAAc,WAAW,IAAI,YAAY;EAC/D;CACF;AACF;;AAGA,SAAgB,sBAAsB,OAAoC;CACxE,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC,QAAQ;CAC3C,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,MAAM,QAAQ,UAAqC,OAAO,UAAU,YAAa,oBAA0C,SAAS,KAAK,CAAC,CAAC,CAAC;CACvK,OAAO,OAAO,SAAS,IAAI,SAAS,CAAC,QAAQ;AAC/C;AAEA,eAAsB,4BACpB,OACA,OACA,cACA,UAA8B,CAAC,GACM;CACrC,MAAM,UAAU,aAAa,OAAO,aAAa;CACjD,MAAM,gBAAgB,SAAS,OAAA,GAAA,EAA6E;CAM5G,OAAO;EAAE,SAAA;EAAyC,OAAO;EAAS,OAAO;EAAe;EAAc,OADxF,YAAY,mBAAmB,MAJ1B,eAAe,uBAAuB;GACvD,QAAQ;GACR,MAAM;IAAE,OAAO;IAAS,OAAO;IAAe,cAAc;GAAa;EAC3E,GAAG,WAAW;GAAE,GAAG;GAAS,WAAW,QAAQ,aAAa;EAAO,CAAC,CACnB,CAAC,CAAC,CAAC,IAAI,sBAC8C;CAAM;AAC9G;AAEA,SAAgB,2BAA2B,QAA4C;CACrF,IAAI,OAAO,MAAM,WAAW,GAAG,OAAO,WAAW,OAAO,MAAM;CAC9D,MAAM,QAAkB,CAAC,WAAW,OAAO,MAAM,KAAK,OAAO,MAAM,OAAO,MAAM,EAAE;CAClF,OAAO,MAAM,SAAS,MAAM,UAAU;EACpC,MAAM,KAAK,GAAG,QAAQ,EAAE,IAAI,KAAK,SAAS;EAC1C,IAAI,KAAK,WAAW,MAAM,KAAK,WAAW,KAAK,WAAW;EAC1D,KAAK,MAAM,WAAW,KAAK,UAAU,MAAM,KAAK,QAAQ,SAAS;EACjE,MAAM,KAAK,EAAE;CACf,CAAC;CACD,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,QAAQ;AAClC;;;;;;;;ACtRA,MAAa,2BAA2B;CACtC,4BAA4B;EAAE,MAAM;EAAyC,OAAO;EAAW,SAAS;CAAU;CAClH,oBAAoB;EAAE,MAAM;EAAoC,OAAO;EAAU,SAAS;CAAmB;CAC7G,kBAAkB;EAAE,MAAM;EAA+B,OAAO;EAAU,SAAS;CAAU;CAC7F,oBAAoB;EAAE,MAAM;EAAiC,OAAO;EAAU,SAAS;CAAU;CACjG,yBAAyB;EAAE,MAAM;EAAsC,OAAO;EAAU,SAAS;CAAU;CAC3G,yBAAyB;EAAE,MAAM;EAAsC,OAAO;EAAU,SAAS;CAAU;CAC3G,OAAO;EAAE,MAAM;EAAiB,OAAO;EAAU,SAAS,KAAA;CAAU;AACtE;AAGA,MAAM,mBAA+D;CACnE,0BAA0B;CAA4B,kBAAkB;CACxE,gBAAgB;CAAkB,qBAAqB;CAAkB,uBAAuB;CAChG,kBAAkB;CAAoB,uBAAuB;CAAoB,yBAAyB;CAC1G,uBAAuB;CAAyB,uBAAuB;AACzE;AACA,SAAgB,kCAAkC,UAA0D;CAC1G,IAAI,OAAO,OAAO,0BAA0B,QAAQ,GAAG,OAAO;CAC9D,OAAO,OAAO,OAAO,kBAAkB,QAAQ,IAAI,iBAAiB,YAAY,KAAA;AAClF;AAuBA,MAAM,UAAU,UAAqD,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAExI,SAAS,UAAU,OAAyC;CAC1D,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW,OAAO;CACtF,IAAI,OAAO,UAAU,UAAU,OAAO,OAAO,SAAS,KAAK;CAC3D,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,MAAM,SAAS;CACtD,OAAO,OAAO,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,SAAS;AAC9D;AACA,SAAS,WAAW,OAAyD;CAC3E,OAAO,OAAO,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,SAAS;AAC9D;AACA,SAAS,QAAQ,SAAwB;CAAE,MAAM,IAAI,iBAAiB,qBAAqB,OAAO;AAAE;AAEpG,SAAS,YAAY,OAAyC;CAC5D,IAAI,CAAC,OAAO,KAAK,GAAG,OAAO,QAAQ,UAAU;CAE7C,KAAK,MAAM,OAAO;EAAC;EAAS;EAAgB;EAAiB;EAAe;EAAgB;EAAc;EAAe;EAA0B;CAA2B,GAC5K,IAAI,OAAO,OAAO,OAAO,GAAG,GAAG,QAAQ,mDAAmD;CAE5F,OAAO;AACT;AACA,SAAS,MAAM,OAAgB,UAAkB,SAAiB,OAAO,SAAiB;CACxF,MAAM,QAAQ,UAAU,KAAA,IAAY,WAAW;CAC/C,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,SAChF,OAAO,QAAQ,GAAG,KAAK,SAAS,QAAQ,MAAM;CAEhD,OAAO,OAAO,KAAK;AACrB;;AAGA,SAAgB,qBAAqB,OAAwB;CAC3D,IAAI;CACJ,IAAI,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,SAAS,GAAG,UAAU,OAAO,KAAK;MAC7F,IAAI,OAAO,UAAU,YAAY,QAAQ,KAAK,KAAK,GAAG,UAAU,MAAM,QAAQ,aAAa,EAAE;MAC7F,OAAO,QAAQ,gEAAgE;CACpF,IAAI,QAAQ,SAAS,MAAM,OAAO,OAAO,IAAI,sBAAsB,OAAO,QAAQ,qBAAqB;CACvG,OAAO;AACT;AAEA,SAAS,SAAS,OAAgB,UAA2B;CAC3D,IAAI,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,GAAG,OAAO,QAAQ,WAAW,sBAAsB,kBAAkB;CAClH,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,0BAA0B,KAAK,OAAO,GAAG,OAAO,QAAQ,sBAAsB;CAClF,IAAI;CACJ,IAAI;EAAE,MAAM,IAAI,IAAI,OAAO;CAAE,QAAQ;EAAE,OAAO,QAAQ,cAAc;CAAE;CACtE,IAAI,IAAI,aAAa,YAAY,IAAI,YAAY,IAAI,YAAY,IAAI,MAAM,OAAO,QAAQ,+BAA+B;CAKzH,IAAI,EAJU,WACV,IAAI,aAAa,mBAAmB,uBAAuB,KAAK,IAAI,QAAQ,IAC3E,IAAI,aAAa,mBAAmB,yEAAyE,KAAK,IAAI,QAAQ,KAC3H,IAAI,aAAa,wBAAwB,gBAAgB,KAAK,IAAI,QAAQ,IACtE,OAAO,QAAQ,WAAW,yDAAyD,kCAAkC;CACjI,OAAO;AACT;AAEA,SAAS,MAAM,OAAgC,QAAsC;CACnF,MAAM,EAAE,WAAW,YAAY;CAC/B,IAAI,cAAc,KAAA,KAAa,YAAY,KAAA,GAAW;CACtD,IAAI,cAAc,KAAA,KAAa,YAAY,KAAA,GAAW,QAAQ,kCAAkC;CAChG,KAAK,MAAM,CAAC,KAAK,UAAU,CAAC,CAAC,aAAa,SAAS,GAAG,CAAC,WAAW,OAAO,CAAC,GAAY;EACpF,IAAI,OAAO,UAAU,YAAY,CAAC,sBAAsB,KAAK,KAAK,KAAK,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,QAAQ,GAAG,IAAI,yBAAyB;EAC7I,MAAM,uBAAO,IAAI,KAAK,GAAG,MAAM,WAAW;EAC1C,IAAI,CAAC,OAAO,SAAS,KAAK,QAAQ,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE,MAAM,OAAO,QAAQ,GAAG,IAAI,WAAW;EAC7G,OAAO,OAAO;CAChB;CACA,IAAI,OAAO,UAAW,OAAO,WAAY,QAAQ,yBAAyB;AAC5E;AAEA,SAAS,UAAU,WAAuC,OAAwD;CAChH,MAAM,SAAiC,CAAC;CACxC,IAAI,cAAc,4BAA4B;EAE5C,IAAI,MAAM,UAAU,KAAA,GAAW;GAC7B,IAAI,OAAO,MAAM,UAAU,YAAY,CAAC,MAAM,MAAM,KAAK,GAAG,QAAQ,6BAA6B;GACjG,OAAO,QAAQ,MAAM,MAAM,KAAK;EAClC;EACA,OAAO,QAAQ,MAAM,MAAM,OAAO,GAAG,IAAI,OAAO;CAClD,OAAO,IAAI,cAAc,oBACvB,OAAO,cAAc,SAAS,MAAM,aAAa,IAAI;MAChD,IAAI,cAAc,oBAAoB,cAAc,sBAAsB,cAAc,yBAC7F,OAAO,aAAa,SAAS,MAAM,YAAY,KAAK;MAC/C,IAAI,cAAc,yBAAyB;EAChD,MAAM,OAAO,MAAM,gBAAgB,KAAA,IAAY,QAAQ,MAAM;EAC7D,IAAI,OAAO,SAAS,YAAY,CAAC;GAAC;GAAO;GAAU;GAAW;GAAO;EAAQ,CAAC,CAAC,SAAS,IAAI,GAAG,QAAQ,kDAAkD;EACzJ,OAAO,cAAc;CACvB,OAAO,IAAI,cAAc,WAAW,MAAM,WAAW,KAAA,GAAW;EAC9D,IAAI,CAAC,MAAM,QAAQ,MAAM,MAAM,KAAK,MAAM,OAAO,MAAK,OAAM,OAAO,OAAO,YAAY,CAAE,gBAAsC,SAAS,EAAE,CAAC,GAAG,QAAQ,mBAAmB;EACxK,IAAI,MAAM,OAAO,QAAQ,OAAO,SAAS,CAAC,GAAG,IAAI,IAAI,MAAM,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;CAC9E;CACA,IAAI,cAAc,sBAAsB,cAAc,oBAAoB;EACxE,OAAO,SAAS,qBAAqB,MAAM,WAAW,KAAA,IAAY,IAAI,MAAM,MAAM;EAClF,OAAO,QAAQ,MAAM,MAAM,OAAO,IAAI,EAAE;CAC1C;CACA,IAAI,cAAc,oBAAoB;EACpC,MAAM,QAAQ,MAAM,UAAU,KAAA,IAAY,UAAU,MAAM;EAC1D,IAAI,OAAO,UAAU,YAAY,CAAC;GAAC;GAAS;GAAW;EAAW,CAAC,CAAC,SAAS,KAAK,GAAG,QAAQ,sCAAsC;EACnI,OAAO,QAAQ;CACjB;CACA,IAAI,cAAc,2BAA2B,cAAc,yBAAyB,MAAM,OAAO,MAAM;CACvG,OAAO;AACT;AAEA,SAAgB,qBAAqB,MAA+B,QAA6B;CAC/F,MAAM,SAAS,KAAK;CACpB,IAAI,CAAC,OAAO,MAAM,KAAK,OAAO,OAAO,UAAU,WAC7C,OAAO;EAAE,aAAa;EAAO,SAAS;CAAoC;CAE5E,IAAI,OAAO,OAAO,OAAO;EAAE,OAAO;EAAM,aAAa;CAAM;CAC3D,IAAI;EACF,MAAM,aAAa,qBAAqB,OAAO,UAAU;EACzD,IAAI,OAAO,UAAU,KAAK,OAAO,MAAM,GAAG,MAAM,IAAI,MAAM,sBAAsB;EAChF,OAAO;GAAE,OAAO;GAAO,aAAa;GAAM;EAAW;CACvD,QAAQ;EACN,OAAO;GAAE,OAAO;GAAO,aAAa;GAAO,SAAS;EAAgD;CACtG;AACF;AAEA,eAAsB,yBACpB,WACA,QAAiB,CAAC,GAClB,UAA8B,CAAC,GACG;CAElC,MAAM,SAAS,UAAU,WADZ,YAAY,KACc,CAAC;CACxC,MAAM,MAAM,yBAAyB;CACrC,MAAM,MAAM,MAAM,eAAe,IAAI,MAAM;EAAE;EAAQ,UAAU;CAAK,GAAG,IAAI,OAAO,OAAO;CACzF,MAAM,OAAO,cAAc,WAAW,MAAM,QAAQ,GAAG,IAAI,EAAE,OAAO,IAAI,IAAI;CAC5E,IAAI,CAAC,WAAW,IAAI,GAAG,MAAM,IAAI,iBAAiB,gBAAgB,GAAG,IAAI,MAAM,oBAAoB;CACnG,OAAO;EACL,SAAS;EAAG;EAAW;EACvB,GAAI,IAAI,UAAU,EAAE,SAAS,IAAI,QAAQ,IAAI,CAAC;EAC9C,GAAI,cAAc,sBAAsB,cAAc,qBAClD,EAAE,QAAQ,qBAAqB,MAAM,OAAO,MAAO,EAAE,IAAI,CAAC;CAChE;AACF;AAWA,SAAgB,6BAA6B,QAAyC;CACpF,IAAI,OAAO,cAAc,SAAS,OAAO;CACzC,IAAI,OAAO,cAAc,kBAAkB,OAAO,OAAO,OAAO,KAAK,SAAS,YAAY,OAAO,KAAK,KAAK,KAAK,IAAI,IAAI;CACxH,IAAI,OAAO,cAAc,yBAAyB,OAAO,OAAO,KAAK,OAAO,IAAI,CAAC,CAAC,SAAS,IAAI;CAC/F,OAAO,MAAM,QAAQ,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,MAAM,OAAO,MAAM,CAAC,CAAC,SAAS;AACtF"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klarkxy/dsh-zhihu",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Zhihu search, agent tools, knowledge bases, and usage tracking for DeepSeek Harness.",
|
|
5
5
|
"icon": "./icon.svg",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": ">=22"
|
|
9
|
+
"node": ">=22",
|
|
10
|
+
"dsh": ">=0.2.0-rc.2"
|
|
10
11
|
},
|
|
11
12
|
"main": "./lib/index.js",
|
|
12
13
|
"types": "./lib/index.d.ts",
|
|
@@ -94,24 +95,24 @@
|
|
|
94
95
|
},
|
|
95
96
|
"dependencies": {
|
|
96
97
|
"zod": "^4.1.5",
|
|
97
|
-
"@klarkxy/dsh-plugin-kit": "0.
|
|
98
|
+
"@klarkxy/dsh-plugin-kit": "0.2.0"
|
|
98
99
|
},
|
|
99
100
|
"peerDependencies": {
|
|
100
|
-
"@deepseek-ai/cordis": "
|
|
101
|
-
"@deepseek-ai/dsh-credentials": ">=0.
|
|
102
|
-
"@deepseek-ai/dsh-storage-domain": ">=0.
|
|
103
|
-
"@deepseek-ai/dsh-tools": ">=0.
|
|
101
|
+
"@deepseek-ai/cordis": ">=4.0.4",
|
|
102
|
+
"@deepseek-ai/dsh-credentials": ">=0.2.0-rc.2",
|
|
103
|
+
"@deepseek-ai/dsh-storage-domain": ">=0.2.0-rc.2",
|
|
104
|
+
"@deepseek-ai/dsh-tools": ">=0.2.0-rc.2"
|
|
104
105
|
},
|
|
105
106
|
"devDependencies": {
|
|
106
|
-
"@deepseek-ai/cordis": "
|
|
107
|
-
"@deepseek-ai/dsh-credentials": "0.
|
|
108
|
-
"@deepseek-ai/dsh-storage-domain": "0.
|
|
109
|
-
"@deepseek-ai/dsh-tools": "0.
|
|
107
|
+
"@deepseek-ai/cordis": "4.0.4",
|
|
108
|
+
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
|
|
109
|
+
"@deepseek-ai/dsh-storage-domain": "0.2.0-rc.2",
|
|
110
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2",
|
|
110
111
|
"react": "^18.2.0",
|
|
111
112
|
"@types/react": "18.3.31",
|
|
112
113
|
"react-dom": "^18.2.0",
|
|
113
114
|
"@types/react-dom": "^18.3.1",
|
|
114
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.
|
|
115
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2"
|
|
115
116
|
},
|
|
116
117
|
"peerDependenciesMeta": {
|
|
117
118
|
"@deepseek-ai/dsh-credentials": {
|
|
@@ -148,6 +149,6 @@
|
|
|
148
149
|
"registry": "https://registry.npmjs.org/"
|
|
149
150
|
},
|
|
150
151
|
"dshRelease": {
|
|
151
|
-
"contentHash": "
|
|
152
|
+
"contentHash": "a3c9e878f104b277262d585294e48e66a0c20da986d0c2efd7e0bb3811028b56"
|
|
152
153
|
}
|
|
153
154
|
}
|