Download web/src/lib/skill-runtime.ts from zlcy/li: direct link, hf CLI and curl.
- Browser
- Download file 25 kB
-
https://huggingface.co/spaces/zlcy/li/resolve/main/web/src/lib/skill-runtime.ts
- Command line
-
hf download hf://spaces/zlcy/li/web/src/lib/skill-runtime.ts
-
curl -L -o skill-runtime.ts https://huggingface.co/spaces/zlcy/li/resolve/main/web/src/lib/skill-runtime.ts
25 kB
| import { | |
| fetchAvailableSkills, | |
| fetchSkillRuntime, | |
| readSkillResource, | |
| runSkillScript, | |
| type InstalledSkill, | |
| type SkillRuntime, | |
| type SkillScriptArtifact, | |
| } from "@/lib/api"; | |
| import { type ChatCompletionResponse, type ChatToolPart } from "@/lib/chat-runtime"; | |
| import { httpRequest } from "@/lib/request"; | |
| export type SkillCapability = "chat" | "web_search" | "image_generation" | "image_edit"; | |
| export type SkillImageWorkflow = { | |
| version: 1 | 2; | |
| summary: string; | |
| tasks: Array<{ | |
| id: string; | |
| title?: string; | |
| operation?: "source_or_generate" | "generate" | "edit"; | |
| reference?: string; | |
| prompt: string; | |
| size?: string; | |
| }>; | |
| postprocess?: { | |
| script: string; | |
| args: string[]; | |
| title?: string; | |
| inputs?: string[]; | |
| }; | |
| }; | |
| export type PreparedSkillInvocation = { | |
| skill: InstalledSkill; | |
| runtime: SkillRuntime; | |
| request: string; | |
| capability: SkillCapability; | |
| prompt: string; | |
| summary: string; | |
| toolParts: ChatToolPart[]; | |
| artifacts: SkillScriptArtifact[]; | |
| plannerContext: string; | |
| imageWorkflow?: SkillImageWorkflow; | |
| awaitingInput?: { | |
| missing: Array<{ id: string; label: string; type: string }>; | |
| question: string; | |
| }; | |
| }; | |
| type SkillAgentDecision = { | |
| action: "read_resource" | "run_script" | "complete"; | |
| path?: string; | |
| script?: string; | |
| args?: string[]; | |
| capability?: SkillCapability; | |
| prompt?: string; | |
| summary?: string; | |
| }; | |
| type MediaParserPayload = { | |
| success?: boolean; | |
| summary?: Record<string, unknown>; | |
| results?: Array<Record<string, unknown>>; | |
| }; | |
| function assistantText(response: ChatCompletionResponse) { | |
| const value = response.choices?.[0]?.message?.content; | |
| if (typeof value === "string") return value.trim(); | |
| if (!Array.isArray(value)) return ""; | |
| return value.flatMap((item) => { | |
| if (typeof item === "string") return [item]; | |
| if (!item || typeof item !== "object") return []; | |
| const record = item as { text?: unknown; content?: unknown }; | |
| if (typeof record.text === "string") return [record.text]; | |
| if (typeof record.content === "string") return [record.content]; | |
| return []; | |
| }).join("\n").trim(); | |
| } | |
| function jsonDecision(value: string): SkillAgentDecision { | |
| const clean = value.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim(); | |
| const start = clean.indexOf("{"); | |
| const end = clean.lastIndexOf("}"); | |
| if (start < 0 || end <= start) { | |
| return { action: "complete", capability: "chat", prompt: clean, summary: "Skill 已完成处理" }; | |
| } | |
| try { | |
| return JSON.parse(clean.slice(start, end + 1)) as SkillAgentDecision; | |
| } catch { | |
| return { action: "complete", capability: "chat", prompt: clean, summary: "Skill 已完成处理" }; | |
| } | |
| } | |
| function invocationId(prefix: string, index: number) { | |
| return `${prefix}-${Date.now()}-${index}`; | |
| } | |
| function requiredResourcePaths(runtime: SkillRuntime) { | |
| const known = runtime.resources | |
| .filter((item) => item.readable && item.path !== "SKILL.md") | |
| .map((item) => item.path); | |
| const trigger = /\b(?:read|load|consult|review)\b|(?:读取|阅读|加载|查阅)/i; | |
| const required = new Set<string>(); | |
| for (const line of runtime.content.split(/\r?\n/)) { | |
| if (!trigger.test(line)) continue; | |
| for (const path of known) { | |
| if (line.includes(path)) required.add(path); | |
| } | |
| } | |
| return [...required]; | |
| } | |
| function markdownEnumeration(path: string, content: string) { | |
| const block = content.match(/(?:^|\n)##\s+Contents\s*\n([\s\S]*?)(?=\n##\s|$)/i)?.[1] || ""; | |
| const items = [...block.matchAll(/^\s*[-*]\s+(.+?)\s*$/gm)] | |
| .map((match) => match[1].replace(/\s+[-–—].*$/, "").trim()) | |
| .filter((item) => item && !/(?:blending|mixing)\s+rules?|混合规则/i.test(item)); | |
| if (items.length < 2) return ""; | |
| return `<skill_variants source="${path}">${JSON.stringify(items)}</skill_variants>`; | |
| } | |
| function buildPlannerContext(runtime: SkillRuntime, readContents: Map<string, string>) { | |
| const sources = new Map<string, string>([["SKILL.md", runtime.content], ...readContents.entries()]); | |
| const prioritized = [...sources.entries()].sort(([left], [right]) => { | |
| const score = (path: string) => /director-grammars|reference-gallery|color-signatures/i.test(path) ? 0 : 1; | |
| return score(left) - score(right) || left.localeCompare(right); | |
| }); | |
| const variants = prioritized.map(([path, content]) => markdownEnumeration(path, content)).filter(Boolean); | |
| const resources = prioritized.map(([path, content]) => ( | |
| `<skill_resource path="${path}">\n${content}\n</skill_resource>` | |
| )).join("\n\n").slice(0, 60_000); | |
| return [...variants, resources].filter(Boolean).join("\n\n"); | |
| } | |
| function parseImageWorkflow(value: string): SkillImageWorkflow | undefined { | |
| try { | |
| const raw = JSON.parse(value) as Partial<SkillImageWorkflow>; | |
| if (![1, 2].includes(Number(raw.version)) || !Array.isArray(raw.tasks) || !raw.tasks.length || raw.tasks.length > 32) return undefined; | |
| const tasks = raw.tasks.flatMap((task) => { | |
| const id = String(task?.id || "").trim(); | |
| const prompt = String(task?.prompt || "").trim(); | |
| if (!id || !prompt) return []; | |
| const operation = String(task?.operation || "").trim(); | |
| if (operation && !["source_or_generate", "generate", "edit"].includes(operation)) return []; | |
| return [{ | |
| id, | |
| prompt, | |
| ...(task.title ? { title: String(task.title) } : {}), | |
| ...(operation ? { operation: operation as "source_or_generate" | "generate" | "edit" } : {}), | |
| ...(task.reference ? { reference: String(task.reference).trim() } : {}), | |
| ...(task.size ? { size: String(task.size) } : {}), | |
| }]; | |
| }); | |
| if (tasks.length !== raw.tasks.length || new Set(tasks.map((task) => task.id)).size !== tasks.length) return undefined; | |
| const taskIds = new Set(tasks.map((task) => task.id)); | |
| if (tasks.some((task) => task.operation === "edit" && (!task.reference || !taskIds.has(task.reference)))) return undefined; | |
| const postprocess = raw.postprocess && typeof raw.postprocess === "object" | |
| ? { | |
| script: String(raw.postprocess.script || "").trim(), | |
| args: Array.isArray(raw.postprocess.args) ? raw.postprocess.args.map(String) : [], | |
| ...(raw.postprocess.title ? { title: String(raw.postprocess.title) } : {}), | |
| ...(Array.isArray(raw.postprocess.inputs) | |
| ? { inputs: raw.postprocess.inputs.map(String).map((item) => item.trim()).filter(Boolean) } | |
| : {}), | |
| } | |
| : undefined; | |
| if (postprocess && (!postprocess.script.startsWith("scripts/") || !postprocess.args.length)) return undefined; | |
| if (postprocess?.inputs?.some((input) => !taskIds.has(input))) return undefined; | |
| return { | |
| version: Number(raw.version) as 1 | 2, | |
| summary: String(raw.summary || "按 Skill 工作流生成独立图片并组装最终成品").trim(), | |
| tasks, | |
| ...(postprocess ? { postprocess } : {}), | |
| }; | |
| } catch { | |
| return undefined; | |
| } | |
| } | |
| export async function loadSkillPlannerContext(skillId: string) { | |
| const runtime = await fetchSkillRuntime(skillId); | |
| const contents = new Map<string, string>(); | |
| const paths = requiredResourcePaths(runtime); | |
| const results = await Promise.allSettled(paths.map(async (path) => { | |
| const resource = await readSkillResource(skillId, path); | |
| return [path, resource.content] as const; | |
| })); | |
| for (const result of results) { | |
| if (result.status === "fulfilled") contents.set(result.value[0], result.value[1]); | |
| } | |
| return buildPlannerContext(runtime, contents); | |
| } | |
| export async function resolveInstalledSkill(prompt: string, forcedSkillId = "") { | |
| const skills = (await fetchAvailableSkills()).items; | |
| const mentions = [...prompt.matchAll(/\$([a-z0-9._-]+)/gi)]; | |
| const match = forcedSkillId | |
| ? null | |
| : mentions.find((item) => !["search", "chat", "image", "image-generation"].includes(item[1].toLowerCase())); | |
| const requested = (forcedSkillId || match?.[1] || "").toLowerCase(); | |
| if (!requested) return null; | |
| const skill = skills.find((item) => item.id.toLowerCase() === requested || item.name.toLowerCase() === requested); | |
| if (!skill) return null; | |
| const request = match && match.index !== undefined | |
| ? `${prompt.slice(0, match.index)}${prompt.slice(match.index + match[0].length)}`.replace(/\s{2,}/g, " ").trim() | |
| : prompt.trim(); | |
| return { skill, request }; | |
| } | |
| export async function prepareInstalledSkill({ | |
| prompt, | |
| model, | |
| attachmentFiles, | |
| attachmentSummary, | |
| invocationKey, | |
| forcedSkillId = "", | |
| conversationContext = "", | |
| onProgress, | |
| }: { | |
| prompt: string; | |
| model: string; | |
| attachmentFiles: File[]; | |
| attachmentSummary: Array<{ name: string; type: string; size: number }>; | |
| invocationKey: string; | |
| forcedSkillId?: string; | |
| conversationContext?: string; | |
| onProgress?: (parts: ChatToolPart[]) => void | Promise<void>; | |
| }): Promise<PreparedSkillInvocation | null> { | |
| const resolved = await resolveInstalledSkill(prompt, forcedSkillId); | |
| if (!resolved) return null; | |
| const runtime = await fetchSkillRuntime(resolved.skill.id); | |
| const toolParts: ChatToolPart[] = []; | |
| const artifacts: SkillScriptArtifact[] = []; | |
| const requiredInputs = runtime.presentation.inputs.filter((input) => input.required); | |
| const missingInputs = requiredInputs.filter((input) => { | |
| const type = input.type.toLowerCase(); | |
| if (["image", "photo"].includes(type)) return !attachmentFiles.some((file) => file.type.startsWith("image/")); | |
| if (["file", "document", "pdf", "archive", "video", "audio"].includes(type)) return attachmentFiles.length === 0; | |
| if (["text", "prompt", "request"].includes(type)) return !resolved.request.trim(); | |
| return false; | |
| }); | |
| if (missingInputs.length) { | |
| const labels = missingInputs.map((input) => input.label); | |
| const question = `开始使用「${runtime.name}」前,还需要:${labels.join("、")}。请补充或上传后继续。`; | |
| return { | |
| skill: resolved.skill, | |
| runtime, | |
| request: resolved.request, | |
| capability: "chat", | |
| prompt: question, | |
| summary: question, | |
| toolParts, | |
| artifacts, | |
| plannerContext: "", | |
| awaitingInput: { | |
| missing: missingInputs.map((input) => ({ id: input.id, label: input.label, type: input.type })), | |
| question, | |
| }, | |
| }; | |
| } | |
| const messages: Array<{ role: "system" | "user" | "assistant"; content: string }> = [ | |
| { | |
| role: "system", | |
| content: [ | |
| "You are a Skill runtime controller. Follow the installed SKILL.md exactly and prepare one executable action for the host application.", | |
| "You can request one tool action at a time. Return JSON only, with no Markdown fences.", | |
| 'Read tool: {"action":"read_resource","path":"relative/path"}. Read every resource the Skill explicitly requires before execution.', | |
| 'Script tool: {"action":"run_script","script":"scripts/name.py","args":["..."]}. Only choose a listed, allowed script. Use {{input:0}}, {{output:name.ext}}, {{workspace}}, {{outputs}}, or {{skill}} for paths.', | |
| 'Complete action: {"action":"complete","capability":"chat|web_search|image_generation|image_edit","prompt":"fully prepared downstream instruction","summary":"short user-facing summary"}.', | |
| "The host has native chat, web search, image generation, and image editing. Prefer those native capabilities when the Skill says to prefer a built-in capability; do not run a provider fallback script in that case.", | |
| "Use image_edit when an uploaded image must be transformed. Use image_generation only without a source image.", | |
| "Never claim a resource was read or a script ran until its tool result is supplied. Do not expose credentials, private environment variables, or internal authentication.", | |
| "Do not silently invent unresolved options when the Skill requires user choice. In that case complete with capability chat and a concise question in prompt.", | |
| ].join("\n"), | |
| }, | |
| { | |
| role: "user", | |
| content: JSON.stringify({ | |
| skill: { | |
| id: runtime.id, | |
| name: runtime.name, | |
| description: runtime.description, | |
| instructions: runtime.content, | |
| resources: runtime.resources.map((item) => ({ path: item.path, size: item.size, readable: item.readable })), | |
| scripts: runtime.scripts.map((item) => ({ path: item.path, kind: item.kind, allowed: item.allowed })), | |
| }, | |
| user_request: resolved.request, | |
| conversation_context: conversationContext || undefined, | |
| attachments: attachmentSummary, | |
| }), | |
| }, | |
| ]; | |
| const readPaths = new Set<string>(); | |
| const readContents = new Map<string, string>(); | |
| const scriptEvidence: string[] = []; | |
| let imageWorkflow: SkillImageWorkflow | undefined; | |
| let scriptRuns = 0; | |
| let lastDecision: SkillAgentDecision | null = null; | |
| const readResource = async (path: string, key: string) => { | |
| const tool: ChatToolPart = { | |
| type: "tool", | |
| toolId: `skill_read_resource_${key}`, | |
| invocationId: invocationId("skill-resource", toolParts.length), | |
| status: "running", | |
| title: `正在读取 Skill 资源 · ${path}`, | |
| input: { skill: runtime.id, path }, | |
| }; | |
| toolParts.push(tool); | |
| await onProgress?.([...toolParts]); | |
| try { | |
| const resource = await readSkillResource(runtime.id, path); | |
| readPaths.add(path); | |
| readContents.set(path, resource.content); | |
| Object.assign(tool, { status: "success", title: `已读取 Skill 资源 · ${path}`, output: { path, size: resource.size } }); | |
| messages.push({ role: "user", content: `<tool_result tool="read_resource" path="${path}">\n${resource.content}\n</tool_result>` }); | |
| } catch (error) { | |
| Object.assign(tool, { status: "error", error: error instanceof Error ? error.message : "读取失败" }); | |
| await onProgress?.([...toolParts]); | |
| throw error; | |
| } | |
| await onProgress?.([...toolParts]); | |
| }; | |
| const workflowPath = runtime.resources.find((item) => item.path.toLowerCase() === "skill-workflow.json" && item.readable)?.path; | |
| if (workflowPath) { | |
| await readResource(workflowPath, "host_workflow"); | |
| imageWorkflow = parseImageWorkflow(readContents.get(workflowPath) || ""); | |
| if (imageWorkflow) { | |
| // A host workflow is already an executable, validated plan. Returning it | |
| // directly avoids both the Skill-controller LLM and the image-planner LLM, | |
| // which add minutes of latency and can accidentally discard postprocess. | |
| return { | |
| skill: resolved.skill, | |
| runtime, | |
| request: resolved.request, | |
| capability: attachmentFiles.length ? "image_edit" : "image_generation", | |
| prompt: imageWorkflow.summary, | |
| summary: imageWorkflow.summary, | |
| toolParts, | |
| artifacts, | |
| plannerContext: buildPlannerContext(runtime, readContents), | |
| imageWorkflow, | |
| }; | |
| } | |
| } | |
| const mandatoryResources = requiredResourcePaths(runtime).filter((path) => !readPaths.has(path)); | |
| for (let index = 0; index < mandatoryResources.length; index += 1) { | |
| await readResource(mandatoryResources[index], `required_${index}`); | |
| } | |
| // Media parsing is an execution Skill, not a prompt-writing Skill. Route | |
| // it deterministically through its approved CLI so the model cannot replace | |
| // a real download with a narrated/fabricated JSON response. | |
| if (runtime.id.toLowerCase() === "media-parser") { | |
| const script = runtime.scripts.find((item) => ( | |
| item.allowed && item.path.replace(/\\/g, "/").endsWith("/media_parser_cli.py") | |
| )); | |
| if (!script) throw new Error("media-parser 的执行入口尚未获得管理员授权"); | |
| const noDownload = /(?:不|不要|无需|仅|只).{0,12}下载|只.{0,12}(?:直链|元数据)|(?:直链|元数据).{0,12}(?:不|不要)下载/i.test(resolved.request); | |
| // Always parse remote media URLs without saving the payload to this server. | |
| // The backend turns those signed CDN URLs into expiring streamed artifacts, | |
| // forwarding platform-required headers (notably Bilibili Referer/User-Agent). | |
| const args = [resolved.request, "--no-download", "--pretty"]; | |
| const tool: ChatToolPart = { | |
| type: "tool", | |
| toolId: "skill_run_script_media_parser", | |
| invocationId: invocationId("skill-script", toolParts.length), | |
| status: "running", | |
| title: noDownload ? "正在解析媒体链接" : "正在解析可下载直链", | |
| input: { skill: runtime.id, script: script.path, args, attachmentCount: attachmentFiles.length }, | |
| }; | |
| toolParts.push(tool); | |
| await onProgress?.([...toolParts]); | |
| try { | |
| const result = await runSkillScript(runtime.id, script.path, args, attachmentFiles, `${invocationKey}:media-parser`); | |
| let parsed: MediaParserPayload | undefined; | |
| try { | |
| parsed = JSON.parse(result.stdout) as MediaParserPayload; | |
| } catch { | |
| parsed = undefined; | |
| } | |
| const parserErrors = Array.isArray(parsed?.results) | |
| ? parsed.results.map((item) => String(item.error || "").trim()).filter(Boolean) | |
| : []; | |
| if (result.status !== "success" || parsed?.success === false) { | |
| throw new Error(parserErrors.join(";") || result.stderr || result.stdout || "媒体解析失败"); | |
| } | |
| artifacts.push(...result.artifacts); | |
| Object.assign(tool, { | |
| status: "success", | |
| title: noDownload ? "媒体链接解析完成" : `媒体直链已就绪 · ${result.artifacts.length} 个文件`, | |
| output: { | |
| exitCode: result.exit_code, | |
| durationMs: result.duration_ms, | |
| summary: parsed?.summary, | |
| results: parsed?.results, | |
| artifacts: result.artifacts, | |
| }, | |
| }); | |
| await onProgress?.([...toolParts]); | |
| const summary = noDownload | |
| ? "媒体链接已真实解析完成" | |
| : `媒体解析已完成,共生成 ${result.artifacts.length} 个可流式下载文件`; | |
| return { | |
| skill: resolved.skill, | |
| runtime, | |
| request: resolved.request, | |
| capability: "chat", | |
| prompt: [ | |
| "media-parser 已由系统真实执行完成。请依据下面的结构化结果简洁汇总标题、作者和成功/失败数量。", | |
| noDownload | |
| ? "用户要求只解析,不要声称已经下载文件。" | |
| : "可下载媒体已经作为本条回复的临时流式附件提供;明确告诉用户可点击附件上的下载按钮直接从源站保存,不要声称服务器已经保存完整媒体。", | |
| `解析结果:\n${result.stdout}`, | |
| result.artifacts.length ? `真实下载附件:\n${JSON.stringify(result.artifacts)}` : "", | |
| ].filter(Boolean).join("\n\n"), | |
| summary, | |
| toolParts, | |
| artifacts, | |
| plannerContext: buildPlannerContext(runtime, readContents), | |
| }; | |
| } catch (error) { | |
| Object.assign(tool, { status: "error", error: error instanceof Error ? error.message : "媒体解析失败" }); | |
| await onProgress?.([...toolParts]); | |
| throw error; | |
| } | |
| } | |
| for (let step = 0; step < 12; step += 1) { | |
| const response = await httpRequest<ChatCompletionResponse>("/v1/chat/completions", { | |
| method: "POST", | |
| body: { model: model || "auto", messages, stream: false }, | |
| }); | |
| const raw = assistantText(response); | |
| const decision = jsonDecision(raw); | |
| lastDecision = decision; | |
| messages.push({ role: "assistant", content: JSON.stringify(decision) }); | |
| if (decision.action === "read_resource") { | |
| const path = String(decision.path || "").trim(); | |
| if (!path) throw new Error("Skill 请求读取资源,但没有提供路径"); | |
| if (readPaths.has(path)) { | |
| messages.push({ | |
| role: "user", | |
| content: `<tool_result tool="read_resource" path="${path}" cached="true">\n${readContents.get(path) || ""}\n</tool_result>`, | |
| }); | |
| continue; | |
| } | |
| await readResource(path, `agent_${step}`); | |
| continue; | |
| } | |
| if (decision.action === "run_script") { | |
| const script = String(decision.script || "").trim(); | |
| if (!script) throw new Error("Skill 请求执行脚本,但没有提供脚本路径"); | |
| if (scriptRuns >= 3) throw new Error("单轮 Skill 最多执行 3 次脚本"); | |
| scriptRuns += 1; | |
| const args = Array.isArray(decision.args) ? decision.args.map(String) : []; | |
| const tool: ChatToolPart = { | |
| type: "tool", | |
| toolId: `skill_run_script_${step}`, | |
| invocationId: invocationId("skill-script", step), | |
| status: "running", | |
| title: `正在执行 Skill 脚本 · ${script}`, | |
| input: { skill: runtime.id, script, args, attachmentCount: attachmentFiles.length }, | |
| }; | |
| toolParts.push(tool); | |
| await onProgress?.([...toolParts]); | |
| try { | |
| const result = await runSkillScript(runtime.id, script, args, attachmentFiles, `${invocationKey}:${step}`); | |
| artifacts.push(...result.artifacts); | |
| scriptEvidence.push([ | |
| `脚本:${script}`, | |
| `状态:${result.status}(退出码 ${result.exit_code})`, | |
| result.stdout ? `标准输出:\n${result.stdout}` : "", | |
| result.stderr ? `标准错误:\n${result.stderr}` : "", | |
| result.artifacts.length ? `产物:${JSON.stringify(result.artifacts)}` : "", | |
| ].filter(Boolean).join("\n")); | |
| Object.assign(tool, { | |
| status: result.status === "success" ? "success" : "error", | |
| title: result.status === "success" ? `Skill 脚本已完成 · ${script}` : `Skill 脚本执行失败 · ${script}`, | |
| output: { | |
| exitCode: result.exit_code, | |
| durationMs: result.duration_ms, | |
| stdout: result.stdout, | |
| stderr: result.stderr, | |
| artifacts: result.artifacts, | |
| }, | |
| ...(result.status === "error" ? { error: result.stderr || `脚本退出码 ${result.exit_code}` } : {}), | |
| }); | |
| messages.push({ role: "user", content: `<tool_result tool="run_script" script="${script}">\n${JSON.stringify(result)}\n</tool_result>` }); | |
| } catch (error) { | |
| Object.assign(tool, { status: "error", error: error instanceof Error ? error.message : "脚本执行失败" }); | |
| await onProgress?.([...toolParts]); | |
| throw error; | |
| } | |
| await onProgress?.([...toolParts]); | |
| continue; | |
| } | |
| if (decision.action === "complete") { | |
| const capability: SkillCapability = ["chat", "web_search", "image_generation", "image_edit"].includes(String(decision.capability)) | |
| ? decision.capability as SkillCapability | |
| : attachmentFiles.length ? "image_edit" : "chat"; | |
| const preparedPrompt = String(decision.prompt || resolved.request).trim(); | |
| const resourceEvidence = [...readContents.entries()] | |
| .map(([path, content]) => `资源:${path}\n${content}`) | |
| .join("\n\n---\n\n") | |
| .slice(0, 18_000); | |
| const promptForHost = capability === "chat" | |
| ? [ | |
| "你是已安装 Skill 的最终执行模型。下面的 Skill 说明和资源已经由系统真实读取并提供给你;不要声称无法访问、无法加载或要求用户换环境。", | |
| "请直接完成原始用户请求,不要只复述计划、征询下一步或把工具结果说成是用户提供的。只有当 Skill 明确要求必须先由用户选择且原请求没有给出选择时,才提出一个必要问题。", | |
| `已安装 Skill「${runtime.name}」的 SKILL.md:\n${runtime.content.slice(0, 20_000)}`, | |
| resourceEvidence ? `已读取的 Skill 资源:\n${resourceEvidence}` : "", | |
| conversationContext ? `与本次 Skill 延续相关的对话上下文:\n${conversationContext}` : "", | |
| `原始用户请求:\n${resolved.request}`, | |
| `Skill 控制器给出的执行指令:\n${preparedPrompt}`, | |
| scriptEvidence.length | |
| ? `已真实执行的 Skill 脚本结果(这是工具证据,不是用户输入):\n${scriptEvidence.join("\n\n---\n\n")}` | |
| : "", | |
| "基于以上内容给出结构清晰、可直接使用的最终交付。用户要求文件时必须创建真实文件附件;若兼容接口不能创建文件,至少返回完整的 fenced SVG/HTML 源码,系统会自动转成可预览附件。若工具已返回具体数值、颜色、字体、布局或文件,必须在答案中呈现。", | |
| ].filter(Boolean).join("\n\n") | |
| : preparedPrompt; | |
| return { | |
| skill: resolved.skill, | |
| runtime, | |
| request: resolved.request, | |
| capability, | |
| prompt: promptForHost, | |
| summary: String(decision.summary || `已按 Skill「${runtime.name}」准备执行`).trim(), | |
| toolParts, | |
| artifacts, | |
| plannerContext: buildPlannerContext(runtime, readContents), | |
| imageWorkflow, | |
| }; | |
| } | |
| } | |
| throw new Error(`Skill 执行超过最大步骤数${lastDecision ? `(最后动作:${lastDecision.action})` : ""}`); | |
| } | |