@klarkxy/dsh-zhihu 0.2.0 → 0.2.2

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.
@@ -26,8 +26,9 @@ declare const ZHIHU_CREATOR_ACCOUNT_STATS_TOOL_NAME = "zhihu_creator_account_sta
26
26
  declare const ZHIHU_CREATOR_CONTENT_STATS_TOOL_NAME = "zhihu_creator_content_stats";
27
27
  declare const ZHIHU_QUOTA_TOOL_NAME = "zhihu_quota";
28
28
  /** Official quota IDs, not local execution counters or predicted balances. */
29
- declare const ZHIHU_QUOTA_IDS: readonly ["global_search", "zhihu_search", "hot_list", "question_answers", "zhida_openai", "tools", "knowledge", "user_data", "creator"];
29
+ declare const ZHIHU_QUOTA_IDS: readonly ["global_search", "zhihu_search", "hot_list", "question_discovery", "zhida_openai", "tools", "knowledge", "user_data", "creator"];
30
+ declare const normalizeZhihuQuotaId: (value: unknown) => unknown;
30
31
  type ZhihuQuotaId = typeof ZHIHU_QUOTA_IDS[number];
31
32
  //#endregion
32
- export { ZHIHU_ASK_TOOL_NAME, ZHIHU_CREATOR_ACCOUNT_STATS_TOOL_NAME, ZHIHU_CREATOR_CONTENT_STATS_TOOL_NAME, ZHIHU_CREDENTIAL_REF, ZHIHU_GLOBAL_SEARCH_TOOL_NAME, ZHIHU_HOT_LIST_TOOL_NAME, ZHIHU_KNOWLEDGE_SEARCH_TOOL_NAME, ZHIHU_QUESTION_ANSWERS_TOOL_NAME, ZHIHU_QUESTION_RECOMMENDATIONS_TOOL_NAME, ZHIHU_QUOTA_IDS, ZHIHU_QUOTA_TOOL_NAME, ZHIHU_RPC_CHANNEL, ZHIHU_SEARCH_EVENT, ZHIHU_SEARCH_TOOL_NAME, ZHIHU_USER_CONTENT_COMMENTS_TOOL_NAME, ZHIHU_USER_CONTENT_DETAIL_TOOL_NAME, ZhihuQuotaId, ZhihuRpcResult };
33
+ export { ZHIHU_ASK_TOOL_NAME, ZHIHU_CREATOR_ACCOUNT_STATS_TOOL_NAME, ZHIHU_CREATOR_CONTENT_STATS_TOOL_NAME, ZHIHU_CREDENTIAL_REF, ZHIHU_GLOBAL_SEARCH_TOOL_NAME, ZHIHU_HOT_LIST_TOOL_NAME, ZHIHU_KNOWLEDGE_SEARCH_TOOL_NAME, ZHIHU_QUESTION_ANSWERS_TOOL_NAME, ZHIHU_QUESTION_RECOMMENDATIONS_TOOL_NAME, ZHIHU_QUOTA_IDS, ZHIHU_QUOTA_TOOL_NAME, ZHIHU_RPC_CHANNEL, ZHIHU_SEARCH_EVENT, ZHIHU_SEARCH_TOOL_NAME, ZHIHU_USER_CONTENT_COMMENTS_TOOL_NAME, ZHIHU_USER_CONTENT_DETAIL_TOOL_NAME, ZhihuQuotaId, ZhihuRpcResult, normalizeZhihuQuotaId };
33
34
  //# sourceMappingURL=contracts.d.ts.map
package/lib/contracts.js CHANGED
@@ -19,14 +19,15 @@ const ZHIHU_QUOTA_IDS = [
19
19
  "global_search",
20
20
  "zhihu_search",
21
21
  "hot_list",
22
- "question_answers",
22
+ "question_discovery",
23
23
  "zhida_openai",
24
24
  "tools",
25
25
  "knowledge",
26
26
  "user_data",
27
27
  "creator"
28
28
  ];
29
+ const normalizeZhihuQuotaId = (value) => value === "question_answers" ? "question_discovery" : value;
29
30
  //#endregion
30
- export { ZHIHU_ASK_TOOL_NAME, ZHIHU_CREATOR_ACCOUNT_STATS_TOOL_NAME, ZHIHU_CREATOR_CONTENT_STATS_TOOL_NAME, ZHIHU_CREDENTIAL_REF, ZHIHU_GLOBAL_SEARCH_TOOL_NAME, ZHIHU_HOT_LIST_TOOL_NAME, ZHIHU_KNOWLEDGE_SEARCH_TOOL_NAME, ZHIHU_QUESTION_ANSWERS_TOOL_NAME, ZHIHU_QUESTION_RECOMMENDATIONS_TOOL_NAME, ZHIHU_QUOTA_IDS, ZHIHU_QUOTA_TOOL_NAME, ZHIHU_RPC_CHANNEL, ZHIHU_SEARCH_EVENT, ZHIHU_SEARCH_TOOL_NAME, ZHIHU_USER_CONTENT_COMMENTS_TOOL_NAME, ZHIHU_USER_CONTENT_DETAIL_TOOL_NAME };
31
+ export { ZHIHU_ASK_TOOL_NAME, ZHIHU_CREATOR_ACCOUNT_STATS_TOOL_NAME, ZHIHU_CREATOR_CONTENT_STATS_TOOL_NAME, ZHIHU_CREDENTIAL_REF, ZHIHU_GLOBAL_SEARCH_TOOL_NAME, ZHIHU_HOT_LIST_TOOL_NAME, ZHIHU_KNOWLEDGE_SEARCH_TOOL_NAME, ZHIHU_QUESTION_ANSWERS_TOOL_NAME, ZHIHU_QUESTION_RECOMMENDATIONS_TOOL_NAME, ZHIHU_QUOTA_IDS, ZHIHU_QUOTA_TOOL_NAME, ZHIHU_RPC_CHANNEL, ZHIHU_SEARCH_EVENT, ZHIHU_SEARCH_TOOL_NAME, ZHIHU_USER_CONTENT_COMMENTS_TOOL_NAME, ZHIHU_USER_CONTENT_DETAIL_TOOL_NAME, normalizeZhihuQuotaId };
31
32
 
32
33
  //# sourceMappingURL=contracts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["export const ZHIHU_SEARCH_TOOL_NAME = 'zhihu_search'\nexport const ZHIHU_GLOBAL_SEARCH_TOOL_NAME = 'zhihu_global_search'\nexport const ZHIHU_HOT_LIST_TOOL_NAME = 'zhihu_hot_list'\nexport const ZHIHU_ASK_TOOL_NAME = 'zhihu_ask'\nexport const ZHIHU_KNOWLEDGE_SEARCH_TOOL_NAME = 'zhihu_knowledge_search'\nexport const ZHIHU_RPC_CHANNEL = '/zhihu'\nexport const ZHIHU_CREDENTIAL_REF = 'ZHIHU_ACCESS_TOKEN'\nexport const ZHIHU_SEARCH_EVENT = 'dsh-editor/zhihu-search'\nexport type ZhihuRpcResult<T = unknown> = { ok: true; value: T } | { ok: false; error: { code: string; message: string; details: Record<string, unknown> } }\n\nexport const ZHIHU_QUESTION_RECOMMENDATIONS_TOOL_NAME = 'zhihu_question_recommendations'\nexport const ZHIHU_QUESTION_ANSWERS_TOOL_NAME = 'zhihu_question_answers'\nexport const ZHIHU_USER_CONTENT_DETAIL_TOOL_NAME = 'zhihu_user_content_detail'\nexport const ZHIHU_USER_CONTENT_COMMENTS_TOOL_NAME = 'zhihu_user_content_comments'\nexport const ZHIHU_CREATOR_ACCOUNT_STATS_TOOL_NAME = 'zhihu_creator_account_stats'\nexport const ZHIHU_CREATOR_CONTENT_STATS_TOOL_NAME = 'zhihu_creator_content_stats'\nexport const ZHIHU_QUOTA_TOOL_NAME = 'zhihu_quota'\n\n/** Official quota IDs, not local execution counters or predicted balances. */\nexport const ZHIHU_QUOTA_IDS = [\n 'global_search', 'zhihu_search', 'hot_list', 'question_answers',\n 'zhida_openai', 'tools', 'knowledge', 'user_data', 'creator',\n] as const\nexport type ZhihuQuotaId = typeof ZHIHU_QUOTA_IDS[number]\n"],"mappings":";AAAA,MAAa,yBAAyB;AACtC,MAAa,gCAAgC;AAC7C,MAAa,2BAA2B;AACxC,MAAa,sBAAsB;AACnC,MAAa,mCAAmC;AAChD,MAAa,oBAAoB;AACjC,MAAa,uBAAuB;AACpC,MAAa,qBAAqB;AAGlC,MAAa,2CAA2C;AACxD,MAAa,mCAAmC;AAChD,MAAa,sCAAsC;AACnD,MAAa,wCAAwC;AACrD,MAAa,wCAAwC;AACrD,MAAa,wCAAwC;AACrD,MAAa,wBAAwB;;AAGrC,MAAa,kBAAkB;CAC7B;CAAiB;CAAgB;CAAY;CAC7C;CAAgB;CAAS;CAAa;CAAa;AACrD"}
1
+ {"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["export const ZHIHU_SEARCH_TOOL_NAME = 'zhihu_search'\nexport const ZHIHU_GLOBAL_SEARCH_TOOL_NAME = 'zhihu_global_search'\nexport const ZHIHU_HOT_LIST_TOOL_NAME = 'zhihu_hot_list'\nexport const ZHIHU_ASK_TOOL_NAME = 'zhihu_ask'\nexport const ZHIHU_KNOWLEDGE_SEARCH_TOOL_NAME = 'zhihu_knowledge_search'\nexport const ZHIHU_RPC_CHANNEL = '/zhihu'\nexport const ZHIHU_CREDENTIAL_REF = 'ZHIHU_ACCESS_TOKEN'\nexport const ZHIHU_SEARCH_EVENT = 'dsh-editor/zhihu-search'\nexport type ZhihuRpcResult<T = unknown> = { ok: true; value: T } | { ok: false; error: { code: string; message: string; details: Record<string, unknown> } }\n\nexport const ZHIHU_QUESTION_RECOMMENDATIONS_TOOL_NAME = 'zhihu_question_recommendations'\nexport const ZHIHU_QUESTION_ANSWERS_TOOL_NAME = 'zhihu_question_answers'\nexport const ZHIHU_USER_CONTENT_DETAIL_TOOL_NAME = 'zhihu_user_content_detail'\nexport const ZHIHU_USER_CONTENT_COMMENTS_TOOL_NAME = 'zhihu_user_content_comments'\nexport const ZHIHU_CREATOR_ACCOUNT_STATS_TOOL_NAME = 'zhihu_creator_account_stats'\nexport const ZHIHU_CREATOR_CONTENT_STATS_TOOL_NAME = 'zhihu_creator_content_stats'\nexport const ZHIHU_QUOTA_TOOL_NAME = 'zhihu_quota'\n\n/** Official quota IDs, not local execution counters or predicted balances. */\nexport const ZHIHU_QUOTA_IDS = [\n 'global_search', 'zhihu_search', 'hot_list', 'question_discovery',\n 'zhida_openai', 'tools', 'knowledge', 'user_data', 'creator',\n] as const\nexport const normalizeZhihuQuotaId = (value: unknown): unknown => value === 'question_answers' ? 'question_discovery' : value\nexport type ZhihuQuotaId = typeof ZHIHU_QUOTA_IDS[number]\n"],"mappings":";AAAA,MAAa,yBAAyB;AACtC,MAAa,gCAAgC;AAC7C,MAAa,2BAA2B;AACxC,MAAa,sBAAsB;AACnC,MAAa,mCAAmC;AAChD,MAAa,oBAAoB;AACjC,MAAa,uBAAuB;AACpC,MAAa,qBAAqB;AAGlC,MAAa,2CAA2C;AACxD,MAAa,mCAAmC;AAChD,MAAa,sCAAsC;AACnD,MAAa,wCAAwC;AACrD,MAAa,wCAAwC;AACrD,MAAa,wCAAwC;AACrD,MAAa,wBAAwB;;AAGrC,MAAa,kBAAkB;CAC7B;CAAiB;CAAgB;CAAY;CAC7C;CAAgB;CAAS;CAAa;CAAa;AACrD;AACA,MAAa,yBAAyB,UAA4B,UAAU,qBAAqB,uBAAuB"}
package/lib/index.d.ts CHANGED
@@ -1,6 +1,15 @@
1
1
  import { ZhihuQuotaId, ZhihuRpcResult } from "./contracts.js";
2
2
  import { ZhihuUsageRecorder } from "./usage.js";
3
3
  import { Context } from "@deepseek-ai/cordis";
4
+ //#region src/call-policy.d.ts
5
+ /** Compatible with zhihu-search/call_policy.py; never contains credentials. */
6
+ type ZhihuCallPolicyOptions = {
7
+ allowPaid?: boolean;
8
+ dailyCallLimit?: number;
9
+ directory?: string;
10
+ lockTimeoutMs?: number;
11
+ };
12
+ //#endregion
4
13
  //#region src/zhihu-client.d.ts
5
14
  type ZhihuSearchFetcher = (input: string, init?: {
6
15
  method?: string;
@@ -27,6 +36,7 @@ type ZhihuSearchExecuted = {
27
36
  quotaId?: ZhihuQuotaId;
28
37
  };
29
38
  type ZhihuClientOptions = ZhihuSearchResolveOptions & {
39
+ callPolicy?: ZhihuCallPolicyOptions;
30
40
  fetcher?: ZhihuSearchFetcher;
31
41
  /** Allows tests to bypass process env; defaults to process.env. */
32
42
  env?: NodeJS.ProcessEnv;
@@ -51,7 +61,7 @@ type ZhihuService = {
51
61
  };
52
62
  /** One shared service for direct UI RPC and optional Tool wrappers. */
53
63
  declare function createZhihuService(options: ZhihuClientOptions, usage: ZhihuUsageRecorder, notify?: (event: ZhihuSearchExecuted) => void): ZhihuService;
54
- declare function apply(ctx: Context): Promise<void>;
64
+ declare function apply(ctx: Context, config?: ZhihuCallPolicyOptions): Promise<void>;
55
65
  //#endregion
56
66
  export { ZhihuService, apply, createZhihuService, inject, name };
57
67
  //# sourceMappingURL=index.d.ts.map
package/lib/index.js CHANGED
@@ -1,8 +1,9 @@
1
- import { S as zhihuFetchJson, _ as executeZhihuSearch, a as ZHIHU_ASK_DEFAULT_MODEL, b as parseZhihuEnvelope, c as executeZhihuAsk, d as executeZhihuKnowledgeSearch, f as normalizeRecallScopes, i as zhihuOpenPlatformResultCount, l as executeZhihuGlobalSearch, n as executeZhihuOpenPlatform, o as ZHIHU_ASK_MODELS, r as resolveZhihuOpenPlatformOperation, t as ZHIHU_OPEN_PLATFORM_APIS, u as executeZhihuHotList, x as reportExecuted, y as ZhihuSearchError } from "./open-platform.js";
1
+ import { C as resolveZhihuCallPolicy, S as zhihuFetchJson, _ as executeZhihuSearch, a as ZHIHU_ASK_DEFAULT_MODEL, b as parseZhihuEnvelope, c as executeZhihuAsk, d as executeZhihuKnowledgeSearch, f as normalizeRecallScopes, i as zhihuOpenPlatformResultCount, l as executeZhihuGlobalSearch, n as executeZhihuOpenPlatform, o as ZHIHU_ASK_MODELS, r as resolveZhihuOpenPlatformOperation, t as ZHIHU_OPEN_PLATFORM_APIS, u as executeZhihuHotList, x as reportExecuted, y as ZhihuSearchError } from "./open-platform.js";
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
5
  import { registerHostRpcTransport, sendHostRpcResponse } from "@klarkxy/dsh-plugin-kit/host-rpc";
6
+ import { WEB_PROVIDER_AVAILABILITY_EVENT } from "@klarkxy/dsh-plugin-kit/contracts";
6
7
  /** 设置页 RPC 走 JSON/base64 传输,限额压到 20MB(平台上限 100MB)。 */
7
8
  const ZHIHU_KNOWLEDGE_UPLOAD_MAX_BYTES = 20971520;
8
9
  const KNOWLEDGE_BASES_PATH = "/api/v1/knowledge/bases";
@@ -196,7 +197,7 @@ function createZhihuWebSearchProvider(options) {
196
197
  const result = await executeZhihuGlobalSearch(request.query.trim(), count, "all", {
197
198
  ...client,
198
199
  signal,
199
- env: {},
200
+ env: client.env ?? process.env,
200
201
  resolveCredential: async () => apiKey
201
202
  });
202
203
  reportExecuted(onExecuted, {
@@ -254,7 +255,7 @@ function bindZhihuWebSearch(ctx, service) {
254
255
  if (!alive || current !== revision) return;
255
256
  if (configured !== ready) {
256
257
  configured = ready;
257
- inner.emit?.("web/provider-availability-updated", "search", ZHIHU_WEB_SEARCH_ID);
258
+ inner.emit?.(WEB_PROVIDER_AVAILABILITY_EVENT, "search", ZHIHU_WEB_SEARCH_ID);
258
259
  }
259
260
  };
260
261
  const off = registerZhihuGlobalSearchProvider(web, service, () => configured);
@@ -329,6 +330,10 @@ function createZhihuService(options, usage, notify = () => {}) {
329
330
  const quotaId = operation ? ZHIHU_OPEN_PLATFORM_APIS[operation].quotaId : void 0;
330
331
  const metering = quotaId ? { quotaId } : {};
331
332
  try {
333
+ if (endpoint === "policy.status") return {
334
+ ok: true,
335
+ value: resolveZhihuCallPolicy(options.env, options.callPolicy)
336
+ };
332
337
  if (endpoint === "usage.summary") {
333
338
  await pendingUsage;
334
339
  return {
@@ -438,10 +443,13 @@ function createZhihuService(options, usage, notify = () => {}) {
438
443
  }
439
444
  };
440
445
  }
441
- async function apply(ctx) {
446
+ async function apply(ctx, config = {}) {
442
447
  const host = ctx;
443
448
  const domain = await ctx.storageDomain.open(zhihuUsageDomainSpec);
444
- const service = createZhihuService({ resolveCredential: async () => (await host.credentials.resolve(credentialRef(ZHIHU_CREDENTIAL_REF)))?.value }, createZhihuUsageRecorder(domain.table("daily")), (event) => {
449
+ const service = createZhihuService({
450
+ callPolicy: config,
451
+ resolveCredential: async () => (await host.credentials.resolve(credentialRef(ZHIHU_CREDENTIAL_REF)))?.value
452
+ }, createZhihuUsageRecorder(domain.table("daily")), (event) => {
445
453
  ctx.emit.call(ctx, ZHIHU_SEARCH_EVENT, event);
446
454
  });
447
455
  ctx.effect(() => async () => {
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 { 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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"}
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 { WEB_PROVIDER_AVAILABILITY_EVENT, type DshWebManagementDescriptor } from '@klarkxy/dsh-plugin-kit/contracts'\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: DshWebManagementDescriptor = {\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: client.env ?? process.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: typeof WEB_PROVIDER_AVAILABILITY_EVENT, 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_EVENT, '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 { resolveZhihuCallPolicy, type ZhihuCallPolicyOptions } from './call-policy.ts'\nimport 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 === 'policy.status') return { ok: true, value: resolveZhihuCallPolicy(options.env, options.callPolicy) }\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, config: ZhihuCallPolicyOptions = {}): Promise<void> {\n const host = ctx as Host\n const domain = await ctx.storageDomain.open(zhihuUsageDomainSpec)\n const service = createZhihuService({\n callPolicy: config,\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,7 +1,178 @@
1
- import { ZHIHU_QUOTA_IDS } from "./contracts.js";
2
- import { readFile } from "node:fs/promises";
1
+ import { ZHIHU_QUOTA_IDS, normalizeZhihuQuotaId } from "./contracts.js";
2
+ import { mkdir, open, readFile, rename, rmdir, unlink } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
+ import { randomUUID } from "node:crypto";
6
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
7
+ //#region src/call-policy.ts
8
+ var ZhihuCallPolicyError = class extends Error {};
9
+ const blocked = (message) => {
10
+ throw new ZhihuCallPolicyError(message);
11
+ };
12
+ const GROUPS = {
13
+ "/api/v1/content/zhihu_search": "zhihu_search",
14
+ "/api/v1/content/global_search": "global_search",
15
+ "/api/v1/content/hot_list": "hot_list",
16
+ "/api/v1/content/question_answers": "question_discovery",
17
+ "/api/v1/user/contents": "user_data",
18
+ "/api/v1/user/followees": "user_data",
19
+ "/api/v1/user/collections": "user_data",
20
+ "/api/v1/user/favlists": "user_data",
21
+ "/api/v1/user/favlist_contents": "user_data",
22
+ "/api/v1/user/content_detail": "creator",
23
+ "/api/v1/user/content_comments": "creator",
24
+ "/api/v1/user/creator_account_stats": "creator",
25
+ "/api/v1/user/creator_content_stats": "creator",
26
+ "/api/v1/knowledge/bases": "knowledge",
27
+ "/api/v1/knowledge/files": "knowledge",
28
+ "/api/v1/knowledge/search": "knowledge"
29
+ };
30
+ const PRICED_PATHS = new Set(Object.keys(GROUPS));
31
+ GROUPS["/api/v1/user/question_recommendations"] = "question_discovery";
32
+ function zhihuQuotaGroup(path, body) {
33
+ if (path === "/v1/chat/completions" && body && typeof body === "object" && [
34
+ "zhida-agent",
35
+ "zhida-fast-1p5",
36
+ "zhida-thinking-1p5"
37
+ ].includes(String(body.model))) return "zhida_openai";
38
+ if (/^\/api\/v1\/knowledge\/bases\/[^/]+\/items$/.test(path)) return "knowledge";
39
+ if (/^\/api\/v1\/(?:pdf-parse|ppt-generation)\/tasks(?:\/[^/]+)?$/.test(path)) return "tools";
40
+ return GROUPS[path] ?? blocked("调用已阻止:该能力的官方额度映射未确认;不能擅自套价。");
41
+ }
42
+ function resolveZhihuCallPolicy(env = process.env, options = {}) {
43
+ const raw = (env.ZHIHU_ALLOW_PAID ?? "false").toLowerCase();
44
+ if (options.allowPaid === void 0 && ![
45
+ "true",
46
+ "false",
47
+ "1",
48
+ "0"
49
+ ].includes(raw)) blocked("ZHIHU_ALLOW_PAID 必须是 true/false 或 1/0。");
50
+ const allowPaid = options.allowPaid ?? ["true", "1"].includes(raw);
51
+ const capText = env.ZHIHU_DAILY_CALL_LIMIT ?? "100";
52
+ if (options.dailyCallLimit === void 0 && !/^\d+$/.test(capText)) blocked("每日调用上限必须是 1-100000 的整数。");
53
+ const dailyCallLimit = options.dailyCallLimit ?? Number(capText);
54
+ if (typeof allowPaid !== "boolean" || !Number.isInteger(dailyCallLimit) || dailyCallLimit < 1 || dailyCallLimit > 1e5) blocked("付费开关必须是布尔值,每日调用上限必须是 1-100000 的整数。");
55
+ return {
56
+ allowPaid,
57
+ dailyCallLimit
58
+ };
59
+ }
60
+ const integer = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
61
+ const day = () => new Date(Date.now() + 288e5).toISOString().slice(0, 10);
62
+ const code = (error) => error?.code;
63
+ async function readLedger(directory) {
64
+ let value;
65
+ try {
66
+ value = JSON.parse(await readFile(join(directory, "daily.json"), "utf8"));
67
+ } catch (error) {
68
+ if (code(error) === "ENOENT") return {
69
+ version: 1,
70
+ day: day(),
71
+ calls: 0,
72
+ uncertain: {},
73
+ free: {}
74
+ };
75
+ return blocked("本地调用记录不可读;已阻止请求,请检查 call-policy/daily.json。");
76
+ }
77
+ const row = value;
78
+ if (!row || row.version !== 1 || typeof row.day !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(row.day) || !integer(row.calls) || !row.uncertain || Array.isArray(row.uncertain) || typeof row.uncertain !== "object" || !Object.values(row.uncertain).every(integer)) blocked("本地调用记录无效;已阻止请求,不能自动清零。");
79
+ if (row.free === void 0) row.free = {};
80
+ if (!row.free || Array.isArray(row.free) || typeof row.free !== "object" || !Object.values(row.free).every(integer)) blocked("本地免费预留记录无效;已阻止请求。");
81
+ if (row.day !== day()) {
82
+ row.day = day();
83
+ row.calls = 0;
84
+ row.free = {};
85
+ }
86
+ return row;
87
+ }
88
+ async function writeLedger(directory, row) {
89
+ const temporary = join(directory, `daily-${randomUUID()}.tmp`);
90
+ try {
91
+ const file = await open(temporary, "wx");
92
+ try {
93
+ await file.writeFile(JSON.stringify(row), "utf8");
94
+ await file.sync();
95
+ } finally {
96
+ await file.close();
97
+ }
98
+ await rename(temporary, join(directory, "daily.json"));
99
+ } catch {
100
+ blocked("本地调用记录保存失败;已阻止请求。");
101
+ } finally {
102
+ await unlink(temporary).catch((error) => {
103
+ if (code(error) !== "ENOENT") throw error;
104
+ });
105
+ }
106
+ }
107
+ async function withZhihuCallPolicy(path, body, quota, execute, options = {}) {
108
+ const group = zhihuQuotaGroup(path, body);
109
+ const env = options.env ?? process.env;
110
+ const policy = resolveZhihuCallPolicy(env, options);
111
+ const priced = PRICED_PATHS.has(path) || /^\/api\/v1\/knowledge\/bases\/[^/]+\/items$/.test(path) || path === "/v1/chat/completions" && body && typeof body === "object" && body.model === "zhida-agent";
112
+ if (policy.allowPaid && !priced) blocked("付费调用已阻止:该能力的官方计费未确认;请使用免费保护模式。");
113
+ const directory = options.directory ?? env.ZHIHU_CALL_POLICY_DIR ?? join(env.ZHIHU_SEARCH_HOME ?? join(homedir(), ".config", "zhihu-search"), "call-policy");
114
+ const lock = join(directory, "request.lock");
115
+ let acquired = false;
116
+ const deadline = Date.now() + (options.lockTimeoutMs ?? 3e4);
117
+ try {
118
+ options.signal?.throwIfAborted();
119
+ await mkdir(directory, { recursive: true });
120
+ while (!acquired) {
121
+ options.signal?.throwIfAborted();
122
+ try {
123
+ await mkdir(lock);
124
+ acquired = true;
125
+ } catch (error) {
126
+ if (code(error) !== "EEXIST") throw error;
127
+ if (Date.now() >= deadline) blocked("本地保护锁忙或遗留;已阻止请求。确认两个客户端均无在途请求后再移除 call-policy/request.lock。");
128
+ await setTimeout$1(50, void 0, { signal: options.signal });
129
+ }
130
+ }
131
+ options.signal?.throwIfAborted();
132
+ const row = await readLedger(directory);
133
+ if (row.calls >= policy.dailyCallLimit) blocked("已达到本地北京时间每日业务调用上限;额度查询仍可使用。");
134
+ if (!policy.allowPaid) {
135
+ let rows;
136
+ try {
137
+ rows = await quota(group);
138
+ } catch {
139
+ options.signal?.throwIfAborted();
140
+ blocked("免费额度预查失败;未发送业务请求,请稍后手动查询官方额度。");
141
+ }
142
+ options.signal?.throwIfAborted();
143
+ const matches = Array.isArray(rows) ? rows.filter((value) => value && typeof value === "object" && value.APIID === group) : [];
144
+ if (matches.length !== 1 || !integer(matches[0].RemainingQuota)) blocked("官方未返回有效的免费剩余额度;未发送业务请求。");
145
+ const available = Math.min(matches[0].RemainingQuota - (row.uncertain[group] ?? 0), row.free[group] ?? matches[0].RemainingQuota);
146
+ if (available <= 0) blocked("免费额度不足,或有结果未知的已发送请求;未发送业务请求。");
147
+ row.free[group] = available - 1;
148
+ } else if (row.free[group] !== void 0) row.free[group] = Math.max(0, row.free[group] - 1);
149
+ options.signal?.throwIfAborted();
150
+ row.calls++;
151
+ row.uncertain[group] = (row.uncertain[group] ?? 0) + 1;
152
+ await writeLedger(directory, row);
153
+ options.signal?.throwIfAborted();
154
+ const result = await execute();
155
+ row.uncertain[group]--;
156
+ await writeLedger(directory, row);
157
+ return result;
158
+ } catch (error) {
159
+ options.signal?.throwIfAborted();
160
+ if (error instanceof ZhihuCallPolicyError) throw error;
161
+ if ([
162
+ "EACCES",
163
+ "EPERM",
164
+ "ENOSPC",
165
+ "EIO",
166
+ "EROFS",
167
+ "ENOENT",
168
+ "ENOTDIR"
169
+ ].includes(String(code(error)))) blocked("本地保护存储不可用;已阻止业务请求。");
170
+ throw error;
171
+ } finally {
172
+ if (acquired) await rmdir(lock);
173
+ }
174
+ }
175
+ //#endregion
5
176
  //#region src/zhihu-client.ts
6
177
  /**
7
178
  * 知乎开放平台共享请求层:Access Secret 解析、超时封装、信封解析与执行计量。
@@ -180,10 +351,34 @@ async function zhihuFetchJson(path, init, label, options = {}) {
180
351
  if (!response.ok) throw httpError();
181
352
  return body;
182
353
  };
183
- return await Promise.race([request(), aborted]);
354
+ const quota = async (apiId) => {
355
+ const quotaUrl = new URL("/api/v1/quota", ZHIHU_BASE_URL);
356
+ quotaUrl.searchParams.set("APIIDs", apiId);
357
+ const response = await fetcher(quotaUrl.toString(), {
358
+ method: "GET",
359
+ signal: controller.signal,
360
+ headers: {
361
+ Authorization: `Bearer ${token.token}`,
362
+ "X-Request-Timestamp": String(Math.floor(Date.now() / 1e3)),
363
+ Accept: "application/json"
364
+ }
365
+ });
366
+ const data = parseZhihuEnvelope(await response.json(), {
367
+ status: response.status,
368
+ officialErrors: true
369
+ });
370
+ if (!response.ok) throw new Error("官方额度查询失败");
371
+ return data;
372
+ };
373
+ return await (path === "/api/v1/quota" && method === "GET" ? Promise.race([request(), aborted]) : withZhihuCallPolicy(path, init.body, (apiId) => Promise.race([quota(apiId), aborted]), () => Promise.race([request(), aborted]), {
374
+ ...options.callPolicy,
375
+ env: options.env,
376
+ signal: controller.signal
377
+ }));
184
378
  } catch (error) {
185
379
  if (linked?.aborted) throw new ZhihuSearchError("CANCELLED", "请求已取消");
186
380
  if (controller.signal.aborted || isAbortError(error)) throw new ZhihuSearchError("TIMEOUT", `${label}超时(${Math.round(timeout / 1e3)} 秒),请稍后再试。`);
381
+ if (error instanceof ZhihuCallPolicyError) throw new ZhihuSearchError("CALL_BLOCKED", error.message, void 0, { retryable: false });
187
382
  if (error instanceof ZhihuSearchError) {
188
383
  error.message = error.message.replaceAll(token.token, "[REDACTED]");
189
384
  throw error;
@@ -475,12 +670,12 @@ const ZHIHU_OPEN_PLATFORM_APIS = {
475
670
  "question.recommendations": {
476
671
  path: "/api/v1/user/question_recommendations",
477
672
  label: "适合回答的问题",
478
- quotaId: "creator"
673
+ quotaId: "question_discovery"
479
674
  },
480
675
  "question.answers": {
481
676
  path: "/api/v1/content/question_answers",
482
677
  label: "问题回答摘要",
483
- quotaId: "question_answers"
678
+ quotaId: "question_discovery"
484
679
  },
485
680
  "content.detail": {
486
681
  path: "/api/v1/user/content_detail",
@@ -550,7 +745,7 @@ function inputObject(input) {
550
745
  "useConfiguredOauthUser",
551
746
  "use_configured_oauth_user"
552
747
  ]) if (Object.hasOwn(input, key)) invalid("这些接口仅使用当前 Access Secret,不接受 token 参数或 OAuth 身份切换。");
553
- return input;
748
+ return { ...input };
554
749
  }
555
750
  function limit(value, fallback, maximum, name = "limit") {
556
751
  const count = value === void 0 ? fallback : value;
@@ -613,6 +808,7 @@ function paramsFor(operation, input) {
613
808
  ].includes(type)) invalid("contentType 必须是 all、answer、article、pin 或 zvideo。");
614
809
  params.ContentType = type;
615
810
  } else if (operation === "quota" && input.apiIds !== void 0) {
811
+ if (Array.isArray(input.apiIds)) input.apiIds = input.apiIds.map(normalizeZhihuQuotaId);
616
812
  if (!Array.isArray(input.apiIds) || input.apiIds.some((id) => typeof id !== "string" || !ZHIHU_QUOTA_IDS.includes(id))) invalid("apiIds 包含未知官方额度项。");
617
813
  if (input.apiIds.length) params.APIIDs = [...new Set(input.apiIds)].join(",");
618
814
  }
@@ -682,6 +878,6 @@ function zhihuOpenPlatformResultCount(result) {
682
878
  return Array.isArray(result.data.Items) ? result.data.Items.filter(record).length : 0;
683
879
  }
684
880
  //#endregion
685
- export { zhihuFetchJson as S, executeZhihuSearch as _, ZHIHU_ASK_DEFAULT_MODEL as a, parseZhihuEnvelope as b, executeZhihuAsk as c, executeZhihuKnowledgeSearch as d, normalizeRecallScopes as f, renderZhihuKnowledgeSearch as g, renderZhihuHotList as h, zhihuOpenPlatformResultCount as i, executeZhihuGlobalSearch as l, renderZhihuGlobalSearch as m, executeZhihuOpenPlatform as n, ZHIHU_ASK_MODELS as o, renderZhihuAsk as p, resolveZhihuOpenPlatformOperation as r, ZHIHU_RECALL_SCOPES as s, ZHIHU_OPEN_PLATFORM_APIS as t, executeZhihuHotList as u, renderZhihuSearch as v, reportExecuted as x, ZhihuSearchError as y };
881
+ export { resolveZhihuCallPolicy as C, zhihuFetchJson as S, executeZhihuSearch as _, ZHIHU_ASK_DEFAULT_MODEL as a, parseZhihuEnvelope as b, executeZhihuAsk as c, executeZhihuKnowledgeSearch as d, normalizeRecallScopes as f, renderZhihuKnowledgeSearch as g, renderZhihuHotList as h, zhihuOpenPlatformResultCount as i, executeZhihuGlobalSearch as l, renderZhihuGlobalSearch as m, executeZhihuOpenPlatform as n, ZHIHU_ASK_MODELS as o, renderZhihuAsk as p, resolveZhihuOpenPlatformOperation as r, ZHIHU_RECALL_SCOPES as s, ZHIHU_OPEN_PLATFORM_APIS as t, executeZhihuHotList as u, renderZhihuSearch as v, reportExecuted as x, ZhihuSearchError as y };
686
882
 
687
883
  //# sourceMappingURL=open-platform.js.map