pi-deepseek-web-search 0.1.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/LICENSE +21 -0
- package/README.md +137 -0
- package/README.zh.md +137 -0
- package/package.json +55 -0
- package/schema/deepseek-web-search.config.schema.en.json +186 -0
- package/schema/deepseek-web-search.config.schema.zh.json +186 -0
- package/src/cache.ts +45 -0
- package/src/config-schema.ts +176 -0
- package/src/config.ts +289 -0
- package/src/deepseek.ts +272 -0
- package/src/format.ts +159 -0
- package/src/index.ts +193 -0
- package/src/provider.ts +109 -0
- package/src/render.ts +67 -0
- package/src/types.ts +21 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"properties": {
|
|
4
|
+
"apiKey": {
|
|
5
|
+
"type": "string",
|
|
6
|
+
"minLength": 1,
|
|
7
|
+
"description": "DeepSeek API key(必填)。优先级:环境变量 DEEPSEEK_API_KEY > 本字段。"
|
|
8
|
+
},
|
|
9
|
+
"baseUrl": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"default": "https://api.deepseek.com",
|
|
12
|
+
"description": "API 基础地址(自建代理时修改)。环境变量 DEEPSEEK_BASE_URL 可覆盖。"
|
|
13
|
+
},
|
|
14
|
+
"model": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"default": "deepseek-v4-flash",
|
|
17
|
+
"minLength": 1,
|
|
18
|
+
"pattern": "^(?!\\s*$)",
|
|
19
|
+
"description": "模型名。Responses API 目前仅支持 deepseek-v4-flash;v4-pro 适配后改这里即切换(价格自动取 prices.models)。环境变量 DEEPSEEK_MODEL 可覆盖。"
|
|
20
|
+
},
|
|
21
|
+
"reasoningEffort": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"enum": [
|
|
24
|
+
"off",
|
|
25
|
+
"low",
|
|
26
|
+
"high",
|
|
27
|
+
"auto"
|
|
28
|
+
],
|
|
29
|
+
"default": "low",
|
|
30
|
+
"description": "推理强度:off 关闭思考(省 token)、low/high 控制思考量、auto 用模型默认行为。环境变量 DEEPSEEK_REASONING_EFFORT 可覆盖。"
|
|
31
|
+
},
|
|
32
|
+
"maxOutputTokens": {
|
|
33
|
+
"type": "integer",
|
|
34
|
+
"minimum": 1,
|
|
35
|
+
"default": 4096,
|
|
36
|
+
"description": "回答最大输出 token 数。环境变量 DEEPSEEK_MAX_OUTPUT_TOKENS 可覆盖。"
|
|
37
|
+
},
|
|
38
|
+
"timeoutMs": {
|
|
39
|
+
"type": "integer",
|
|
40
|
+
"minimum": 1,
|
|
41
|
+
"default": 30000,
|
|
42
|
+
"description": "单次请求超时(毫秒)。环境变量 DEEPSEEK_TIMEOUT_MS 可覆盖。"
|
|
43
|
+
},
|
|
44
|
+
"cacheTtlMs": {
|
|
45
|
+
"type": "integer",
|
|
46
|
+
"minimum": 1,
|
|
47
|
+
"default": 300000,
|
|
48
|
+
"description": "搜索结果缓存 TTL(毫秒)。环境变量 DEEPSEEK_CACHE_TTL_MS 可覆盖。"
|
|
49
|
+
},
|
|
50
|
+
"maxResultChars": {
|
|
51
|
+
"type": "integer",
|
|
52
|
+
"minimum": 1,
|
|
53
|
+
"default": 50000,
|
|
54
|
+
"description": "返回给 LLM 的文本最大字符数(超出截断)。环境变量 DEEPSEEK_MAX_RESULT_CHARS 可覆盖。"
|
|
55
|
+
},
|
|
56
|
+
"prices": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"properties": {
|
|
59
|
+
"inputPerMillion": {
|
|
60
|
+
"type": "number",
|
|
61
|
+
"exclusiveMinimum": 0,
|
|
62
|
+
"default": 1,
|
|
63
|
+
"description": "输入单价(缓存未命中)。环境变量 DEEPSEEK_PRICE_INPUT 可覆盖。"
|
|
64
|
+
},
|
|
65
|
+
"cachedInputPerMillion": {
|
|
66
|
+
"type": "number",
|
|
67
|
+
"exclusiveMinimum": 0,
|
|
68
|
+
"default": 0.02,
|
|
69
|
+
"description": "输入单价(缓存命中)。环境变量 DEEPSEEK_PRICE_CACHED_INPUT 可覆盖。"
|
|
70
|
+
},
|
|
71
|
+
"outputPerMillion": {
|
|
72
|
+
"type": "number",
|
|
73
|
+
"exclusiveMinimum": 0,
|
|
74
|
+
"default": 2,
|
|
75
|
+
"description": "输出单价。环境变量 DEEPSEEK_PRICE_OUTPUT 可覆盖。"
|
|
76
|
+
},
|
|
77
|
+
"peak": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"required": [
|
|
80
|
+
"multiplier",
|
|
81
|
+
"hours"
|
|
82
|
+
],
|
|
83
|
+
"properties": {
|
|
84
|
+
"multiplier": {
|
|
85
|
+
"type": "number",
|
|
86
|
+
"exclusiveMinimum": 0,
|
|
87
|
+
"description": "高峰单价倍率(官方规则为 2)。"
|
|
88
|
+
},
|
|
89
|
+
"hours": {
|
|
90
|
+
"type": "array",
|
|
91
|
+
"items": {
|
|
92
|
+
"type": "array",
|
|
93
|
+
"additionalItems": false,
|
|
94
|
+
"items": [
|
|
95
|
+
{
|
|
96
|
+
"type": "string",
|
|
97
|
+
"pattern": "^(?:[01][0-9]|2[0-3]):[0-5][0-9]$"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"type": "string",
|
|
101
|
+
"pattern": "^(?:[01][0-9]|2[0-3]):[0-5][0-9]$"
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"minItems": 2
|
|
105
|
+
},
|
|
106
|
+
"minItems": 1,
|
|
107
|
+
"description": "高峰时段列表,半开区间 [start, end)(09:00 计高峰、12:00 不计)。"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"additionalProperties": false,
|
|
111
|
+
"description": "峰谷定价:高峰时段价格 = 平峰 × multiplier(适用所有计费项)。官方定义高峰时段为北京时间每日 9:00-12:00 与 14:00-18:00。"
|
|
112
|
+
},
|
|
113
|
+
"models": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"patternProperties": {
|
|
116
|
+
"^.*$": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": {
|
|
119
|
+
"inputPerMillion": {
|
|
120
|
+
"type": "number",
|
|
121
|
+
"exclusiveMinimum": 0,
|
|
122
|
+
"description": "输入单价(缓存未命中)。"
|
|
123
|
+
},
|
|
124
|
+
"cachedInputPerMillion": {
|
|
125
|
+
"type": "number",
|
|
126
|
+
"exclusiveMinimum": 0,
|
|
127
|
+
"description": "输入单价(缓存命中)。"
|
|
128
|
+
},
|
|
129
|
+
"outputPerMillion": {
|
|
130
|
+
"type": "number",
|
|
131
|
+
"exclusiveMinimum": 0,
|
|
132
|
+
"description": "输出单价。"
|
|
133
|
+
},
|
|
134
|
+
"peak": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"required": [
|
|
137
|
+
"multiplier",
|
|
138
|
+
"hours"
|
|
139
|
+
],
|
|
140
|
+
"properties": {
|
|
141
|
+
"multiplier": {
|
|
142
|
+
"type": "number",
|
|
143
|
+
"exclusiveMinimum": 0,
|
|
144
|
+
"description": "高峰单价倍率(官方规则为 2)。"
|
|
145
|
+
},
|
|
146
|
+
"hours": {
|
|
147
|
+
"type": "array",
|
|
148
|
+
"items": {
|
|
149
|
+
"type": "array",
|
|
150
|
+
"additionalItems": false,
|
|
151
|
+
"items": [
|
|
152
|
+
{
|
|
153
|
+
"type": "string",
|
|
154
|
+
"pattern": "^(?:[01][0-9]|2[0-3]):[0-5][0-9]$"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"type": "string",
|
|
158
|
+
"pattern": "^(?:[01][0-9]|2[0-3]):[0-5][0-9]$"
|
|
159
|
+
}
|
|
160
|
+
],
|
|
161
|
+
"minItems": 2
|
|
162
|
+
},
|
|
163
|
+
"minItems": 1,
|
|
164
|
+
"description": "高峰时段列表,半开区间 [start, end)(09:00 计高峰、12:00 不计)。"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"additionalProperties": false,
|
|
168
|
+
"description": "峰谷定价:高峰时段价格 = 平峰 × multiplier(适用所有计费项)。官方定义高峰时段为北京时间每日 9:00-12:00 与 14:00-18:00。"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"additionalProperties": false,
|
|
172
|
+
"description": "单模型价格覆盖:缺省项回退顶层价格。"
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
"description": "按模型覆盖的价格表(key = 模型名,命中优先;整块替换默认表)。"
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"additionalProperties": false,
|
|
179
|
+
"description": "价格配置,单位:元/百万 tokens。默认值为官方平峰价。"
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"additionalProperties": false,
|
|
183
|
+
"description": "Generated by pnpm gen:schema from src/config-schema.ts — do not edit. 由 src/config-schema.ts 自动生成,请勿手改。",
|
|
184
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
185
|
+
"title": "deepseek-web-search config (zh)"
|
|
186
|
+
}
|
package/src/cache.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// 进程内 TTL 缓存:避免短时间内重复搜索相同 query 造成重复开销。
|
|
2
|
+
|
|
3
|
+
interface CacheEntry<T> {
|
|
4
|
+
value: T;
|
|
5
|
+
expiresAt: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class TtlCache<T> {
|
|
9
|
+
private readonly entries = new Map<string, CacheEntry<T>>();
|
|
10
|
+
private readonly ttlMs: number;
|
|
11
|
+
|
|
12
|
+
constructor(ttlMs: number) {
|
|
13
|
+
this.ttlMs = ttlMs;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 已过期条目视为未命中(惰性删除) */
|
|
17
|
+
get(key: string): T | undefined {
|
|
18
|
+
const entry = this.entries.get(key);
|
|
19
|
+
if (entry === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
if (Date.now() >= entry.expiresAt) {
|
|
22
|
+
this.entries.delete(key);
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return entry.value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
set(key: string, value: T): void {
|
|
29
|
+
this.entries.set(key, { value, expiresAt: Date.now() + this.ttlMs });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
clear(): void {
|
|
33
|
+
this.entries.clear();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 当前有效条目数(惰性清理过期条目后统计) */
|
|
37
|
+
size(): number {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
for (const [key, entry] of this.entries) {
|
|
40
|
+
if (now >= entry.expiresAt)
|
|
41
|
+
this.entries.delete(key);
|
|
42
|
+
}
|
|
43
|
+
return this.entries.size;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { Static } from "typebox";
|
|
2
|
+
import type { ReasoningEffort } from "./provider.js";
|
|
3
|
+
// config-schema.ts —— 配置文件结构的单一事实来源(TypeBox)。
|
|
4
|
+
// 派生:① ConfigFileShape 类型(config.ts 经 Static)② 运行时校验(Check/Errors)③ 中/英两份 JSON Schema(pnpm gen:schema)。
|
|
5
|
+
// 双语注释约定:descriptionZh(中文)/ descriptionEn(英文),生成时映射为标准 description。
|
|
6
|
+
// 说明:reasoningEffort 用 Type.Unsafe 而非 StringEnum(StringEnum 不透传自定义注释键;配置 schema 无 Gemini 兼容约束)。
|
|
7
|
+
import { Type } from "typebox";
|
|
8
|
+
|
|
9
|
+
/** HH:MM 24 小时制(00:00-23:59) */
|
|
10
|
+
const TIME_PATTERN = "^(?:[01][0-9]|2[0-3]):[0-5][0-9]$";
|
|
11
|
+
|
|
12
|
+
/** 峰谷定价覆盖(可选)。官方定义:高峰 = 平峰 × multiplier,时段为北京时间每日 */
|
|
13
|
+
export const peakSchema = Type.Object({
|
|
14
|
+
multiplier: Type.Number({
|
|
15
|
+
exclusiveMinimum: 0,
|
|
16
|
+
descriptionZh: "高峰单价倍率(官方规则为 2)。",
|
|
17
|
+
descriptionEn: "Peak multiplier; official value is 2.",
|
|
18
|
+
}),
|
|
19
|
+
hours: Type.Array(
|
|
20
|
+
Type.Tuple([
|
|
21
|
+
Type.String({ pattern: TIME_PATTERN }),
|
|
22
|
+
Type.String({ pattern: TIME_PATTERN }),
|
|
23
|
+
]),
|
|
24
|
+
{
|
|
25
|
+
minItems: 1,
|
|
26
|
+
descriptionZh: "高峰时段列表,半开区间 [start, end)(09:00 计高峰、12:00 不计)。",
|
|
27
|
+
descriptionEn: "Peak hour ranges, half-open [start, end).",
|
|
28
|
+
},
|
|
29
|
+
),
|
|
30
|
+
}, {
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
descriptionZh: "峰谷定价:高峰时段价格 = 平峰 × multiplier(适用所有计费项)。官方定义高峰时段为北京时间每日 9:00-12:00 与 14:00-18:00。",
|
|
33
|
+
descriptionEn: "Peak pricing; official peak hours are daily 09:00-12:00 and 14:00-18:00 Beijing time.",
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/** 单模型价格覆盖(部分覆盖:缺省项回退顶层价格;无 models 键 + additionalProperties:false 天然禁止嵌套) */
|
|
37
|
+
export const modelPriceSchema = Type.Object({
|
|
38
|
+
inputPerMillion: Type.Optional(Type.Number({
|
|
39
|
+
exclusiveMinimum: 0,
|
|
40
|
+
descriptionZh: "输入单价(缓存未命中)。",
|
|
41
|
+
descriptionEn: "Input price, cache miss.",
|
|
42
|
+
})),
|
|
43
|
+
cachedInputPerMillion: Type.Optional(Type.Number({
|
|
44
|
+
exclusiveMinimum: 0,
|
|
45
|
+
descriptionZh: "输入单价(缓存命中)。",
|
|
46
|
+
descriptionEn: "Input price, cache hit.",
|
|
47
|
+
})),
|
|
48
|
+
outputPerMillion: Type.Optional(Type.Number({
|
|
49
|
+
exclusiveMinimum: 0,
|
|
50
|
+
descriptionZh: "输出单价。",
|
|
51
|
+
descriptionEn: "Output price.",
|
|
52
|
+
})),
|
|
53
|
+
peak: Type.Optional(peakSchema),
|
|
54
|
+
}, {
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
descriptionZh: "单模型价格覆盖:缺省项回退顶层价格。",
|
|
57
|
+
descriptionEn: "Per-model price override; missing fields fall back to top-level prices.",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** 价格配置(元/百万 tokens,依据官方定价页 2026-08) */
|
|
61
|
+
export const pricesSchema = Type.Object({
|
|
62
|
+
inputPerMillion: Type.Optional(Type.Number({
|
|
63
|
+
exclusiveMinimum: 0,
|
|
64
|
+
default: 1,
|
|
65
|
+
descriptionZh: "输入单价(缓存未命中)。环境变量 DEEPSEEK_PRICE_INPUT 可覆盖。",
|
|
66
|
+
descriptionEn: "Input price, cache miss. Overridable via DEEPSEEK_PRICE_INPUT.",
|
|
67
|
+
})),
|
|
68
|
+
cachedInputPerMillion: Type.Optional(Type.Number({
|
|
69
|
+
exclusiveMinimum: 0,
|
|
70
|
+
default: 0.02,
|
|
71
|
+
descriptionZh: "输入单价(缓存命中)。环境变量 DEEPSEEK_PRICE_CACHED_INPUT 可覆盖。",
|
|
72
|
+
descriptionEn: "Input price, cache hit. Overridable via DEEPSEEK_PRICE_CACHED_INPUT.",
|
|
73
|
+
})),
|
|
74
|
+
outputPerMillion: Type.Optional(Type.Number({
|
|
75
|
+
exclusiveMinimum: 0,
|
|
76
|
+
default: 2,
|
|
77
|
+
descriptionZh: "输出单价。环境变量 DEEPSEEK_PRICE_OUTPUT 可覆盖。",
|
|
78
|
+
descriptionEn: "Output price. Overridable via DEEPSEEK_PRICE_OUTPUT.",
|
|
79
|
+
})),
|
|
80
|
+
peak: Type.Optional(peakSchema),
|
|
81
|
+
models: Type.Optional(Type.Record(Type.String(), modelPriceSchema, {
|
|
82
|
+
descriptionZh: "按模型覆盖的价格表(key = 模型名,命中优先;整块替换默认表)。",
|
|
83
|
+
descriptionEn: "Per-model price overrides, keyed by model name.",
|
|
84
|
+
})),
|
|
85
|
+
}, {
|
|
86
|
+
additionalProperties: false,
|
|
87
|
+
descriptionZh: "价格配置,单位:元/百万 tokens。默认值为官方平峰价。",
|
|
88
|
+
descriptionEn: "Pricing in CNY per million tokens; defaults are the official off-peak prices.",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/** 配置文件结构(所有字段可选,按优先级合并)——单一事实来源 */
|
|
92
|
+
export const configFileSchema = Type.Object({
|
|
93
|
+
apiKey: Type.Optional(Type.String({
|
|
94
|
+
minLength: 1,
|
|
95
|
+
descriptionZh: "DeepSeek API key(必填)。优先级:环境变量 DEEPSEEK_API_KEY > 本字段。",
|
|
96
|
+
descriptionEn: "DeepSeek API key (required). Env DEEPSEEK_API_KEY takes precedence.",
|
|
97
|
+
})),
|
|
98
|
+
baseUrl: Type.Optional(Type.String({
|
|
99
|
+
default: "https://api.deepseek.com",
|
|
100
|
+
descriptionZh: "API 基础地址(自建代理时修改)。环境变量 DEEPSEEK_BASE_URL 可覆盖。",
|
|
101
|
+
descriptionEn: "API base URL; change when using a proxy. Overridable via DEEPSEEK_BASE_URL.",
|
|
102
|
+
})),
|
|
103
|
+
model: Type.Optional(Type.String({
|
|
104
|
+
default: "deepseek-v4-flash",
|
|
105
|
+
minLength: 1,
|
|
106
|
+
pattern: "^(?!\\s*$)",
|
|
107
|
+
descriptionZh: "模型名。Responses API 目前仅支持 deepseek-v4-flash;v4-pro 适配后改这里即切换(价格自动取 prices.models)。环境变量 DEEPSEEK_MODEL 可覆盖。",
|
|
108
|
+
descriptionEn: "Model name; switch to deepseek-v4-pro once supported. Overridable via DEEPSEEK_MODEL.",
|
|
109
|
+
})),
|
|
110
|
+
reasoningEffort: Type.Optional(Type.Unsafe<ReasoningEffort>({
|
|
111
|
+
type: "string",
|
|
112
|
+
enum: ["off", "low", "high", "auto"],
|
|
113
|
+
default: "low",
|
|
114
|
+
descriptionZh: "推理强度:off 关闭思考(省 token)、low/high 控制思考量、auto 用模型默认行为。环境变量 DEEPSEEK_REASONING_EFFORT 可覆盖。",
|
|
115
|
+
descriptionEn: "Reasoning effort: off disables thinking; auto uses model default. Overridable via DEEPSEEK_REASONING_EFFORT.",
|
|
116
|
+
})),
|
|
117
|
+
maxOutputTokens: Type.Optional(Type.Integer({
|
|
118
|
+
minimum: 1,
|
|
119
|
+
default: 4096,
|
|
120
|
+
descriptionZh: "回答最大输出 token 数。环境变量 DEEPSEEK_MAX_OUTPUT_TOKENS 可覆盖。",
|
|
121
|
+
descriptionEn: "Max output tokens. Overridable via DEEPSEEK_MAX_OUTPUT_TOKENS.",
|
|
122
|
+
})),
|
|
123
|
+
timeoutMs: Type.Optional(Type.Integer({
|
|
124
|
+
minimum: 1,
|
|
125
|
+
default: 30_000,
|
|
126
|
+
descriptionZh: "单次请求超时(毫秒)。环境变量 DEEPSEEK_TIMEOUT_MS 可覆盖。",
|
|
127
|
+
descriptionEn: "Request timeout in ms. Overridable via DEEPSEEK_TIMEOUT_MS.",
|
|
128
|
+
})),
|
|
129
|
+
cacheTtlMs: Type.Optional(Type.Integer({
|
|
130
|
+
minimum: 1,
|
|
131
|
+
default: 300_000,
|
|
132
|
+
descriptionZh: "搜索结果缓存 TTL(毫秒)。环境变量 DEEPSEEK_CACHE_TTL_MS 可覆盖。",
|
|
133
|
+
descriptionEn: "Search result cache TTL in ms. Overridable via DEEPSEEK_CACHE_TTL_MS.",
|
|
134
|
+
})),
|
|
135
|
+
maxResultChars: Type.Optional(Type.Integer({
|
|
136
|
+
minimum: 1,
|
|
137
|
+
default: 50_000,
|
|
138
|
+
descriptionZh: "返回给 LLM 的文本最大字符数(超出截断)。环境变量 DEEPSEEK_MAX_RESULT_CHARS 可覆盖。",
|
|
139
|
+
descriptionEn: "Max result chars before truncation. Overridable via DEEPSEEK_MAX_RESULT_CHARS.",
|
|
140
|
+
})),
|
|
141
|
+
prices: Type.Optional(pricesSchema),
|
|
142
|
+
}, {
|
|
143
|
+
additionalProperties: false,
|
|
144
|
+
descriptionZh: "deepseek-web-search 扩展配置文件(.pi/deepseek-web-search.json)。校验规则与运行时一致;峰谷定价按官方文档(北京时间每日 9:00-12:00 / 14:00-18:00,即将执行)。",
|
|
145
|
+
descriptionEn: "Config file for the deepseek-web-search extension. Peak pricing per official docs (daily 09:00-12:00 / 14:00-18:00 Beijing time, pending).",
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/** 配置文件结构类型(由 configFileSchema 派生,单一来源) */
|
|
149
|
+
export type ConfigFileShape = Static<typeof configFileSchema>;
|
|
150
|
+
|
|
151
|
+
/** 生成某语言版本的 schema 文档(localize 双语注释键 → 标准 description,并补 $schema/title) */
|
|
152
|
+
export function buildSchemaDoc(lang: "zh" | "en"): Record<string, unknown> {
|
|
153
|
+
const doc = localize(JSON.parse(JSON.stringify(configFileSchema)), lang) as Record<string, unknown>;
|
|
154
|
+
doc.$schema = "http://json-schema.org/draft-07/schema#";
|
|
155
|
+
doc.title = `deepseek-web-search config (${lang})`;
|
|
156
|
+
doc.description = `Generated by pnpm gen:schema from src/config-schema.ts — do not edit. 由 src/config-schema.ts 自动生成,请勿手改。`;
|
|
157
|
+
return doc;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** 深度遍历:把 descriptionZh/descriptionEn 映射为标准 description(按语言),删除另一个语言键 */
|
|
161
|
+
function localize(node: unknown, lang: "zh" | "en"): unknown {
|
|
162
|
+
if (Array.isArray(node))
|
|
163
|
+
return node.map(item => localize(item, lang));
|
|
164
|
+
if (node == null || typeof node !== "object")
|
|
165
|
+
return node;
|
|
166
|
+
const out: Record<string, unknown> = {};
|
|
167
|
+
for (const [key, value] of Object.entries(node)) {
|
|
168
|
+
if (key === "descriptionZh" || key === "descriptionEn") {
|
|
169
|
+
if (key === (lang === "zh" ? "descriptionZh" : "descriptionEn"))
|
|
170
|
+
out.description = value;
|
|
171
|
+
} else {
|
|
172
|
+
out[key] = localize(value, lang);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|