@webskill/sdk 0.13.0 → 0.15.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/dist/agent.d.ts +3 -3
- package/dist/agent.js +2442 -3
- package/dist/{dist-ExSQky4C.js → approval-BnLLCOG0.js} +277 -2934
- package/dist/browser.d.ts +327 -8
- package/dist/browser.js +1017 -51
- package/dist/{openUiLibrary-BKXW7Iwx-CWWBVOkE.js → dist-CTsxblSD.js} +496 -28
- package/dist/{echarts-De78wXqV.js → echarts-BE7oV_Dl.js} +1 -1
- package/dist/{env-8cY40DXB-CGnEVZby.js → env-Bj1MI2Ww.js} +1 -1
- package/dist/errors-BDZNpC13.js +22 -0
- package/dist/{eventTypes-FllCrX-Z-DNDeHWoG.js → eventTypes-Y07N8IAC.js} +14 -2
- package/dist/external-_ZRQe-V9.js +53 -0
- package/dist/governance.d.ts +15 -3
- package/dist/governance.js +86 -4
- package/dist/{index-iBm9tJL_.d.ts → index-CQYo4tcT.d.ts} +469 -10
- package/dist/{index-C3XdItd_.d.ts → index-CU2md8R1.d.ts} +26 -18
- package/dist/{index-DkvBhJQy.d.ts → index-Oc3H89TJ.d.ts} +204 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1001 -5
- package/dist/kind-DxgS8LM-.js +77 -0
- package/dist/linkedDocument-xNXF-z8n.js +94 -0
- package/dist/llm-B7lLH0ZI.js +1767 -0
- package/dist/mcp.d.ts +56 -3
- package/dist/mcp.js +75 -5
- package/dist/memoryArtifactStore-D6dLeY55.js +48 -0
- package/dist/miniChart-owdCdaw-.js +533 -0
- package/dist/node.d.ts +3 -3
- package/dist/node.js +110 -13
- package/dist/openUiLibrary-CY_ADoe0.js +30 -0
- package/dist/openUiSpecLang-DuOCdw9G.js +1849 -0
- package/dist/pathSecurity-B1owvJAF.js +65 -0
- package/dist/processSandboxEntry.js +5 -1
- package/dist/renderResult-D9Q-Vu2x.js +143 -0
- package/dist/{rolldown-runtime-BOF7iYI8.js → rolldown-runtime-BHkdefai.js} +1 -2
- package/dist/sandboxWorkerEntry.js +5 -1
- package/dist/{dist-Bev6i6Ip.js → skill-CAJMsLod.js} +44 -314
- package/dist/{skillVersionStore-D-qHk9ZE-DheTIwAB.d.ts → skillVersionStore-B24Jjjry-C4zomhMZ.d.ts} +14 -2
- package/dist/surface-DVGiCmwq.js +145 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +64 -3
- package/dist/{types-DLctJep_-B5G4uk2u.d.ts → types-C3V6lAeO-BIf7UB2e.d.ts} +79 -4
- package/dist/types-TUydnxaj.js +78 -0
- package/dist/ui-react.d.ts +12 -5
- package/dist/ui-react.js +128548 -4729
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +11 -2
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +1254 -3
- package/dist/urlSafety-CiSuCJvX.js +65 -0
- package/dist/webSkillApi-DOOyz2G5.js +106 -0
- package/dist/{webskillLitCatalog-D_zCqeQF-C9lrvvMr.js → webskillLitCatalog-DSqVFQjK.js} +11 -3
- package/package.json +1 -1
- package/dist/catalogComponents-BgAJN0p8-CYEXSk45.js +0 -124384
- package/dist/client-BCM6Z3yq-qUQNkKrK.js +0 -7787
- package/dist/dist-DqcL6jKO.js +0 -3509
- package/dist/dist-DxyxmXPU-DGC-dpXf.js +0 -5621
- package/dist/dist-GK6dtjRv.js +0 -1493
- package/dist/memoryArtifactStore-52Zn9npI-LbCQaqyx.js +0 -86
- package/dist/stdio-CFMoANJJ-BxrTeXh7.js +0 -31
- package/dist/testing-WPTyXQYt.js +0 -142
- package/dist/types-WovEf4ED-CZSDiiBU.js +0 -6215
|
@@ -1,31 +1,15 @@
|
|
|
1
|
+
import { n as messageOf, t as WebSkillError } from "./errors-BDZNpC13.js";
|
|
2
|
+
import { a as atomicWriteText, o as isAtomicTempPath, r as resolveInsideRoot } from "./pathSecurity-B1owvJAF.js";
|
|
1
3
|
import { parse } from "yaml";
|
|
2
4
|
import { Unzip, UnzipInflate, zipSync } from "fflate";
|
|
3
5
|
|
|
4
|
-
//#region ../core/
|
|
5
|
-
/**
|
|
6
|
-
* 所有公开 API 抛出的结构化错误,code 供上层可编程处理
|
|
7
|
-
* @stable
|
|
8
|
-
*/
|
|
9
|
-
var WebSkillError = class extends Error {
|
|
10
|
-
code;
|
|
11
|
-
details;
|
|
12
|
-
constructor(code, message, details) {
|
|
13
|
-
super(message);
|
|
14
|
-
this.name = "WebSkillError";
|
|
15
|
-
this.code = code;
|
|
16
|
-
this.details = details;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
/** unknown 异常取消息文本(全仓单一来源,勿再本地重复定义) */
|
|
20
|
-
function messageOf(e) {
|
|
21
|
-
return e instanceof Error ? e.message : String(e);
|
|
22
|
-
}
|
|
6
|
+
//#region ../core/src/skill/manifest.ts
|
|
23
7
|
/** 技能目录内的安装清单文件名(不参与 files 列表与 digest) */
|
|
24
8
|
const SKILL_MANIFEST_FILE = "webskill.skill-manifest.json";
|
|
25
9
|
/** managed root 下的锁定文件名(不参与 files 列表与 digest) */
|
|
26
10
|
const SKILLS_LOCKFILE = "skills.lock.json";
|
|
27
11
|
/** 技能目录内的签名文件名(不参与 files 列表与 digest) */
|
|
28
|
-
const SKILL_SIGNATURE_FILE = "webskill.skill-signature.json";
|
|
12
|
+
const SKILL_SIGNATURE_FILE$1 = "webskill.skill-signature.json";
|
|
29
13
|
/**
|
|
30
14
|
* 不参与 digest 的技能目录内文件。
|
|
31
15
|
*
|
|
@@ -36,7 +20,7 @@ const SKILL_SIGNATURE_FILE = "webskill.skill-signature.json";
|
|
|
36
20
|
const MANIFEST_EXCLUDED_FILES = /* @__PURE__ */ new Set([
|
|
37
21
|
SKILL_MANIFEST_FILE,
|
|
38
22
|
SKILLS_LOCKFILE,
|
|
39
|
-
SKILL_SIGNATURE_FILE
|
|
23
|
+
SKILL_SIGNATURE_FILE$1
|
|
40
24
|
]);
|
|
41
25
|
/** 整体 digest:逐文件 "<path>:<sha256>\n" 拼接串(文件按 path 排序)的 sha256(hash 由调用方提供,可为 WebCrypto 异步实现) */
|
|
42
26
|
async function computeDigest(files, sha256) {
|
|
@@ -75,27 +59,11 @@ function verifyManifest(manifest, actualHashes, actualFiles = [...actualHashes.k
|
|
|
75
59
|
missing
|
|
76
60
|
};
|
|
77
61
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* @stable
|
|
82
|
-
*/
|
|
83
|
-
const ATOMIC_TMP_SUFFIX_PATTERN = /\.tmp-\d+-[a-z0-9]+$/;
|
|
84
|
-
/** 该路径是否是 atomicWriteText 留下的临时文件 */
|
|
85
|
-
const isAtomicTempPath = (path) => ATOMIC_TMP_SUFFIX_PATTERN.test(path);
|
|
86
|
-
/**
|
|
87
|
-
* 原子写文本:先写临时文件再 rename(进程崩溃不留下半写文件;lockfile 等账本场景)。
|
|
88
|
-
* 原子性强弱取决于 provider 的 rename 实现:NodeFS/MemoryFS 为真 rename;
|
|
89
|
-
* OPFS 无原生 move,rename 是 copy+delete(非原子,崩溃可能残留双份),
|
|
90
|
-
* 对崩溃一致性要求极高的场景在浏览器侧需知悉此前提。
|
|
91
|
-
*/
|
|
92
|
-
async function atomicWriteText(fs, path, content) {
|
|
93
|
-
const tmp = `${path}.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
94
|
-
await fs.writeText(tmp, content);
|
|
95
|
-
await fs.rename(tmp, path);
|
|
96
|
-
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region ../core/src/skill/signature.ts
|
|
97
65
|
/** 技能目录内的签名文件名(与 manifest 同级,不参与 files 列表与 digest) */
|
|
98
|
-
const SKILL_SIGNATURE_FILE
|
|
66
|
+
const SKILL_SIGNATURE_FILE = "webskill.skill-signature.json";
|
|
99
67
|
const SIGNATURE_SCHEMA_VERSION = 1;
|
|
100
68
|
const encoder = new TextEncoder();
|
|
101
69
|
const toBase64Url = (bytes) => {
|
|
@@ -171,7 +139,7 @@ function parseSignature(raw, origin) {
|
|
|
171
139
|
}
|
|
172
140
|
/** 从技能目录读签名文件;文件不存在返回 undefined(缺签名由策略层判定,不是错误) */
|
|
173
141
|
async function readSkillSignature(fs, skillRoot) {
|
|
174
|
-
const path = `${skillRoot}/${SKILL_SIGNATURE_FILE
|
|
142
|
+
const path = `${skillRoot}/${SKILL_SIGNATURE_FILE}`;
|
|
175
143
|
if (!await fs.exists(path)) return void 0;
|
|
176
144
|
let parsed;
|
|
177
145
|
try {
|
|
@@ -193,7 +161,7 @@ async function verifySkillSignature(input) {
|
|
|
193
161
|
const { manifest, signature, trustedKeys, policy } = input;
|
|
194
162
|
const skill = JSON.stringify(manifest.name);
|
|
195
163
|
if (!signature) {
|
|
196
|
-
if (policy.unsigned === "deny") throw new WebSkillError("SIGNATURE_MISSING", `Skill ${skill} carries no ${SKILL_SIGNATURE_FILE
|
|
164
|
+
if (policy.unsigned === "deny") throw new WebSkillError("SIGNATURE_MISSING", `Skill ${skill} carries no ${SKILL_SIGNATURE_FILE} and the unsigned policy is "deny"`);
|
|
197
165
|
return {
|
|
198
166
|
ok: true,
|
|
199
167
|
unsigned: true,
|
|
@@ -279,245 +247,9 @@ var FsTrustedKeyStore = class {
|
|
|
279
247
|
await this.#write((await this.#read()).filter((key) => key.keyId !== keyId));
|
|
280
248
|
}
|
|
281
249
|
};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
kind = "memory";
|
|
286
|
-
#entries = /* @__PURE__ */ new Map();
|
|
287
|
-
constructor() {
|
|
288
|
-
this.#entries.set(ROOT, {
|
|
289
|
-
type: "directory",
|
|
290
|
-
content: /* @__PURE__ */ new Uint8Array(0),
|
|
291
|
-
mtimeMs: Date.now()
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
#normalize(path) {
|
|
295
|
-
const out = [];
|
|
296
|
-
for (const seg of path.replace(/\\/g, "/").split("/")) {
|
|
297
|
-
if (seg === "" || seg === ".") continue;
|
|
298
|
-
if (seg === "..") out.pop();
|
|
299
|
-
else out.push(seg);
|
|
300
|
-
}
|
|
301
|
-
return ROOT + out.join("/");
|
|
302
|
-
}
|
|
303
|
-
#parentOf(key) {
|
|
304
|
-
const i = key.lastIndexOf("/");
|
|
305
|
-
return i <= 0 ? ROOT : key.slice(0, i);
|
|
306
|
-
}
|
|
307
|
-
#ensureParents(key) {
|
|
308
|
-
const chain = [];
|
|
309
|
-
let dir = this.#parentOf(key);
|
|
310
|
-
while (!this.#entries.has(dir)) {
|
|
311
|
-
chain.push(dir);
|
|
312
|
-
dir = this.#parentOf(dir);
|
|
313
|
-
}
|
|
314
|
-
for (const d of chain.reverse()) this.#entries.set(d, {
|
|
315
|
-
type: "directory",
|
|
316
|
-
content: /* @__PURE__ */ new Uint8Array(0),
|
|
317
|
-
mtimeMs: Date.now()
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
#getFile(key) {
|
|
321
|
-
const entry = this.#entries.get(key);
|
|
322
|
-
if (!entry || entry.type !== "file") throw new WebSkillError("FS_NOT_FOUND", `File not found: ${key}`);
|
|
323
|
-
return entry;
|
|
324
|
-
}
|
|
325
|
-
#getDirectory(key) {
|
|
326
|
-
const entry = this.#entries.get(key);
|
|
327
|
-
if (!entry || entry.type !== "directory") throw new WebSkillError("FS_NOT_FOUND", `Directory not found: ${key}`);
|
|
328
|
-
return entry;
|
|
329
|
-
}
|
|
330
|
-
async readText(path) {
|
|
331
|
-
return new TextDecoder().decode(this.#getFile(this.#normalize(path)).content);
|
|
332
|
-
}
|
|
333
|
-
async writeText(path, content) {
|
|
334
|
-
await this.writeBinary(path, new TextEncoder().encode(content));
|
|
335
|
-
}
|
|
336
|
-
async appendText(path, content) {
|
|
337
|
-
const key = this.#normalize(path);
|
|
338
|
-
const existing = this.#entries.get(key);
|
|
339
|
-
if (existing && existing.type !== "file") throw new WebSkillError("FS_PERMISSION_DENIED", `Cannot append to a directory: ${key}`);
|
|
340
|
-
const prior = existing ? new TextDecoder().decode(existing.content) : "";
|
|
341
|
-
this.#ensureParents(key);
|
|
342
|
-
this.#entries.set(key, {
|
|
343
|
-
type: "file",
|
|
344
|
-
content: new TextEncoder().encode(prior + content),
|
|
345
|
-
mtimeMs: Date.now()
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
async readBinary(path) {
|
|
349
|
-
return this.#getFile(this.#normalize(path)).content;
|
|
350
|
-
}
|
|
351
|
-
async writeBinary(path, content) {
|
|
352
|
-
const key = this.#normalize(path);
|
|
353
|
-
this.#ensureParents(key);
|
|
354
|
-
this.#entries.set(key, {
|
|
355
|
-
type: "file",
|
|
356
|
-
content,
|
|
357
|
-
mtimeMs: Date.now()
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
async exists(path) {
|
|
361
|
-
return this.#entries.has(this.#normalize(path));
|
|
362
|
-
}
|
|
363
|
-
async stat(path) {
|
|
364
|
-
const key = this.#normalize(path);
|
|
365
|
-
const entry = this.#entries.get(key);
|
|
366
|
-
if (!entry) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${key}`);
|
|
367
|
-
return {
|
|
368
|
-
path: key,
|
|
369
|
-
type: entry.type,
|
|
370
|
-
size: entry.type === "file" ? entry.content.length : void 0,
|
|
371
|
-
mtimeMs: entry.mtimeMs
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
async list(path) {
|
|
375
|
-
const key = this.#normalize(path);
|
|
376
|
-
this.#getDirectory(key);
|
|
377
|
-
const prefix = key === ROOT ? ROOT : key + "/";
|
|
378
|
-
const out = [];
|
|
379
|
-
for (const [k, entry] of this.#entries) {
|
|
380
|
-
if (k === key || !k.startsWith(prefix)) continue;
|
|
381
|
-
if (k.slice(prefix.length).includes("/")) continue;
|
|
382
|
-
out.push({
|
|
383
|
-
path: k,
|
|
384
|
-
type: entry.type,
|
|
385
|
-
size: entry.type === "file" ? entry.content.length : void 0,
|
|
386
|
-
mtimeMs: entry.mtimeMs
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
return out;
|
|
390
|
-
}
|
|
391
|
-
async mkdir(path) {
|
|
392
|
-
const key = this.#normalize(path);
|
|
393
|
-
if (this.#entries.has(key)) return;
|
|
394
|
-
this.#ensureParents(key);
|
|
395
|
-
this.#entries.set(key, {
|
|
396
|
-
type: "directory",
|
|
397
|
-
content: /* @__PURE__ */ new Uint8Array(0),
|
|
398
|
-
mtimeMs: Date.now()
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
async remove(path, options) {
|
|
402
|
-
const key = this.#normalize(path);
|
|
403
|
-
if (!this.#entries.get(key)) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${key}`);
|
|
404
|
-
if (key === ROOT) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", "Cannot delete root directory");
|
|
405
|
-
const prefix = key + "/";
|
|
406
|
-
const descendants = [...this.#entries.keys()].filter((k) => k.startsWith(prefix));
|
|
407
|
-
if (descendants.length > 0 && !options?.recursive) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Directory not empty, use recursive: ${key}`);
|
|
408
|
-
for (const k of descendants) this.#entries.delete(k);
|
|
409
|
-
this.#entries.delete(key);
|
|
410
|
-
}
|
|
411
|
-
async rename(from, to) {
|
|
412
|
-
const src = this.#normalize(from);
|
|
413
|
-
const dst = this.#normalize(to);
|
|
414
|
-
const entry = this.#entries.get(src);
|
|
415
|
-
if (!entry) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${src}`);
|
|
416
|
-
const prefix = src + "/";
|
|
417
|
-
const descendants = [...this.#entries.entries()].filter(([k]) => k.startsWith(prefix));
|
|
418
|
-
this.#ensureParents(dst);
|
|
419
|
-
this.#entries.set(dst, entry);
|
|
420
|
-
for (const [k, v] of descendants) this.#entries.set(dst + k.slice(src.length), v);
|
|
421
|
-
for (const [k] of descendants) this.#entries.delete(k);
|
|
422
|
-
this.#entries.delete(src);
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
/** 统一分隔符为 `/`,去除 `.` 段与重复分隔符(不解析 `..`) */
|
|
426
|
-
function normalizePath(path) {
|
|
427
|
-
return path.replace(/\\/g, "/").split("/").filter((s) => s !== "" && s !== ".").join("/");
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* 外部标识(runId、candidateId、skillName、versionId 等)作为单一路径段使用前的统一校验:
|
|
431
|
-
* 拒绝空串、`.`、`..`、含 `/` 或 `\`、含 `:`(Windows 盘符/ADS)。
|
|
432
|
-
* 违规抛 FS_PATH_OUTSIDE_ROOT(kind 用于错误消息定位,如 "runId")。
|
|
433
|
-
*/
|
|
434
|
-
const CONTROL_CHARS_RE = /[\x00-\x1f\x7f]/;
|
|
435
|
-
function assertSafePathSegment(segment, kind) {
|
|
436
|
-
if (CONTROL_CHARS_RE.test(segment)) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Invalid ${kind} (control characters including NUL are not allowed): ${JSON.stringify(segment)}`);
|
|
437
|
-
if (segment === "" || segment === "." || segment === ".." || segment.includes("/") || segment.includes("\\") || segment.includes(":")) throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Invalid ${kind} (must be a single safe path segment): ${JSON.stringify(segment)}`);
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* 将相对路径安全地解析到 root 之内。
|
|
441
|
-
* 拒绝空路径、绝对路径(`/`、`\\`、`C:\` 形式)与任何 `..` 段。
|
|
442
|
-
*
|
|
443
|
-
* 注意:本函数仅做**词法**校验,不解析符号链接——root 内经符号链接指向
|
|
444
|
-
* 外部的目标无法被词法检查拦截(realpath 防护见 NodeFS root 模式与
|
|
445
|
-
* 安装管线的 lstat 符号链接扫描)。
|
|
446
|
-
*/
|
|
447
|
-
function resolveInsideRoot(root, relativePath) {
|
|
448
|
-
const fail = (reason) => {
|
|
449
|
-
throw new WebSkillError("FS_PATH_OUTSIDE_ROOT", `Path outside root: ${reason} (path: ${JSON.stringify(relativePath)})`);
|
|
450
|
-
};
|
|
451
|
-
if (relativePath.trim() === "") fail("empty path");
|
|
452
|
-
if (relativePath.startsWith("/") || relativePath.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(relativePath)) fail("absolute paths not allowed");
|
|
453
|
-
const segments = relativePath.replace(/\\/g, "/").split("/");
|
|
454
|
-
if (CONTROL_CHARS_RE.test(relativePath)) fail("control characters including NUL are not allowed");
|
|
455
|
-
if (segments.some((s) => s === "..")) fail("`..` segments not allowed");
|
|
456
|
-
const clean = segments.filter((s) => s !== "" && s !== ".");
|
|
457
|
-
if (clean.length === 0) fail("path resolves to empty");
|
|
458
|
-
const normalizedRoot = normalizePath(root);
|
|
459
|
-
const joined = normalizedRoot === "" ? clean.join("/") : `${normalizedRoot}/${clean.join("/")}`;
|
|
460
|
-
return root.startsWith("/") || root.startsWith("\\") ? `/${joined}` : joined;
|
|
461
|
-
}
|
|
462
|
-
const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "localhost."]);
|
|
463
|
-
function ipv4Octets(host) {
|
|
464
|
-
const parts = host.split(".");
|
|
465
|
-
if (parts.length !== 4) return void 0;
|
|
466
|
-
const octets = parts.map((p) => /^\d{1,3}$/.test(p) ? Number(p) : NaN);
|
|
467
|
-
return octets.every((n) => n >= 0 && n <= 255) ? octets : void 0;
|
|
468
|
-
}
|
|
469
|
-
function isPrivateIpv4(octets) {
|
|
470
|
-
const [a, b] = octets;
|
|
471
|
-
return a === 0 || a === 10 || a === 127 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
|
|
472
|
-
}
|
|
473
|
-
function isPrivateIpv6(host) {
|
|
474
|
-
const h = host.toLowerCase();
|
|
475
|
-
if (h === "::1" || h === "::") return true;
|
|
476
|
-
const mapped = h.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
477
|
-
if (mapped) {
|
|
478
|
-
const octets = ipv4Octets(mapped[1]);
|
|
479
|
-
return octets !== void 0 && isPrivateIpv4(octets);
|
|
480
|
-
}
|
|
481
|
-
const mappedHex = h.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
|
|
482
|
-
if (mappedHex) {
|
|
483
|
-
const hi = parseInt(mappedHex[1], 16);
|
|
484
|
-
const lo = parseInt(mappedHex[2], 16);
|
|
485
|
-
return isPrivateIpv4([
|
|
486
|
-
hi >> 8,
|
|
487
|
-
hi & 255,
|
|
488
|
-
lo >> 8,
|
|
489
|
-
lo & 255
|
|
490
|
-
]);
|
|
491
|
-
}
|
|
492
|
-
const first = h.split(":")[0];
|
|
493
|
-
if (/^[0-9a-f]{1,4}$/.test(first)) {
|
|
494
|
-
const n = parseInt(first, 16);
|
|
495
|
-
if ((n & 65472) === 65152) return true;
|
|
496
|
-
if ((n & 65024) === 64512) return true;
|
|
497
|
-
}
|
|
498
|
-
return false;
|
|
499
|
-
}
|
|
500
|
-
function isPrivateHost(hostname) {
|
|
501
|
-
const host = hostname.toLowerCase();
|
|
502
|
-
if (LOOPBACK_HOSTNAMES.has(host) || host.endsWith(".localhost")) return true;
|
|
503
|
-
const v4 = ipv4Octets(host);
|
|
504
|
-
if (v4) return isPrivateIpv4(v4);
|
|
505
|
-
if (host.startsWith("[") && host.endsWith("]")) return isPrivateIpv6(host.slice(1, -1));
|
|
506
|
-
if (host.includes(":")) return isPrivateIpv6(host);
|
|
507
|
-
return false;
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* 校验远程 URL 是否允许连接;违规抛 NETWORK_BLOCKED。
|
|
511
|
-
* rawUrl 非合法 URL 时抛 TypeError(由调用方按自身错误码包装)。
|
|
512
|
-
*/
|
|
513
|
-
function assertRemoteUrlAllowed(rawUrl, policy = {}) {
|
|
514
|
-
const url = new URL(rawUrl);
|
|
515
|
-
if (url.protocol === "http:") {
|
|
516
|
-
if (!policy.allowHttp) throw new WebSkillError("NETWORK_BLOCKED", `Insecure URL scheme "http:" is not allowed (opt in via allowHttp): ${url.href}`);
|
|
517
|
-
} else if (url.protocol !== "https:") throw new WebSkillError("NETWORK_BLOCKED", `URL scheme "${url.protocol}" is not allowed (https only): ${url.href}`);
|
|
518
|
-
if (!policy.allowPrivateHosts && isPrivateHost(url.hostname)) throw new WebSkillError("NETWORK_BLOCKED", `Refusing to connect to a private/loopback/link-local address: ${url.hostname}`);
|
|
519
|
-
return url;
|
|
520
|
-
}
|
|
250
|
+
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region ../core/src/skill/parser.ts
|
|
521
253
|
const FRONTMATTER_RE = /^---[^\S\r\n]*\r?\n([\s\S]*?)\r?\n---[^\S\r\n]*(?:\r?\n|$)([\s\S]*)$/;
|
|
522
254
|
const invalid = (message, details) => {
|
|
523
255
|
throw new WebSkillError("SKILL_INVALID_METADATA", message, details);
|
|
@@ -541,12 +273,18 @@ function parseSkillMarkdown(raw) {
|
|
|
541
273
|
body: bodyRaw.trim()
|
|
542
274
|
};
|
|
543
275
|
}
|
|
276
|
+
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region ../core/src/skill/naming.ts
|
|
544
279
|
const SKILL_NAME_MAX_LENGTH = 64;
|
|
545
280
|
/** Agent Skills 官方命名规范:小写字母/数字/连字符,不允许首尾或连续连字符 */
|
|
546
281
|
const SKILL_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
547
282
|
function isValidSkillName(name) {
|
|
548
283
|
return name.length <= 64 && SKILL_NAME_PATTERN.test(name);
|
|
549
284
|
}
|
|
285
|
+
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region ../core/src/skill/rules.ts
|
|
550
288
|
const SCRIPT_EXTENSION_RE = /\.(ts|js)$/;
|
|
551
289
|
/** D2 schema sidecar(scripts/<name>.schema.json)属于协议内文件,不算非法脚本 */
|
|
552
290
|
const SCHEMA_SIDECAR_RE = /\.schema\.json$/;
|
|
@@ -649,6 +387,9 @@ function checkDependencyCycles(adjacency) {
|
|
|
649
387
|
involved
|
|
650
388
|
};
|
|
651
389
|
}
|
|
390
|
+
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region ../core/src/skill/skillPack.ts
|
|
652
393
|
/**
|
|
653
394
|
* 技能包集(skill pack)格式与打包逻辑(node/browser 共用单一来源):
|
|
654
395
|
* webskill.skill-pack.json { schemaVersion: 1, skills: [{ name, digest }] }
|
|
@@ -722,6 +463,9 @@ function parseSkillPackManifest(text) {
|
|
|
722
463
|
}
|
|
723
464
|
return raw;
|
|
724
465
|
}
|
|
466
|
+
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region ../core/src/skill/archiveShape.ts
|
|
725
469
|
const SKILL_FILE = "SKILL.md";
|
|
726
470
|
function normalize(path) {
|
|
727
471
|
return path.replace(/^\.?\//, "");
|
|
@@ -754,7 +498,7 @@ function detectSkillArchiveShape(entries) {
|
|
|
754
498
|
if (!hasLooseRootFile && topLevels.size === 1) {
|
|
755
499
|
const [dir] = [...topLevels];
|
|
756
500
|
if (dir !== void 0) {
|
|
757
|
-
if (paths.includes(`${dir}
|
|
501
|
+
if (paths.includes(`${dir}/${"webskill.skill-pack.json"}`)) return {
|
|
758
502
|
shape: "pack-set",
|
|
759
503
|
rootDir: dir
|
|
760
504
|
};
|
|
@@ -798,11 +542,14 @@ async function detectSkillArchiveShapeFromFs(fs, contentDir) {
|
|
|
798
542
|
continue;
|
|
799
543
|
}
|
|
800
544
|
if (await fs.exists(`${entry.path}/${SKILL_FILE}`)) sample.push(`${name}/${SKILL_FILE}`);
|
|
801
|
-
else if (await fs.exists(`${entry.path}
|
|
545
|
+
else if (await fs.exists(`${entry.path}/${"webskill.skill-pack.json"}`)) sample.push(`${name}/${SKILL_PACK_FILE}`);
|
|
802
546
|
else sample.push(`${name}/`);
|
|
803
547
|
}
|
|
804
548
|
return detectSkillArchiveShape(sample);
|
|
805
549
|
}
|
|
550
|
+
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region ../core/src/skill/zipLimits.ts
|
|
806
553
|
const DEFAULT_ARCHIVE_LIMITS = {
|
|
807
554
|
maxDownloadBytes: 64 * 1024 * 1024,
|
|
808
555
|
maxEntryBytes: 64 * 1024 * 1024,
|
|
@@ -912,10 +659,16 @@ async function readResponseWithLimit(res, limits) {
|
|
|
912
659
|
}
|
|
913
660
|
return concat(chunks, size);
|
|
914
661
|
}
|
|
662
|
+
|
|
663
|
+
//#endregion
|
|
664
|
+
//#region ../core/src/skill/catalog.ts
|
|
915
665
|
/** 由条目列表构建 Catalog,保证按名称排序 */
|
|
916
666
|
function buildCatalog(entries) {
|
|
917
667
|
return { entries: [...entries].sort((a, b) => a.name.localeCompare(b.name)) };
|
|
918
668
|
}
|
|
669
|
+
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region ../core/src/skill/reader.ts
|
|
919
672
|
/** 按技能名读取技能根目录内的文件,全部读取强制经过路径安全检查 */
|
|
920
673
|
var SkillReader = class {
|
|
921
674
|
#fs;
|
|
@@ -939,6 +692,9 @@ var SkillReader = class {
|
|
|
939
692
|
return this.#fs.readBinary(resolveInsideRoot(this.#rootFor(skillName), relativePath));
|
|
940
693
|
}
|
|
941
694
|
};
|
|
695
|
+
|
|
696
|
+
//#endregion
|
|
697
|
+
//#region ../core/src/skill/discovery.ts
|
|
942
698
|
const baseName = (path) => path.split("/").pop() ?? path;
|
|
943
699
|
var SkillDiscovery = class {
|
|
944
700
|
#fs;
|
|
@@ -1085,6 +841,9 @@ var SkillDiscovery = class {
|
|
|
1085
841
|
return (await this.#fs.list(scriptsDir)).filter((s) => s.type === "file").map((s) => baseName(s.path));
|
|
1086
842
|
}
|
|
1087
843
|
};
|
|
844
|
+
|
|
845
|
+
//#endregion
|
|
846
|
+
//#region ../core/src/skill/validator.ts
|
|
1088
847
|
/**
|
|
1089
848
|
* 合规校验。内部直接复用 SkillDiscovery 的扫描逻辑,
|
|
1090
849
|
* 规则判定同样落在 checkSkillRules 单一来源上。
|
|
@@ -1099,35 +858,6 @@ async function validateSkills(fs, roots, options) {
|
|
|
1099
858
|
issues
|
|
1100
859
|
};
|
|
1101
860
|
}
|
|
1102
|
-
function renderCatalogJson(catalog) {
|
|
1103
|
-
return JSON.stringify(catalog, null, 2);
|
|
1104
|
-
}
|
|
1105
|
-
const jsonRenderer = {
|
|
1106
|
-
format: "json",
|
|
1107
|
-
render: renderCatalogJson
|
|
1108
|
-
};
|
|
1109
|
-
/** 转义 XML 五个特殊字符:& < > " ' */
|
|
1110
|
-
function escapeXml(text) {
|
|
1111
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1112
|
-
}
|
|
1113
|
-
function renderAvailableSkillsXml(catalog) {
|
|
1114
|
-
const lines = ["<available_skills>"];
|
|
1115
|
-
for (const skill of catalog.entries) {
|
|
1116
|
-
lines.push(" <skill>");
|
|
1117
|
-
lines.push(` <name>${escapeXml(skill.name)}</name>`);
|
|
1118
|
-
lines.push(` <description>${escapeXml(skill.description)}</description>`);
|
|
1119
|
-
if (skill.version !== void 0) lines.push(` <version>${escapeXml(skill.version)}</version>`);
|
|
1120
|
-
if (skill.license !== void 0) lines.push(` <license>${escapeXml(skill.license)}</license>`);
|
|
1121
|
-
lines.push(` <source>${escapeXml(skill.source)}</source>`);
|
|
1122
|
-
lines.push(" </skill>");
|
|
1123
|
-
}
|
|
1124
|
-
lines.push("</available_skills>");
|
|
1125
|
-
return lines.join("\n");
|
|
1126
|
-
}
|
|
1127
|
-
const xmlRenderer = {
|
|
1128
|
-
format: "xml",
|
|
1129
|
-
render: renderAvailableSkillsXml
|
|
1130
|
-
};
|
|
1131
861
|
|
|
1132
862
|
//#endregion
|
|
1133
|
-
export {
|
|
863
|
+
export { SKILL_MANIFEST_FILE as A, keyIdOf as C, verifySkillSignature as D, signaturePayloadBytes as E, buildManifest as M, computeDigest as N, MANIFEST_EXCLUDED_FILES as O, verifyManifest as P, SIGNATURE_SCHEMA_VERSION as S, signSkill as T, SKILL_NAME_MAX_LENGTH as _, DEFAULT_ARCHIVE_LIMITS as a, parseSkillMarkdown as b, unzipWithLimits as c, stripArchiveRoot as d, SKILL_PACK_FILE as f, checkSkillRules as g, checkDependencyCycles as h, buildCatalog as i, SKILL_SIGNATURE_FILE$1 as j, SKILLS_LOCKFILE as k, detectSkillArchiveShape as l, parseSkillPackManifest as m, SkillDiscovery as n, readResponseWithLimit as o, exportSkills as p, SkillReader as r, resolveArchiveLimits as s, validateSkills as t, detectSkillArchiveShapeFromFs as u, SKILL_NAME_PATTERN as v, readSkillSignature as w, FsTrustedKeyStore as x, isValidSkillName as y };
|
package/dist/{skillVersionStore-D-qHk9ZE-DheTIwAB.d.ts → skillVersionStore-B24Jjjry-C4zomhMZ.d.ts}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region ../governance/dist/skillVersionStore-
|
|
1
|
+
import { U as FileSystemProvider, X as PageQuery, Y as Page, _t as SkillManifest, ut as SkillCatalogEntry } from "./types-C3V6lAeO-BIf7UB2e.js";
|
|
2
|
+
//#region ../governance/dist/skillVersionStore-B24Jjjry.d.ts
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
type CandidateStatus = 'draft' | 'pending-review' | 'approved' | 'published' | 'rejected';
|
|
5
5
|
/** `generated` 是 0.5.0 的技能自动生成来源(需求 12 号 AC-9.1) */
|
|
@@ -94,6 +94,8 @@ declare class CandidateStore {
|
|
|
94
94
|
constructor(deps: {
|
|
95
95
|
root: string;
|
|
96
96
|
fs: FileSystemProvider;
|
|
97
|
+
/** 治理根下该技能是否仍装着;不注入则 published 一律拒删(见 `remove`) */
|
|
98
|
+
isSkillInstalled?: (name: string) => Promise<boolean>;
|
|
97
99
|
});
|
|
98
100
|
save(candidate: CandidateSkill): Promise<void>;
|
|
99
101
|
get(id: string): Promise<CandidateSkill>;
|
|
@@ -106,6 +108,16 @@ declare class CandidateStore {
|
|
|
106
108
|
status?: CandidateStatus;
|
|
107
109
|
} & PageQuery): Promise<CandidatePage>;
|
|
108
110
|
updateStatus(id: string, status: CandidateStatus, now?: string): Promise<CandidateSkill>;
|
|
111
|
+
/**
|
|
112
|
+
* 把候选记录从队列里删掉。
|
|
113
|
+
*
|
|
114
|
+
* `approved` 恒拒删:它是「已批准但还没发布」,删掉等于让批准过的东西凭空消失;
|
|
115
|
+
* 要清理先 `reject`(留下理由)再删。
|
|
116
|
+
* `published` 是已安装技能的来源凭证,技能还装着时同样拒删;技能卸载后它只剩堆在队列里,
|
|
117
|
+
* 此时可删(0.15.0 分册 16 D-16-2)。未注入 `isSkillInstalled` 时判断不了装没装,
|
|
118
|
+
* 退化为「published 一律拒删」——宁可留着清不掉,也不能把还装着的技能的来源凭证删了。
|
|
119
|
+
*/
|
|
120
|
+
remove(id: string): Promise<void>;
|
|
109
121
|
}
|
|
110
122
|
/** 硬门禁:仅 published 可转换为 Catalog 条目,否则 APPROVAL_REQUIRED */
|
|
111
123
|
declare function candidateToCatalogEntry(candidate: CandidateSkill): SkillCatalogEntry;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { t as WebSkillError } from "./errors-BDZNpC13.js";
|
|
2
|
+
|
|
3
|
+
//#region ../runtime/src/interaction/surface.ts
|
|
4
|
+
const MAX_SURFACE_BYTES = 256 * 1024;
|
|
5
|
+
const MAX_ACTIONS = 32;
|
|
6
|
+
const MAX_NODES = 2e3;
|
|
7
|
+
const MAX_NODE_DEPTH = 32;
|
|
8
|
+
const MAX_JSON_DEPTH = 16;
|
|
9
|
+
const MAX_PATCH_OPERATIONS = 128;
|
|
10
|
+
const actionIntents = /* @__PURE__ */ new Set([
|
|
11
|
+
"submit",
|
|
12
|
+
"cancel",
|
|
13
|
+
"select",
|
|
14
|
+
"download",
|
|
15
|
+
"refresh"
|
|
16
|
+
]);
|
|
17
|
+
function isRecord(value) {
|
|
18
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
function reject(message) {
|
|
21
|
+
throw new WebSkillError("VALIDATION_FAILED", message);
|
|
22
|
+
}
|
|
23
|
+
function requireString(value, name) {
|
|
24
|
+
if (typeof value !== "string" || value.trim() === "") reject(`${name} must be a non-empty string`);
|
|
25
|
+
}
|
|
26
|
+
function requireNonNegativeInteger(value, name) {
|
|
27
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) reject(`${name} must be a non-negative integer`);
|
|
28
|
+
}
|
|
29
|
+
function isJsonValue(value, depth = 0) {
|
|
30
|
+
if (depth > MAX_JSON_DEPTH || value === void 0 || typeof value === "function" || typeof value === "symbol") return false;
|
|
31
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
32
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
33
|
+
if (Array.isArray(value)) return value.every((item) => isJsonValue(item, depth + 1));
|
|
34
|
+
if (!isRecord(value)) return false;
|
|
35
|
+
return Object.keys(value).every((key) => key !== "__proto__" && key !== "constructor" && isJsonValue(value[key], depth + 1));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Runtime 只做结构校验;组件名白名单与 props schema 属于 catalog 语义,
|
|
39
|
+
* 留在 `@webskill/ui`——runtime 不得为了校验反向依赖 ui。
|
|
40
|
+
*/
|
|
41
|
+
function assertNode(value, path, depth, counter) {
|
|
42
|
+
if (depth > MAX_NODE_DEPTH) reject(`A UI spec tree must not nest deeper than ${MAX_NODE_DEPTH} levels`);
|
|
43
|
+
if (++counter.nodes > MAX_NODES) reject(`A UI spec tree must contain at most ${MAX_NODES} nodes`);
|
|
44
|
+
if (!isRecord(value)) reject(`${path} must be an object`);
|
|
45
|
+
requireString(value["component"], `${path}.component`);
|
|
46
|
+
if (value["id"] !== void 0) requireString(value["id"], `${path}.id`);
|
|
47
|
+
if (value["props"] !== void 0 && (!isRecord(value["props"]) || !isJsonValue(value["props"]))) reject(`${path}.props must be a JSON object`);
|
|
48
|
+
const children = value["children"];
|
|
49
|
+
if (children === void 0) return;
|
|
50
|
+
if (!Array.isArray(children)) reject(`${path}.children must be an array`);
|
|
51
|
+
children.forEach((child, index) => assertNode(child, `${path}.children[${index}]`, depth + 1, counter));
|
|
52
|
+
}
|
|
53
|
+
function assertActions(value) {
|
|
54
|
+
if (value === void 0) return;
|
|
55
|
+
if (!Array.isArray(value) || value.length > MAX_ACTIONS) reject(`Surface actions must contain at most ${MAX_ACTIONS} items`);
|
|
56
|
+
for (const action of value) {
|
|
57
|
+
if (!isRecord(action)) reject("A surface action must be an object");
|
|
58
|
+
requireString(action["id"], "Surface action ID");
|
|
59
|
+
const intent = action["intent"];
|
|
60
|
+
if (typeof intent !== "string" || !actionIntents.has(intent)) reject("Surface action intent is invalid");
|
|
61
|
+
if (action["awaitResponse"] !== void 0 && typeof action["awaitResponse"] !== "boolean") reject("Surface action awaitResponse must be a boolean");
|
|
62
|
+
if (action["nonce"] !== void 0 && (typeof action["nonce"] !== "string" || action["nonce"] === "")) reject("Surface action nonce must be a non-empty string");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Validates the allowlisted, data-only node tree accepted by a UI surface renderer. @experimental */
|
|
66
|
+
function validateUiSpecNode(value) {
|
|
67
|
+
assertNode(value, "root", 0, { nodes: 0 });
|
|
68
|
+
if (!isJsonValue(value)) reject("A UI spec tree must contain JSON data only");
|
|
69
|
+
if (JSON.stringify(value).length > MAX_SURFACE_BYTES) reject(`A UI spec tree exceeds the ${MAX_SURFACE_BYTES}-byte limit`);
|
|
70
|
+
return structuredClone(value);
|
|
71
|
+
}
|
|
72
|
+
function assertPatch(value) {
|
|
73
|
+
if (!isRecord(value)) reject("A surface patch operation must be an object");
|
|
74
|
+
if (value["op"] !== "replace" && value["op"] !== "merge" && value["op"] !== "append") reject("Surface patch operation is invalid");
|
|
75
|
+
requireString(value["path"], "Surface patch path");
|
|
76
|
+
if (!value["path"].startsWith("/")) reject("Surface patch path must be a JSON pointer");
|
|
77
|
+
if (!isJsonValue(value["value"])) reject("Surface patch value must be JSON data");
|
|
78
|
+
}
|
|
79
|
+
/** Validates an individual event in the framework-neutral surface stream. @experimental */
|
|
80
|
+
function validateUiSpecEvent(value) {
|
|
81
|
+
if (!isRecord(value) || typeof value["type"] !== "string") reject("A UI surface event must have a type");
|
|
82
|
+
if (value["runId"] !== void 0) requireString(value["runId"], "Surface event run ID");
|
|
83
|
+
switch (value["type"]) {
|
|
84
|
+
case "open":
|
|
85
|
+
requireString(value["id"], "UI surface ID");
|
|
86
|
+
assertActions(value["actions"]);
|
|
87
|
+
return {
|
|
88
|
+
type: "open",
|
|
89
|
+
...value["runId"] ? { runId: value["runId"] } : {},
|
|
90
|
+
id: value["id"],
|
|
91
|
+
node: validateUiSpecNode(value["node"]),
|
|
92
|
+
...value["actions"] ? { actions: structuredClone(value["actions"]) } : {}
|
|
93
|
+
};
|
|
94
|
+
case "patch":
|
|
95
|
+
requireString(value["id"], "Surface patch ID");
|
|
96
|
+
requireNonNegativeInteger(value["revision"], "Surface patch revision");
|
|
97
|
+
if (!Array.isArray(value["operations"]) || value["operations"].length === 0) reject("A surface patch requires operations");
|
|
98
|
+
if (value["operations"].length > MAX_PATCH_OPERATIONS) reject(`A surface patch must contain at most ${MAX_PATCH_OPERATIONS} operations`);
|
|
99
|
+
for (const operation of value["operations"]) assertPatch(operation);
|
|
100
|
+
return {
|
|
101
|
+
type: "patch",
|
|
102
|
+
...value["runId"] ? { runId: value["runId"] } : {},
|
|
103
|
+
id: value["id"],
|
|
104
|
+
revision: value["revision"],
|
|
105
|
+
operations: structuredClone(value["operations"])
|
|
106
|
+
};
|
|
107
|
+
case "complete":
|
|
108
|
+
requireString(value["id"], "Surface completion ID");
|
|
109
|
+
requireNonNegativeInteger(value["revision"], "Surface completion revision");
|
|
110
|
+
return {
|
|
111
|
+
type: "complete",
|
|
112
|
+
...value["runId"] ? { runId: value["runId"] } : {},
|
|
113
|
+
id: value["id"],
|
|
114
|
+
revision: value["revision"]
|
|
115
|
+
};
|
|
116
|
+
case "error":
|
|
117
|
+
requireString(value["id"], "Surface error ID");
|
|
118
|
+
requireString(value["code"], "Surface error code");
|
|
119
|
+
requireString(value["message"], "Surface error message");
|
|
120
|
+
return {
|
|
121
|
+
type: "error",
|
|
122
|
+
...value["runId"] ? { runId: value["runId"] } : {},
|
|
123
|
+
id: value["id"],
|
|
124
|
+
code: value["code"],
|
|
125
|
+
message: value["message"]
|
|
126
|
+
};
|
|
127
|
+
case "cancel":
|
|
128
|
+
requireString(value["id"], "Surface cancellation ID");
|
|
129
|
+
return {
|
|
130
|
+
type: "cancel",
|
|
131
|
+
...value["runId"] ? { runId: value["runId"] } : {},
|
|
132
|
+
id: value["id"]
|
|
133
|
+
};
|
|
134
|
+
default: return reject("UI surface event type is invalid");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** Extracts validated surface stream events from structured tool output. @experimental */
|
|
138
|
+
function extractUiSpecEvents(data) {
|
|
139
|
+
if (!isRecord(data) || data["$surface"] === void 0) return [];
|
|
140
|
+
const raw = data["$surface"];
|
|
141
|
+
return (Array.isArray(raw) ? raw : [raw]).map((event) => validateUiSpecEvent(event));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
export { validateUiSpecEvent as n, validateUiSpecNode as r, extractUiSpecEvents as t };
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as UiBridge, c as InteractionResponse, l as LlmClient, m as LlmStreamEvent, n as ArtifactStore, p as LlmResponse, s as InteractionRequest, t as Artifact, u as LlmCompleteInput, v as MemoryStore } from "./types-
|
|
1
|
+
import { S as UiBridge, c as InteractionResponse, l as LlmClient, m as LlmStreamEvent, n as ArtifactStore, p as LlmResponse, s as InteractionRequest, t as Artifact, u as LlmCompleteInput, v as MemoryStore } from "./types-C3V6lAeO-BIf7UB2e.js";
|
|
2
2
|
import { a as loadGoogleConfigFromEnv, i as loadAnthropicConfigFromEnv, n as LlmEnvConfig, o as loadLlmConfigFromEnv, r as ProviderEnvConfig } from "./env-AK3cSMEA-Dli6QU5E.js";
|
|
3
3
|
//#region ../runtime/dist/testing.d.ts
|
|
4
4
|
//#region src/llm/mockLlmClient.d.ts
|