chore: commit workspace updates

This commit is contained in:
ddCat
2026-09-14 01:11:56 +08:00
parent 01a2d171f5
commit 9727656b2b
491 changed files with 12826 additions and 72249 deletions

View File

@@ -1,17 +1,13 @@
import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references";
import type { ResponseFunctionTool } from "@/services/api/image";
import {
getSkillBundle,
getSkillFile,
listSkillFiles,
searchSkillFiles,
type Skill,
type SkillPackageBundle,
type SkillPackageFile,
} from "@/services/api/skills";
export type SkillRuntimeProfile = "canvas" | "creation" | "shortDrama" | "director" | "onlineAgent" | "localAgent";
export type SkillRuntimeDelivery = "linked-context" | "progressive-tools" | "native-package";
export type SkillRuntimeProfile = "canvas" | "creation" | "shortDrama" | "director";
export type SkillRuntimeDelivery = "linked-context";
type SkillRuntimeProfileConfig = {
delivery: SkillRuntimeDelivery;
@@ -25,8 +21,6 @@ export const SKILL_RUNTIME_PROFILES: Record<SkillRuntimeProfile, SkillRuntimePro
creation: { delivery: "linked-context", maxSkills: 4, maxContextChars: 32_000, maxLinkedFilesPerSkill: 3 },
shortDrama: { delivery: "linked-context", maxSkills: 4, maxContextChars: 32_000, maxLinkedFilesPerSkill: 3 },
director: { delivery: "linked-context", maxSkills: 4, maxContextChars: 32_000, maxLinkedFilesPerSkill: 3 },
onlineAgent: { delivery: "progressive-tools", maxSkills: 0, maxContextChars: 0, maxLinkedFilesPerSkill: 0 },
localAgent: { delivery: "native-package", maxSkills: 4, maxContextChars: 0, maxLinkedFilesPerSkill: 0 },
};
export type SkillRuntimeVersion = {
@@ -61,29 +55,11 @@ export type LinkedSkillRuntimeResult = {
metadata: SkillRuntimeMetadata;
};
export type NativeSkillPackage = {
skillId: string;
name: string;
description: string;
version: string;
files: Array<{ path: string; mimeType: string; contentBase64: string }>;
};
export type NativeSkillRuntimeResult = {
delivery: "native-package";
prompt: string;
selectedSkills: Skill[];
skills: NativeSkillPackage[];
provenance: SkillRuntimeProvenance;
metadata: SkillRuntimeMetadata;
};
type SkillRuntimeResultByProfile = {
canvas: LinkedSkillRuntimeResult;
creation: LinkedSkillRuntimeResult;
shortDrama: LinkedSkillRuntimeResult;
director: LinkedSkillRuntimeResult;
localAgent: NativeSkillRuntimeResult;
};
export type PrepareSkillRuntimeInput<P extends keyof SkillRuntimeResultByProfile> = {
@@ -98,8 +74,6 @@ export type SkillRuntimeToolResult = { ok: true; message: string; data?: unknown
type SkillRuntimeDependencies = {
getFile: typeof getSkillFile;
listFiles: typeof listSkillFiles;
searchFiles: typeof searchSkillFiles;
getBundle: typeof getSkillBundle;
};
type PreparedSkillInput = {
@@ -112,31 +86,10 @@ type SkillDeliveryAdapter<TResult> = {
prepare: (input: PreparedSkillInput) => Promise<TResult>;
};
type SkillToolAdapter = {
tools: ResponseFunctionTool[];
toolNames: ReadonlySet<string>;
executeTool: (name: string, args: Record<string, unknown>, skills: Skill[]) => Promise<SkillRuntimeToolResult | null>;
};
const SKILL_REF_PATTERN = /@\[skill:([^\]]+)\]/g;
const TEXT_FILE_EXTENSIONS = new Set([".md", ".mdx", ".txt", ".json", ".yaml", ".yml", ".toml", ".csv"]);
const EMPTY_PROVENANCE: SkillRuntimeProvenance = { skillIds: [], skillVersions: [], skillFiles: [] };
export const SKILL_RUNTIME_AGENT_GUIDANCE =
"技能采用渐进式读取:canvas_list_skills 只发现元数据;canvas_get_skill 只读取入口 SKILL.md;入口引用其他文件时,再调用 canvas_list_skill_files、canvas_search_skill_files 或 canvas_read_skill_file。禁止一次性读取整个技能包。";
function functionTool(name: string, description: string, properties: Record<string, unknown>, required: string[] = []): ResponseFunctionTool {
return { type: "function", function: { name, description, parameters: { type: "object", properties, required, additionalProperties: false }, strict: false } };
}
const PROGRESSIVE_SKILL_TOOLS: ResponseFunctionTool[] = [
functionTool("canvas_list_skills", "自动发现当前用户已加入的技能;可按 query 检索名称、描述或标签,offset/limit 分页;只返回元数据。", { query: { type: "string" }, offset: { type: "number" }, limit: { type: "number" } }),
functionTool("canvas_get_skill", "按 skillId 或名称读取技能元数据和入口 SKILL.md;不会加载 references、scripts、assets 等其他文件。", { skillId: { type: "string" }, name: { type: "string" } }),
functionTool("canvas_list_skill_files", "列出一个技能包的文件路径、类型和大小,不读取文件正文。", { skillId: { type: "string" }, name: { type: "string" } }),
functionTool("canvas_read_skill_file", "按路径读取技能包中的一个文本文件。先从入口引用或文件清单确定路径,禁止猜测路径。", { skillId: { type: "string" }, name: { type: "string" }, path: { type: "string" } }, ["path"]),
functionTool("canvas_search_skill_files", "在一个技能包的文本文件中搜索关键词,返回路径、行号和短片段,不返回整包正文。", { skillId: { type: "string" }, name: { type: "string" }, query: { type: "string" } }, ["query"]),
];
export function resolveSkillMentions(prompt: string, skills: Skill[], selectedSkillIds?: string[]) {
const activeSkills = skills.filter((skill) => skill.isAdded);
if (!activeSkills.length) return [];
@@ -183,22 +136,12 @@ export function skillRuntimeMetadata(provenance: SkillRuntimeProvenance): SkillR
export function createSkillRuntime(dependencies: SkillRuntimeDependencies = {
getFile: getSkillFile,
listFiles: listSkillFiles,
searchFiles: searchSkillFiles,
getBundle: getSkillBundle,
}) {
const linkedContextAdapter: SkillDeliveryAdapter<LinkedSkillRuntimeResult> = {
prepare: (input) => prepareLinkedContext(input, dependencies),
};
const nativePackageAdapter: SkillDeliveryAdapter<NativeSkillRuntimeResult> = {
prepare: (input) => prepareNativePackages(input, dependencies),
};
const progressiveToolsAdapter = createProgressiveToolsAdapter(dependencies);
const deliveryAdapters: Record<Exclude<SkillRuntimeDelivery, "progressive-tools">, SkillDeliveryAdapter<LinkedSkillRuntimeResult | NativeSkillRuntimeResult>> = {
const deliveryAdapters: Record<SkillRuntimeDelivery, SkillDeliveryAdapter<LinkedSkillRuntimeResult>> = {
"linked-context": linkedContextAdapter,
"native-package": nativePackageAdapter,
};
const toolAdapters: Partial<Record<SkillRuntimeDelivery, SkillToolAdapter>> = {
"progressive-tools": progressiveToolsAdapter,
};
return {
@@ -209,16 +152,6 @@ export function createSkillRuntime(dependencies: SkillRuntimeDependencies = {
if (!adapter) throw new Error(`技能运行模式 ${config.delivery} 不支持直接准备上下文`);
return adapter.prepare({ prompt: normalizeSkillTokens(input.prompt, input.skills), selectedSkills, config }) as Promise<SkillRuntimeResultByProfile[P]>;
},
agentTools(profile: SkillRuntimeProfile) {
return toolAdapters[SKILL_RUNTIME_PROFILES[profile].delivery]?.tools || [];
},
agentToolNames(profile: SkillRuntimeProfile) {
return toolAdapters[SKILL_RUNTIME_PROFILES[profile].delivery]?.toolNames || new Set<string>();
},
executeAgentTool(profile: SkillRuntimeProfile, name: string, args: Record<string, unknown>, skills: Skill[]) {
const adapter = toolAdapters[SKILL_RUNTIME_PROFILES[profile].delivery];
return adapter ? adapter.executeTool(name, args, skills) : Promise.resolve(null);
},
};
}
@@ -320,100 +253,6 @@ function linkedResult(prompt: string, selectedSkills: Skill[], provenance: Skill
return { delivery: "linked-context", prompt, selectedSkills, provenance, metadata: skillRuntimeMetadata(provenance) };
}
async function prepareNativePackages(input: PreparedSkillInput, dependencies: SkillRuntimeDependencies): Promise<NativeSkillRuntimeResult> {
const bundles = await Promise.all(input.selectedSkills.map((skill) => dependencies.getBundle(skill.skillId)));
const skills = bundles.map((result, index) => nativePackage(input.selectedSkills[index], result.bundle));
const provenance = {
skillIds: input.selectedSkills.map((skill) => skill.skillId),
skillVersions: input.selectedSkills.map((skill, index) => ({ skillId: skill.skillId, versionId: bundles[index].bundle.versionId, version: bundles[index].bundle.version })),
skillFiles: bundles.flatMap((result, index) => result.bundle.files.map((file) => ({ skillId: input.selectedSkills[index].skillId, path: file.path }))),
};
return { delivery: "native-package", prompt: input.prompt, selectedSkills: input.selectedSkills, skills, provenance, metadata: skillRuntimeMetadata(provenance) };
}
function nativePackage(skill: Skill, bundle: SkillPackageBundle): NativeSkillPackage {
return {
skillId: skill.skillId,
name: skill.skillName,
description: skill.description,
version: bundle.version,
files: bundle.files.map((file) => ({ path: file.path, mimeType: file.mimeType, contentBase64: file.contentBase64 })),
};
}
function createProgressiveToolsAdapter(dependencies: SkillRuntimeDependencies): SkillToolAdapter {
const handlers: Record<string, (args: Record<string, unknown>, skills: Skill[]) => Promise<SkillRuntimeToolResult>> = {
canvas_list_skills: async (args, skills) => {
const terms = typeof args.query === "string"
? args.query.trim().toLocaleLowerCase().split(/\s+/).filter(Boolean)
: [];
const matches = skills.filter((skill) => skill.isAdded && terms.every((term) =>
`${skill.skillName} ${skill.description} ${skill.tag}`.toLocaleLowerCase().includes(term),
));
const offset = typeof args.offset === "number" && Number.isFinite(args.offset)
? Math.max(0, Math.floor(args.offset))
: 0;
const limit = typeof args.limit === "number" && Number.isFinite(args.limit)
? Math.max(1, Math.min(100, Math.floor(args.limit)))
: 40;
const items = matches.slice(offset, offset + limit).map((skill) => ({
skillId: skill.skillId,
name: skill.skillName,
description: skill.description,
tag: skill.tag,
version: skill.version,
fileCount: skill.fileCount,
sourceType: skill.sourceType,
}));
return {
ok: true,
message: items.length ? "已发现相关技能,请按需读取入口;无匹配时可缩短关键词或查询完整目录。" : "没有匹配技能,可调整关键词。",
data: {
items,
total: matches.length,
nextOffset: offset + items.length < matches.length ? offset + items.length : null,
},
};
},
canvas_get_skill: async (args, skills) => {
const skill = requireAddedSkill(skills, args);
const entry = await dependencies.getFile(skill.skillId, "SKILL.md");
return { ok: true, message: `已读取技能「${skill.skillName}」的入口 SKILL.md;如入口引用其他文件,请继续按需读取。`, data: { skillId: skill.skillId, name: skill.skillName, description: skill.description, version: skill.version, fileCount: skill.fileCount, sourceType: skill.sourceType, entry: entry.file } };
},
canvas_list_skill_files: async (args, skills) => {
const skill = requireAddedSkill(skills, args);
const result = await dependencies.listFiles(skill.skillId);
return { ok: true, message: `已列出技能「${skill.skillName}」的 ${result.files.length} 个文件,仅含元数据。`, data: { skillId: skill.skillId, files: result.files } };
},
canvas_read_skill_file: async (args, skills) => {
const skill = requireAddedSkill(skills, args);
const path = requireString(args.path, "path");
const result = await dependencies.getFile(skill.skillId, path);
if (result.file.binary) return { ok: false, message: `文件 ${path} 不是可读取的文本文件。` };
return { ok: true, message: `已按需读取 ${path}。`, data: { skillId: skill.skillId, file: result.file } };
},
canvas_search_skill_files: async (args, skills) => {
const skill = requireAddedSkill(skills, args);
const query = requireString(args.query, "query");
const result = await dependencies.searchFiles(skill.skillId, query);
return { ok: true, message: result.results.length ? `在技能「${skill.skillName}」中找到 ${result.results.length} 处匹配。` : "技能包中没有匹配内容。", data: { skillId: skill.skillId, query, results: result.results } };
},
};
return {
tools: PROGRESSIVE_SKILL_TOOLS,
toolNames: new Set(Object.keys(handlers)),
executeTool: async (name, args, skills) => {
const handler = handlers[name];
if (!handler) return null;
try {
return await handler(args, skills);
} catch (error) {
return { ok: false, message: error instanceof Error ? error.message : "技能工具执行失败" };
}
},
};
}
function provenanceFromLoaded(loaded: Array<{ skill: Skill; files: Array<{ path: string; sha256?: string }> }>): SkillRuntimeProvenance {
return {
skillIds: loaded.map((item) => item.skill.skillId),
@@ -431,14 +270,16 @@ function normalizeSkillTokens(prompt: string, skills: Skill[]) {
}
function containsNaturalSkillMention(value: string, name: string) {
const token = `@${name}`;
let index = 0;
while (index < value.length) {
const found = value.indexOf(token, index);
if (found < 0) return false;
const after = found + token.length;
if (hasMentionBoundary(value, after)) return true;
index = after;
for (const prefix of ["@", "/"]) {
const token = `${prefix}${name}`;
let index = 0;
while (index < value.length) {
const found = value.indexOf(token, index);
if (found < 0) break;
const after = found + token.length;
if (hasMentionBoundary(value, after)) return true;
index = after;
}
}
return false;
}
@@ -455,16 +296,3 @@ function normalizePackagePath(path: string) {
function escapeAttribute(value: string) {
return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function requireAddedSkill(skills: Skill[], args: Record<string, unknown>) {
const skillId = typeof args.skillId === "string" ? args.skillId.trim() : "";
const name = typeof args.name === "string" ? args.name.trim().toLocaleLowerCase() : "";
const skill = skills.find((item) => item.isAdded && (item.skillId === skillId || (name && item.skillName.toLocaleLowerCase() === name)));
if (!skill) throw new Error("未找到已加入的技能,请先调用 canvas_list_skills。");
return skill;
}
function requireString(value: unknown, field: string) {
if (typeof value !== "string" || !value.trim()) throw new Error(`${field} 必须是非空字符串`);
return value.trim();
}