| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 |
- // Codex 技能的展示 ID 与按 Profile 选择(与 Claude 侧的 providers/claude/skills.ts 对应)
- import fs from 'node:fs'
- import path from 'node:path'
- import { COMMUNITY_SKILL_PLUGIN, CUSTOM_SKILL_PLUGIN } from '@agent-server/shared'
- import type { SkillMetadata } from './protocol/v2'
- import { NO_CODEX_PLUGINS, type CodexPluginGrant } from '../types'
- /** 服务场景下始终禁用的系统技能:会联网下载或写入 Codex 自身目录 */
- export const ALWAYS_DISABLED = new Set(['skill-installer', 'skill-creator', 'plugin-creator'])
- /**
- * 展示 ID:
- * - 系统技能 → codex-system:<name>
- * - 从 openai/skills 安装的技能(CODEX_HOME/skills 下有标记文件)→ openai-skills:<name>
- * - 本地技能目录与第三方技能目录(extraRoots,Codex 按 plugin.json 加了前缀)→ 原样,如 agent-skills:report-conventions
- * - 其他用户技能 → codex-user:<name>
- */
- export function skillDisplayId(s: Pick<SkillMetadata, 'name' | 'scope' | 'path'>, codexHome: string): string {
- if (s.scope === 'system') return `codex-system:${s.name}`
- // Codex 用 SKILL.md 的 name 命名(可能是 "Excel Analysis"),Claude 用目录名;第三方技能统一按目录名,两个引擎 ID 一致
- for (const plugin of [COMMUNITY_SKILL_PLUGIN, CUSTOM_SKILL_PLUGIN]) {
- if (s.name.startsWith(`${plugin}:`)) return `${plugin}:${path.basename(path.dirname(s.path))}`
- }
- if (s.name.includes(':')) return s.name
- const managedRoot = path.join(codexHome, 'skills')
- const dir = path.dirname(s.path)
- if (path.dirname(dir) === managedRoot && fs.existsSync(path.join(dir, '.agent-server.json'))) return `openai-skills:${s.name}`
- return `codex-user:${s.name}`
- }
- /**
- * 计算需要在本次对话中禁用的技能(按路径选择);选择"全部技能"时第三方技能仍需单独勾选。
- * 远程插件带来的技能(pluginId 非空)跟随插件授权:全部开放时(Playground)全部保留,否则只保留开放的插件的技能
- */
- export function skillsToDisable(
- all: SkillMetadata[], selected: string[] | 'all', codexHome: string, localPlugin: string, plugins: CodexPluginGrant = NO_CODEX_PLUGINS, excludedFromAll: string[] = [],
- ): { path: string; enabled: false }[] {
- const excluded = new Set(excludedFromAll)
- const wanted = selected === 'all' ? null : new Set(selected.map(s => (s.includes(':') ? s : `${localPlugin}:${s}`)))
- return all
- .filter(s => {
- if (ALWAYS_DISABLED.has(s.name)) return true
- if (s.pluginId) return plugins !== 'all' && !plugins.pluginIds.includes(s.pluginId)
- const id = skillDisplayId(s, codexHome)
- return wanted === null ? id.startsWith(`${COMMUNITY_SKILL_PLUGIN}:`) || excluded.has(id) : !wanted.has(id)
- })
- .map(s => ({ path: s.path, enabled: false as const }))
- }
|