@webskill/chatbot 0.16.0 → 0.17.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.
@@ -0,0 +1,642 @@
1
+ import { a as QUICK_PROMPT_ICON_NAMES, n as defaultRuntimeConfig, t as RUNTIME_RENDERER_IDS } from "./types-C4dzGOPp.js";
2
+
3
+ //#region ../ui-kit/src/runtime-config/enums.ts
4
+ const RUNTIME_LLM_PROVIDERS = [
5
+ "openai-compatible",
6
+ "anthropic",
7
+ "google",
8
+ "chrome-builtin"
9
+ ];
10
+ const RUNTIME_SANDBOX_EXECUTORS = [
11
+ "auto",
12
+ "blob-worker",
13
+ "iframe-sandbox"
14
+ ];
15
+ /**
16
+ * `NetworkPolicy` 里的**标量**取值。
17
+ * 该类型还有 `{ allow: string[] }` 一种对象形态,不属于枚举,由解析器单独处理。
18
+ */
19
+ const RUNTIME_NETWORK_POLICY_KINDS = ["deny-all", "allow-all"];
20
+ const RUNTIME_ROUTER_STRATEGIES = ["progressive", "full-disclosure"];
21
+ const RUNTIME_MISSING_PARAMS_MODES = ["user", "llm"];
22
+ const RUNTIME_CONFIRMATION_MODES = ["required", "auto-approve"];
23
+ const RUNTIME_APPROVAL_SCOPES = ["once-per-run", "every-call"];
24
+ const RUNTIME_UNSIGNED_SKILL_POLICIES = [
25
+ "allow",
26
+ "warn",
27
+ "deny"
28
+ ];
29
+ const RUNTIME_THEMES = ["light", "dark"];
30
+ const RUNTIME_LOCALES = ["zh", "en"];
31
+ /**
32
+ * `CapabilityMode` 里的**字符串**取值。
33
+ * 该类型是三态(`false` / 需批准 / `true`),两个布尔端不属于枚举,由解析器单独处理。
34
+ */
35
+ const RUNTIME_CAPABILITY_MODES = ["require-approval"];
36
+
37
+ //#endregion
38
+ //#region ../ui-kit/src/runtime-config/bounds.ts
39
+ const RUNTIME_CONFIG_BOUNDS = {
40
+ loop: {
41
+ maxTurns: {
42
+ min: 1,
43
+ max: 9999
44
+ },
45
+ maxHistoryMessages: {
46
+ min: 1,
47
+ max: 1e3
48
+ },
49
+ totalTimeoutMs: { min: 1e3 },
50
+ toolTimeoutMs: { min: 1e3 },
51
+ toolResultMaxBytes: { min: 1024 },
52
+ maxDocumentBytes: { min: 1024 },
53
+ maxDocumentTextBytes: { min: 1024 },
54
+ temperature: {
55
+ min: 0,
56
+ max: 2
57
+ }
58
+ },
59
+ interaction: { interactionTimeoutMs: { min: 1e3 } },
60
+ hooks: { timeoutMs: { min: 100 } },
61
+ skillState: { quarantineThreshold: {
62
+ min: 1,
63
+ max: 20
64
+ } },
65
+ multimodal: {
66
+ maxImagesPerMessage: {
67
+ min: 1,
68
+ max: 100
69
+ },
70
+ maxImageBytes: { min: 65536 },
71
+ minImageArea: { min: 0 }
72
+ },
73
+ sandbox: {
74
+ maxDataSourceBytes: { min: 1024 },
75
+ maxUploadFileBytes: { min: 1024 }
76
+ },
77
+ userProfile: {
78
+ injectMaxBytes: { min: 1024 },
79
+ recordLimit: { min: 1 }
80
+ },
81
+ /** 单个模型条目的区间;`llm.entries` 是数组,够不到上面的按分区结构 */
82
+ llmEntry: { requestTimeoutMs: { min: 1e3 } },
83
+ quickPromptLimit: {
84
+ min: 1,
85
+ max: 20
86
+ }
87
+ };
88
+
89
+ //#endregion
90
+ //#region ../ui-kit/src/runtime-config/providerDefaults.ts
91
+ /**
92
+ * provider 相关的默认值与切换规则(0.6.0 FR-13.1)。
93
+ * chatbot 设置面板与 console 的 `connections.models` 共用这一份——
94
+ * 0.5.x 时两处各自硬编码,改一处漏一处。
95
+ */
96
+ /**
97
+ * 各 provider 的官方默认端点。取值必须与客户端内部的 fallback 一致(T-03-2 断言):
98
+ * 客户端的 fallback 保证「用户没填也能跑」,这张表只服务 UI 预填,两者说的是同一件事。
99
+ * `openai-compatible` 与 `chrome-builtin` 无默认——前者端点任意,后者根本不走 HTTP。
100
+ * @experimental
101
+ */
102
+ const PROVIDER_DEFAULT_BASE_URL = {
103
+ "openai-compatible": void 0,
104
+ anthropic: "https://api.anthropic.com",
105
+ google: "https://generativelanguage.googleapis.com",
106
+ "chrome-builtin": void 0
107
+ };
108
+ /**
109
+ * `chrome-builtin` 由浏览器决定用哪个内置模型,调用方填不了模型名。
110
+ * 其余 provider 必须显式指定。 @experimental
111
+ */
112
+ function providerUsesModelName(provider) {
113
+ return provider !== "chrome-builtin";
114
+ }
115
+ const KNOWN_DEFAULT_BASE_URLS = new Set(Object.values(PROVIDER_DEFAULT_BASE_URL).filter((url) => url !== void 0));
116
+
117
+ //#endregion
118
+ //#region src/config/errors.ts
119
+ /**
120
+ * 配置文件校验失败。
121
+ *
122
+ * 校验一律**硬失败**:拼错一个字段名而解析器静默忽略,得到的是「看起来生效了其实没生效」
123
+ * 的构建产物,症状出现在运行时且完全无从反推。
124
+ * @experimental
125
+ */
126
+ var WebSkillConfigError = class extends Error {
127
+ code;
128
+ /** 出问题的位置在配置文件里的路径,如 `agentRuntime.loop.maxTurns` */
129
+ path;
130
+ constructor(code, path, message) {
131
+ super(message);
132
+ this.name = "WebSkillConfigError";
133
+ this.code = code;
134
+ this.path = path;
135
+ }
136
+ };
137
+
138
+ //#endregion
139
+ //#region src/config/schema.ts
140
+ /**
141
+ * 分节名与 Console「设置」各页的分组一一对应,字段名与 `RuntimeConfig` 同形,
142
+ * 方便对着界面逐项核对。
143
+ * @experimental
144
+ */
145
+ const CONFIG_FIELDS = {
146
+ models: {
147
+ target: ["llm", "entries"],
148
+ kind: "custom"
149
+ },
150
+ defaultModel: {
151
+ target: ["llm", "defaultId"],
152
+ kind: "string"
153
+ },
154
+ "agentRuntime.streaming": { target: ["streaming"] },
155
+ "agentRuntime.renderResult": { target: ["renderResult"] },
156
+ "agentRuntime.router": {
157
+ target: ["router", "strategy"],
158
+ values: RUNTIME_ROUTER_STRATEGIES
159
+ },
160
+ "agentRuntime.skillState.quarantineThreshold": { target: ["skillState", "quarantineThreshold"] },
161
+ "agentRuntime.loop.maxTurns": { target: ["loop", "maxTurns"] },
162
+ "agentRuntime.loop.maxHistoryMessages": { target: ["loop", "maxHistoryMessages"] },
163
+ "agentRuntime.loop.totalTimeoutMs": { target: ["loop", "totalTimeoutMs"] },
164
+ "agentRuntime.loop.toolTimeoutMs": { target: ["loop", "toolTimeoutMs"] },
165
+ "agentRuntime.loop.toolResultMaxBytes": { target: ["loop", "toolResultMaxBytes"] },
166
+ "agentRuntime.loop.maxDocumentBytes": { target: ["loop", "maxDocumentBytes"] },
167
+ "agentRuntime.loop.maxDocumentTextBytes": { target: ["loop", "maxDocumentTextBytes"] },
168
+ "agentRuntime.loop.temperature": {
169
+ target: ["loop", "temperature"],
170
+ kind: "number"
171
+ },
172
+ "agentRuntime.interaction.missingParams": {
173
+ target: ["interaction", "missingParams"],
174
+ values: RUNTIME_MISSING_PARAMS_MODES
175
+ },
176
+ "agentRuntime.interaction.confirmations": {
177
+ target: ["interaction", "confirmations"],
178
+ values: RUNTIME_CONFIRMATION_MODES
179
+ },
180
+ "agentRuntime.interaction.approvalScope": {
181
+ target: ["interaction", "approvalScope"],
182
+ values: RUNTIME_APPROVAL_SCOPES
183
+ },
184
+ "agentRuntime.interaction.interactionTimeoutMs": { target: ["interaction", "interactionTimeoutMs"] },
185
+ "agentRuntime.hooks.timeoutMs": { target: ["hooks", "timeoutMs"] },
186
+ "agentRuntime.hooks.failOnHookError": { target: ["hooks", "failOnHookError"] },
187
+ "agentRuntime.agentCapabilities.todo": { target: ["agentCapabilities", "todo"] },
188
+ "agentRuntime.agentCapabilities.generativeUi": { target: ["agentCapabilities", "generativeUi"] },
189
+ "agentRuntime.agentCapabilities.skillGeneration": { target: ["agentCapabilities", "skillGeneration"] },
190
+ "agentRuntime.agentCapabilities.delegation": { target: ["agentCapabilities", "delegation"] },
191
+ "agentRuntime.agentCapabilities.uiPresets": { target: ["agentCapabilities", "uiPresets"] },
192
+ "agentRuntime.multimodal.imageAttachments": { target: ["multimodal", "imageAttachments"] },
193
+ "agentRuntime.multimodal.pageImageCapture": { target: ["multimodal", "pageImageCapture"] },
194
+ "agentRuntime.multimodal.maxImagesPerMessage": { target: ["multimodal", "maxImagesPerMessage"] },
195
+ "agentRuntime.multimodal.maxImageBytes": { target: ["multimodal", "maxImageBytes"] },
196
+ "agentRuntime.multimodal.minImageArea": { target: ["multimodal", "minImageArea"] },
197
+ "sandbox.executor": {
198
+ target: ["sandbox", "executor"],
199
+ values: RUNTIME_SANDBOX_EXECUTORS
200
+ },
201
+ "sandbox.networkPolicy": {
202
+ target: ["sandbox", "networkPolicy"],
203
+ kind: "custom",
204
+ values: RUNTIME_NETWORK_POLICY_KINDS
205
+ },
206
+ "sandbox.typescript": {
207
+ target: ["sandbox", "typescript"],
208
+ kind: "custom",
209
+ covers: [
210
+ [
211
+ "sandbox",
212
+ "typescript",
213
+ "enabled"
214
+ ],
215
+ [
216
+ "sandbox",
217
+ "typescript",
218
+ "esbuildUrl"
219
+ ],
220
+ [
221
+ "sandbox",
222
+ "typescript",
223
+ "wasmUrl"
224
+ ]
225
+ ]
226
+ },
227
+ "sandbox.downloadedFiles": { target: ["sandbox", "downloadedFiles"] },
228
+ "sandbox.uploadFiles": { target: ["sandbox", "uploadFiles"] },
229
+ "sandbox.allowHttp": { target: [
230
+ "sandbox",
231
+ "remoteUrl",
232
+ "allowHttp"
233
+ ] },
234
+ "sandbox.allowPrivateHosts": { target: [
235
+ "sandbox",
236
+ "remoteUrl",
237
+ "allowPrivateHosts"
238
+ ] },
239
+ "sandbox.capabilities.readReference": {
240
+ target: [
241
+ "sandbox",
242
+ "capabilities",
243
+ "readReference"
244
+ ],
245
+ kind: "capability"
246
+ },
247
+ "sandbox.capabilities.readAsset": {
248
+ target: [
249
+ "sandbox",
250
+ "capabilities",
251
+ "readAsset"
252
+ ],
253
+ kind: "capability"
254
+ },
255
+ "sandbox.capabilities.writeArtifact": {
256
+ target: [
257
+ "sandbox",
258
+ "capabilities",
259
+ "writeArtifact"
260
+ ],
261
+ kind: "capability"
262
+ },
263
+ "sandbox.capabilities.confirm": {
264
+ target: [
265
+ "sandbox",
266
+ "capabilities",
267
+ "confirm"
268
+ ],
269
+ kind: "capability"
270
+ },
271
+ "sandbox.capabilities.fetchData": {
272
+ target: [
273
+ "sandbox",
274
+ "capabilities",
275
+ "fetchData"
276
+ ],
277
+ kind: "capability"
278
+ },
279
+ "sandbox.dataSources": {
280
+ target: ["sandbox", "dataSources"],
281
+ kind: "custom"
282
+ },
283
+ "sandbox.maxDataSourceBytes": { target: ["sandbox", "maxDataSourceBytes"] },
284
+ "sandbox.maxUploadFileBytes": { target: ["sandbox", "maxUploadFileBytes"] },
285
+ "sandbox.unsignedSkills": {
286
+ target: ["security", "unsignedSkills"],
287
+ values: RUNTIME_UNSIGNED_SKILL_POLICIES
288
+ },
289
+ "sandbox.documentSurface": { target: ["documentSurface", "enabled"] },
290
+ "quickPrompts.limit": { target: ["quickPromptLimit"] },
291
+ "quickPrompts.items": {
292
+ target: ["quickPrompts"],
293
+ kind: "custom"
294
+ },
295
+ "privacy.userProfile": { target: ["userProfile", "enabled"] },
296
+ "privacy.encryptProfile": { target: ["userProfile", "encrypted"] },
297
+ "privacy.injectMaxBytes": { target: ["userProfile", "injectMaxBytes"] },
298
+ "privacy.recordLimit": { target: ["userProfile", "recordLimit"] },
299
+ "appearance.theme": {
300
+ target: ["appearance", "theme"],
301
+ values: RUNTIME_THEMES
302
+ },
303
+ "appearance.locale": {
304
+ target: ["appearance", "locale"],
305
+ values: RUNTIME_LOCALES
306
+ },
307
+ "appearance.renderer": {
308
+ target: ["appearance", "renderer"],
309
+ values: RUNTIME_RENDERER_IDS
310
+ },
311
+ "appearance.dictationLang": {
312
+ target: ["appearance", "dictationLang"],
313
+ allowEmpty: true
314
+ }
315
+ };
316
+ /** 快捷指令图标的合法取值;`quickPrompts.items` 的专用解析器要用 */
317
+ const QUICK_PROMPT_ICONS = QUICK_PROMPT_ICON_NAMES;
318
+ /** 模型条目的 provider 取值;`models` 的专用解析器要用 */
319
+ const LLM_PROVIDERS = RUNTIME_LLM_PROVIDERS;
320
+ /**
321
+ * `RuntimeConfig` 里**不可配置**的字段(DV-5)。
322
+ *
323
+ * 它们不是出厂设置:`quickPromptsSeeded` 是写了 `quickPrompts.items` 之后的派生值,
324
+ * `dismissedAutoEntries` 是用户点掉过哪几条模型的运行时状态。
325
+ * 完整性测试要读这张表来算排除项——把排除项内联进测试就是造下一个手抄副本。
326
+ * @experimental
327
+ */
328
+ const NON_CONFIGURABLE_RUNTIME_PATHS = ["quickPromptsSeeded", "dismissedAutoEntries"];
329
+ /** 配置文件覆盖到的全部 `RuntimeConfig` 路径(点号形式),供完整性测试使用 @experimental */
330
+ const CONFIGURED_RUNTIME_PATHS = Object.values(CONFIG_FIELDS).flatMap((spec) => (spec.covers ?? [spec.target]).map((path) => path.join("."))).sort();
331
+ function valueAt(root, path) {
332
+ let current = root;
333
+ for (const segment of path) {
334
+ if (typeof current !== "object" || current === null) return void 0;
335
+ current = current[segment];
336
+ }
337
+ return current;
338
+ }
339
+ /**
340
+ * 叶子类型:显式声明优先,否则看 `defaultRuntimeConfig()` 里那个位置放的是什么。
341
+ * 推不出来(默认值缺席)时抛错——这是开发期错误,表里补一个 `kind` 即可。
342
+ */
343
+ function leafKindOf(spec, path) {
344
+ if (spec.kind !== void 0) return spec.kind;
345
+ if (spec.values !== void 0) return "string";
346
+ const fallback = valueAt(defaultRuntimeConfig(), spec.target);
347
+ if (typeof fallback === "boolean") return "boolean";
348
+ if (typeof fallback === "number") return "integer";
349
+ if (typeof fallback === "string") return "string";
350
+ if (Array.isArray(fallback)) return "string[]";
351
+ throw new Error(`config field "${path}" has no default to derive its type from; declare "kind" explicitly`);
352
+ }
353
+ /** 取值区间:与 Console 的 `NumberSetting` 同一张表,避免烘焙出界面调不回来的值 */
354
+ function boundOf(spec) {
355
+ const [head, tail] = spec.target;
356
+ if (head === "quickPromptLimit") return RUNTIME_CONFIG_BOUNDS.quickPromptLimit;
357
+ if (tail === void 0 || spec.target.length !== 2) return void 0;
358
+ return RUNTIME_CONFIG_BOUNDS[head]?.[tail];
359
+ }
360
+ /** 单个模型条目的区间(`llm.entries` 是数组,够不到按分区组织的主表) */
361
+ const LLM_ENTRY_BOUNDS = RUNTIME_CONFIG_BOUNDS.llmEntry;
362
+ const FIELD_PATHS = Object.keys(CONFIG_FIELDS);
363
+ /**
364
+ * 某一层的合法子键。由字段表的前缀关系算出来,不另存一份 allowlist——
365
+ * 0.20.0 之前扩展侧那 20 多个手写 allowlist 正是这么漂移的。
366
+ */
367
+ function legalKeysAt(prefix) {
368
+ const head = prefix === "" ? "" : `${prefix}.`;
369
+ const keys = /* @__PURE__ */ new Set();
370
+ for (const path of FIELD_PATHS) {
371
+ if (!path.startsWith(head)) continue;
372
+ const rest = path.slice(head.length);
373
+ if (rest === "") continue;
374
+ keys.add(rest.split(".")[0]);
375
+ }
376
+ return [...keys];
377
+ }
378
+ function specAt(path) {
379
+ return CONFIG_FIELDS[path];
380
+ }
381
+
382
+ //#endregion
383
+ //#region src/config/parse.ts
384
+ function fail(code, path, message) {
385
+ throw new WebSkillConfigError(code, path, message);
386
+ }
387
+ function isPlainObject(value) {
388
+ return typeof value === "object" && value !== null && !Array.isArray(value);
389
+ }
390
+ function expectObject(value, path) {
391
+ if (!isPlainObject(value)) fail("CONFIG_SHAPE", path, `${path} must be an object`);
392
+ return value;
393
+ }
394
+ /** 未知键一律报错,并把合法键列出来——拼错字段是这套配置最容易犯也最难发现的错 */
395
+ function rejectUnknownKeys(value, allowed, path) {
396
+ for (const key of Object.keys(value)) {
397
+ if (allowed.includes(key)) continue;
398
+ const at = path === "" ? key : `${path}.${key}`;
399
+ fail("CONFIG_UNKNOWN_KEY", at, `${at} is not a recognized option (allowed: ${allowed.join(", ")})`);
400
+ }
401
+ }
402
+ function readBool(value, path) {
403
+ if (typeof value !== "boolean") fail("CONFIG_TYPE", path, `${path} must be a boolean`);
404
+ return value;
405
+ }
406
+ function readString(value, path, allowEmpty = false) {
407
+ if (typeof value !== "string") fail("CONFIG_TYPE", path, `${path} must be a string`);
408
+ if (!allowEmpty && value.trim() === "") fail("CONFIG_TYPE", path, `${path} must not be empty`);
409
+ return value;
410
+ }
411
+ function checkRange(value, path, bound) {
412
+ const min = bound?.min ?? 0;
413
+ const max = bound?.max ?? Number.MAX_SAFE_INTEGER;
414
+ if (value < min || value > max) fail("CONFIG_RANGE", path, `${path} must be between ${min} and ${max}`);
415
+ return value;
416
+ }
417
+ function readInt(value, path, bound) {
418
+ if (typeof value !== "number" || !Number.isInteger(value)) fail("CONFIG_TYPE", path, `${path} must be an integer`);
419
+ return checkRange(value, path, bound);
420
+ }
421
+ function readNumber(value, path, bound) {
422
+ if (typeof value !== "number" || !Number.isFinite(value)) fail("CONFIG_TYPE", path, `${path} must be a number`);
423
+ return checkRange(value, path, bound);
424
+ }
425
+ function readEnum(value, path, allowed) {
426
+ if (typeof value !== "string" || !allowed.includes(value)) fail("CONFIG_ENUM", path, `${path} must be one of: ${allowed.join(", ")}`);
427
+ return value;
428
+ }
429
+ /** `CapabilityMode` 是三态:布尔,或 {@link RUNTIME_CAPABILITY_MODES} 里的字符串档位 */
430
+ function readCapability(value, path) {
431
+ if (typeof value === "boolean") return value;
432
+ if (typeof value === "string" && RUNTIME_CAPABILITY_MODES.includes(value)) return value;
433
+ return fail("CONFIG_TYPE", path, `${path} must be a boolean or ${RUNTIME_CAPABILITY_MODES.map((mode) => `"${mode}"`).join(" or ")}`);
434
+ }
435
+ function readStringArray(value, path) {
436
+ if (!Array.isArray(value)) fail("CONFIG_SHAPE", path, `${path} must be an array`);
437
+ return value.map((item, index) => readString(item, `${path}[${index}]`));
438
+ }
439
+ function setAt(root, target, value) {
440
+ let node = root;
441
+ for (const segment of target.slice(0, -1)) {
442
+ const next = node[segment];
443
+ if (!isPlainObject(next)) node[segment] = {};
444
+ node = node[segment];
445
+ }
446
+ node[target[target.length - 1]] = value;
447
+ }
448
+ /**
449
+ * 模型条目与 SDK 的 `RuntimeLlmEntry` **同形**:不做字段改名,
450
+ * 少一层映射就少一处长期漂移的来源。
451
+ * `apiKey` 是唯一的例外——它被摘出去单独处理,不进解析结果。
452
+ */
453
+ function parseModels(raw, secrets, root) {
454
+ if (!Array.isArray(raw)) fail("CONFIG_SHAPE", root, `${root} must be an array`);
455
+ const entries = [];
456
+ const seen = /* @__PURE__ */ new Set();
457
+ raw.forEach((item, index) => {
458
+ const path = `${root}[${index}]`;
459
+ const source = expectObject(item, path);
460
+ rejectUnknownKeys(source, [
461
+ "id",
462
+ "label",
463
+ "provider",
464
+ "baseUrl",
465
+ "apiKey",
466
+ "model",
467
+ "requestTimeoutMs",
468
+ "capabilities",
469
+ "thinkingBudgetTokens"
470
+ ], path);
471
+ const id = readString(source["id"], `${path}.id`);
472
+ if (seen.has(id)) fail("CONFIG_REFERENCE", `${path}.id`, `${path}.id duplicates an earlier entry: ${id}`);
473
+ seen.add(id);
474
+ const provider = readEnum(source["provider"], `${path}.provider`, LLM_PROVIDERS);
475
+ const entry = {
476
+ id,
477
+ label: readString(source["label"], `${path}.label`),
478
+ provider,
479
+ model: providerUsesModelName(provider) ? readString(source["model"], `${path}.model`) : source["model"] ?? ""
480
+ };
481
+ if (source["baseUrl"] !== void 0) entry.baseUrl = readString(source["baseUrl"], `${path}.baseUrl`);
482
+ if (source["requestTimeoutMs"] !== void 0) entry.requestTimeoutMs = readInt(source["requestTimeoutMs"], `${path}.requestTimeoutMs`, LLM_ENTRY_BOUNDS.requestTimeoutMs);
483
+ if (source["thinkingBudgetTokens"] !== void 0) entry.thinkingBudgetTokens = readInt(source["thinkingBudgetTokens"], `${path}.thinkingBudgetTokens`);
484
+ if (source["capabilities"] !== void 0) {
485
+ const caps = expectObject(source["capabilities"], `${path}.capabilities`);
486
+ rejectUnknownKeys(caps, ["tools", "image"], `${path}.capabilities`);
487
+ entry.capabilities = {
488
+ tools: caps["tools"] === void 0 ? true : readBool(caps["tools"], `${path}.capabilities.tools`),
489
+ image: caps["image"] === void 0 ? false : readBool(caps["image"], `${path}.capabilities.image`)
490
+ };
491
+ }
492
+ if (source["apiKey"] !== void 0) {
493
+ const key = readString(source["apiKey"], `${path}.apiKey`, true);
494
+ if (key !== "") secrets[id] = key;
495
+ }
496
+ entries.push(entry);
497
+ });
498
+ return entries;
499
+ }
500
+ function parseQuickPromptItems(raw, path) {
501
+ if (!Array.isArray(raw)) fail("CONFIG_SHAPE", path, `${path} must be an array`);
502
+ const seen = /* @__PURE__ */ new Set();
503
+ return raw.map((item, index) => {
504
+ const at = `${path}[${index}]`;
505
+ const entry = expectObject(item, at);
506
+ rejectUnknownKeys(entry, [
507
+ "id",
508
+ "text",
509
+ "icon"
510
+ ], at);
511
+ const id = readString(entry["id"], `${at}.id`);
512
+ if (seen.has(id)) fail("CONFIG_REFERENCE", `${at}.id`, `${at}.id duplicates an earlier entry: ${id}`);
513
+ seen.add(id);
514
+ const text = expectObject(entry["text"], `${at}.text`);
515
+ rejectUnknownKeys(text, RUNTIME_LOCALES, `${at}.text`);
516
+ const parsed = {
517
+ id,
518
+ text: Object.fromEntries(RUNTIME_LOCALES.map((locale) => [locale, readString(text[locale], `${at}.text.${locale}`)]))
519
+ };
520
+ if (entry["icon"] !== void 0) parsed.icon = readEnum(entry["icon"], `${at}.icon`, QUICK_PROMPT_ICONS);
521
+ return parsed;
522
+ });
523
+ }
524
+ function parseDataSources(raw, path) {
525
+ if (!Array.isArray(raw)) fail("CONFIG_SHAPE", path, `${path} must be an array`);
526
+ return raw.map((item, index) => {
527
+ const at = `${path}[${index}]`;
528
+ const entry = expectObject(item, at);
529
+ rejectUnknownKeys(entry, [
530
+ "id",
531
+ "url",
532
+ "description"
533
+ ], at);
534
+ return {
535
+ id: readString(entry["id"], `${at}.id`),
536
+ url: readString(entry["url"], `${at}.url`),
537
+ description: readString(entry["description"], `${at}.description`, true)
538
+ };
539
+ });
540
+ }
541
+ /** 白名单形态:`{ "allow": ["https://example.com"] }` */
542
+ function parseNetworkPolicy(raw, allowed, path) {
543
+ if (isPlainObject(raw)) {
544
+ rejectUnknownKeys(raw, ["allow"], path);
545
+ return { allow: readStringArray(raw["allow"], `${path}.allow`) };
546
+ }
547
+ return readEnum(raw, path, allowed);
548
+ }
549
+ /** 布尔简写等价于 `{ enabled: <bool> }`——只想开关时不必写整个对象 */
550
+ function parseTypeScript(raw, path) {
551
+ if (!isPlainObject(raw)) return { enabled: readBool(raw, path) };
552
+ rejectUnknownKeys(raw, [
553
+ "enabled",
554
+ "esbuildUrl",
555
+ "wasmUrl"
556
+ ], path);
557
+ const parsed = { enabled: readBool(raw["enabled"], `${path}.enabled`) };
558
+ if (raw["esbuildUrl"] !== void 0) parsed.esbuildUrl = readString(raw["esbuildUrl"], `${path}.esbuildUrl`);
559
+ if (raw["wasmUrl"] !== void 0) parsed.wasmUrl = readString(raw["wasmUrl"], `${path}.wasmUrl`);
560
+ return parsed;
561
+ }
562
+ /**
563
+ * `path` 是字段表用的无前缀路径(决定走哪个解析器),`at` 是报给人看的带前缀路径。
564
+ * 两者分开传,免得为了错误信息好看而把 switch 的匹配串一起改掉。
565
+ */
566
+ function readLeaf(value, path, at, spec, secrets) {
567
+ if (spec.values !== void 0 && spec.kind === void 0) return readEnum(value, at, spec.values);
568
+ switch (leafKindOf(spec, path)) {
569
+ case "boolean": return readBool(value, at);
570
+ case "integer": return readInt(value, at, boundOf(spec));
571
+ case "number": return readNumber(value, at, boundOf(spec));
572
+ case "string": return readString(value, at, spec.allowEmpty === true);
573
+ case "string[]": return readStringArray(value, at);
574
+ case "capability": return readCapability(value, at);
575
+ case "custom": return parseCustom(value, path, at, spec, secrets);
576
+ }
577
+ }
578
+ function parseCustom(value, path, at, spec, secrets) {
579
+ switch (path) {
580
+ case "models": return parseModels(value, secrets, at);
581
+ case "sandbox.networkPolicy": return parseNetworkPolicy(value, spec.values ?? [], at);
582
+ case "sandbox.typescript": return parseTypeScript(value, at);
583
+ case "sandbox.dataSources": return parseDataSources(value, at);
584
+ case "quickPrompts.items": return parseQuickPromptItems(value, at);
585
+ default: throw new Error(`config field "${path}" is declared custom but has no parser`);
586
+ }
587
+ }
588
+ /**
589
+ * 报错路径。根一层叫 `config`(未知键会拼成 `config.<key>`),其余层直接用点号路径——
590
+ * 与扩展现行解析器**逐字一致**,AC-10.2 断言的就是这个串。
591
+ */
592
+ const label = (path) => path === "" ? "config" : path;
593
+ /**
594
+ * 逐层下钻。合法子键由字段表的前缀关系算出来,因此不存在
595
+ * 「加了字段忘了往某个 allowlist 里补一行」这种漏。
596
+ */
597
+ function walk(raw, prefix, out, secrets, extraKeys) {
598
+ const source = expectObject(raw, label(prefix));
599
+ rejectUnknownKeys(source, [...extraKeys, ...legalKeysAt(prefix)], label(prefix));
600
+ for (const [key, value] of Object.entries(source)) {
601
+ if (value === void 0 || extraKeys.includes(key)) continue;
602
+ const path = prefix === "" ? key : `${prefix}.${key}`;
603
+ const spec = specAt(path);
604
+ if (spec === void 0) {
605
+ walk(value, path, out, secrets, []);
606
+ continue;
607
+ }
608
+ setAt(out, spec.target, readLeaf(value, path, label(path), spec, secrets));
609
+ }
610
+ }
611
+ /**
612
+ * 把一份已解析的配置 JSON 校验并映射成 `RuntimeConfig` 缺省值。
613
+ *
614
+ * 任何不合法的字段都抛 `WebSkillConfigError`(带稳定错误码与完整路径),
615
+ * 不做任何静默忽略。`apiKey` 被摘到 `secrets` 里,不进 `defaults`。
616
+ * @experimental
617
+ */
618
+ function parseWebSkillConfig(raw, options = {}) {
619
+ const hostSections = options.hostSections ?? [];
620
+ const source = expectObject(raw, "config");
621
+ const defaults = {};
622
+ const secrets = {};
623
+ walk(source, "", defaults, secrets, hostSections);
624
+ const host = {};
625
+ for (const section of hostSections) if (source[section] !== void 0) host[section] = source[section];
626
+ const llm = defaults["llm"];
627
+ if (llm?.defaultId !== void 0) {
628
+ if (llm.entries === void 0) fail("CONFIG_REFERENCE", "defaultModel", "defaultModel requires models to be declared");
629
+ if (!llm.entries.some((entry) => entry.id === llm.defaultId)) fail("CONFIG_REFERENCE", "defaultModel", `defaultModel must reference an id declared in models: ${llm.defaultId}`);
630
+ }
631
+ if (defaults["quickPrompts"] !== void 0) defaults["quickPromptsSeeded"] = true;
632
+ return {
633
+ defaults,
634
+ secrets,
635
+ host
636
+ };
637
+ }
638
+ /** 配置文件的顶层分节名,供宿主生成文档或校验示例文件 @experimental */
639
+ const CONFIG_TOP_LEVEL_SECTIONS = legalKeysAt("");
640
+
641
+ //#endregion
642
+ export { NON_CONFIGURABLE_RUNTIME_PATHS as a, CONFIG_FIELDS as i, parseWebSkillConfig as n, WebSkillConfigError as o, CONFIGURED_RUNTIME_PATHS as r, CONFIG_TOP_LEVEL_SECTIONS as t };