platform.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. /**
  2. * Fmode Harness 平台常量与端点真值表
  3. * ---------------------------------------------------------------------------
  4. * 本文件是 skill-core-guide 的唯一真值源(single source of truth)。
  5. * 所有端点状态均于 2026-09-22 实测(curl 探针),并标注 verification 字段:
  6. *
  7. * "live" — 实测返回 200/401(401 = 端点存在、需鉴权),可直接使用
  8. * "planned" — 实测 404,设计文档存在但服务端未上线;调用方必须先探测再回落
  9. * "deprecated" — 曾经存在或曾被文档描述,实测 404 且已确认不再维护
  10. *
  11. * ⚠️ 纪律:任何技能调用 planned/deprecated 端点前,必须做一次探测并显式回落,
  12. * 禁止把「文档写了」当成「已经能跑」——这是 Fmode 技能生态最贵的一课
  13. * (见 skill-storage 0.3.0 的「伪自举」事故复盘)。
  14. */
  15. // ============================================================
  16. // 平台基址
  17. // ============================================================
  18. export const PLATFORM = {
  19. name: 'Fmode Harness',
  20. version: '1.0.0',
  21. author: 'Yuyang001 (FmodeAgent)',
  22. /** OpenAI 兼容网关:LLM / 图像生成。鉴权:Bearer sk-... */
  23. apiBase: 'https://api.fmode.cn',
  24. /** Parse 业务网关:转写 / 凭据自举 / deploy STS。鉴权:x-parse-session-token */
  25. gatewayBase: 'https://server.fmode.cn',
  26. /** CDN 映射域名(OBS → fmode.cn 回源) */
  27. cdnBase: 'https://fmode.cn',
  28. /** OBS 直链(CDN 未生效时的降级通道) */
  29. obsBase: 'https://fmode-s3.obs.cn-north-4.myhuaweicloud.com',
  30. /** 主 Gogs(内网日常迭代) */
  31. gogsBase: 'https://git.fmode.cn',
  32. gogsOrg: 'fmode',
  33. /** GitHub 镜像(公开发布) */
  34. githubOrg: 'fmodecn',
  35. npmRegistry: 'https://registry.npmjs.org',
  36. npmOwner: 'fmode001',
  37. /** skillhub.cn 分发渠道 */
  38. skillhub: {
  39. host: 'https://api.skillhub.cn',
  40. team: 'fmode',
  41. orgId: 'org-m8z913un',
  42. cliInstall: 'https://skillhub.cn/install/install.sh',
  43. cliPath: '~/.local/bin/skillhub',
  44. },
  45. };
  46. // ============================================================
  47. // 端点真值表
  48. // ============================================================
  49. /**
  50. * @typedef {Object} Endpoint
  51. * @property {string} id 稳定标识(代码里引用这个,不要硬编码 URL)
  52. * @property {string} method
  53. * @property {string} url
  54. * @property {'api'|'gateway'|'oss'} host 归属基址
  55. * @property {'live'|'planned'|'deprecated'} status
  56. * @property {string} auth 鉴权方式
  57. * @property {string} purpose
  58. * @property {string} [note]
  59. */
  60. /** @type {Record<string, Endpoint>} */
  61. export const ENDPOINTS = {
  62. // ---------- 已上线(实测 200/401)----------
  63. llmChat: {
  64. id: 'llmChat',
  65. method: 'POST',
  66. url: 'https://api.fmode.cn/v1/chat/completions',
  67. host: 'api',
  68. status: 'live',
  69. auth: 'Bearer <fmodeApiToken>',
  70. purpose: 'LLM 对话补全(OpenAI 兼容)。所有技能的统一模型出口。',
  71. note: '实测无 token 返回 401(端点存在)。模型如 glm-5.3-flash / deepseek 系列。',
  72. },
  73. imageGenerate: {
  74. id: 'imageGenerate',
  75. method: 'POST',
  76. url: 'https://api.fmode.cn/v1/images/generations',
  77. host: 'api',
  78. status: 'live',
  79. auth: 'Bearer <fmodeApiToken>',
  80. purpose: '图像生成(fmode-image 使用)。白底 PNG 场景图,约 ¥0.3-0.5/张。',
  81. note: '实测无 token 返回 401(端点存在)。注意是 /v1/images/generations,不是 /api/image/generate。',
  82. },
  83. listenTranscribe: {
  84. id: 'listenTranscribe',
  85. method: 'POST',
  86. url: 'https://server.fmode.cn/api/listen/transcribe',
  87. host: 'gateway',
  88. status: 'live',
  89. auth: 'Bearer <fmodeApiToken>',
  90. purpose: '录音转写(讯飞 LFASR)。fmode-listen 使用,服务端按音频真实时长计费。',
  91. note: '实测无 token 返回 401。讯飞凭据仅服务端持有,客户端零下放。',
  92. },
  93. vocSkillBootstrap: {
  94. id: 'vocSkillBootstrap',
  95. method: 'POST',
  96. url: 'https://server.fmode.cn/api/fmode/voc-skill/install-prompt',
  97. host: 'gateway',
  98. status: 'live',
  99. auth: 'x-parse-session-token: <sessionToken>',
  100. purpose: '【凭据自举唯一通道】sessionToken → fmode API token(sk- 开头)。',
  101. note:
  102. '实测无 token 返回 401。token 内嵌在返回 body.data.prompt 文本中,' +
  103. '用 /sk-(?!ant-)[A-Za-z0-9_-]{8,}/ 提取。token 仅内存持有,禁止落盘进日志。',
  104. },
  105. deploySts: {
  106. id: 'deploySts',
  107. method: 'POST',
  108. url: 'https://server.fmode.cn/api/apig/deploy/huaweicloud',
  109. host: 'gateway',
  110. status: 'live',
  111. auth: 'Bearer <sessionToken>',
  112. purpose: '签发项目隔离 OBS STS 临时凭证(skill-storage 第 3 级凭据)。',
  113. note: '实测匿名 POST 返回 200(权威端点)。入参 {token, projectId},返回 {accessKey, secretKey, securityToken, obsPath}。',
  114. },
  115. // ---------- 未上线(实测 404)——调用前必须探测 ----------
  116. storageUpload: {
  117. id: 'storageUpload',
  118. method: 'POST',
  119. url: 'https://server.fmode.cn/api/storage/upload',
  120. host: 'gateway',
  121. status: 'planned',
  122. auth: 'Bearer <fmodeApiToken>',
  123. purpose: '对象存储上传(规划中)。',
  124. note: '⚠️ 实测 404。当前上传走 obsutil 直传 OBS 或 deploySts 换 STS,不要依赖本端点。',
  125. },
  126. storageCredentials: {
  127. id: 'storageCredentials',
  128. method: 'POST',
  129. url: 'https://server.fmode.cn/api/storage/credentials',
  130. host: 'gateway',
  131. status: 'deprecated',
  132. auth: 'Bearer <sessionToken>',
  133. purpose: '(历史)sessionToken 直接换 OBS STS。',
  134. note:
  135. '⚠️ 从未上线(HEAD/GET 探测恒 404)。skill-storage 0.2.x 的「登录即可上传」' +
  136. '即因依赖本端点而成为「伪自举」事故。0.3.0 已降级为 --experimental-sts。',
  137. },
  138. imageGenerateLegacy: {
  139. id: 'imageGenerateLegacy',
  140. method: 'POST',
  141. url: 'https://server.fmode.cn/api/image/generate',
  142. host: 'gateway',
  143. status: 'planned',
  144. auth: 'Bearer <fmodeApiToken>',
  145. purpose: '(规划)网关侧图像生成。',
  146. note: '⚠️ 实测 404。图像生成请用 imageGenerate(/v1/images/generations)。',
  147. },
  148. visionAnalyze: {
  149. id: 'visionAnalyze',
  150. method: 'POST',
  151. url: 'https://server.fmode.cn/api/vision/analyze',
  152. host: 'gateway',
  153. status: 'planned',
  154. auth: 'Bearer <fmodeApiToken>',
  155. purpose: '(规划)网关侧视觉识别。',
  156. note: '⚠️ 实测 404。视觉识别请用 llmChat(多模态 messages)或宿主多模态模型优先。',
  157. },
  158. verifyCode: {
  159. id: 'verifyCode',
  160. method: 'POST',
  161. url: 'https://server.fmode.cn/api/fmode/verifycode',
  162. host: 'gateway',
  163. status: 'planned',
  164. auth: '无(公开)',
  165. purpose: '(规划)手机号验证码下发,用于一键开户。',
  166. note:
  167. '⚠️ 实测 404。当前一键凭证供给走「登录 FMODE Studio 取 sessionToken」路径,' +
  168. '见 lib/bootstrap.mjs 的 resolveSessionToken()。端点上线后本文件状态改 live 即可启用短信路径。',
  169. },
  170. };
  171. /** 便捷查询:按状态筛选端点 */
  172. export function endpointsByStatus(status) {
  173. return Object.values(ENDPOINTS).filter((e) => e.status === status);
  174. }
  175. // ============================================================
  176. // 凭据解析链(实测自 skill-listen / skill-vision / skill-storage 生产实现)
  177. // ============================================================
  178. /**
  179. * 标准 5 级凭据解析链。命中即用,全失败必须显式报错,绝不伪装成功。
  180. * 各级返回 { token, source, level } 或 null。
  181. */
  182. export const CREDENTIAL_CHAIN = [
  183. {
  184. level: 0,
  185. source: 'sessionToken 自举',
  186. detail:
  187. 'FMODE_SESSION_TOKEN 环境变量 或 ~/.fmode/config.json 的 sessionToken' +
  188. ' → POST /api/fmode/voc-skill/install-prompt → 提取 sk- token(仅内存持有)',
  189. endpoint: 'vocSkillBootstrap',
  190. },
  191. {
  192. level: 1,
  193. source: '环境变量',
  194. detail: 'FMODE_API_TOKEN',
  195. },
  196. {
  197. level: 2,
  198. source: '用户级 config',
  199. detail: '~/.fmode/config.json → fmodeApiToken / newapiToken',
  200. },
  201. {
  202. level: 3,
  203. source: '项目级 config',
  204. detail: '<cwd>/.fmode/config.json → fmodeApiToken / newapiToken',
  205. },
  206. {
  207. level: 4,
  208. source: 'Claude Code settings',
  209. detail:
  210. '~/.claude/settings.json(含 settings.local.json / 项目级 .claude/)' +
  211. ' 的 env.ANTHROPIC_AUTH_TOKEN —— fmode 的 newapi SK 默认就是它',
  212. },
  213. ];
  214. /** 校验规则:合法的 fmode token 形态 */
  215. export const TOKEN_RULES = {
  216. /** 必须以 sk- 开头 */
  217. prefix: 'sk-',
  218. /** 必须排除真正的 Anthropic 官方 key */
  219. exclude: 'sk-ant-',
  220. /** 若设置了 ANTHROPIC_BASE_URL,必须指向 fmode */
  221. baseUrlMustInclude: 'fmode',
  222. /** 从自举返回文本中提取 token 的正则(与 listen/vision 生产实现一致) */
  223. extractRe: /sk-(?!ant-)[A-Za-z0-9_-]{8,}/,
  224. };
  225. // ============================================================
  226. // 技能分类体系
  227. // ============================================================
  228. export const TIERS = {
  229. system: {
  230. key: 'system',
  231. label: '系统层 / Infrastructure',
  232. desc: '平台基础设施与 Agent 运行时治理:认知协同、任务编排、权限、进度、克隆备份。',
  233. },
  234. service: {
  235. key: 'service',
  236. label: '服务层 / Platform Services',
  237. desc: 'Fmode 基础服务封装:存储、图像、视觉、语音、音视频、企微网关。',
  238. },
  239. application: {
  240. key: 'application',
  241. label: '应用层 / Business Applications',
  242. desc: '面向业务场景的端到端技能:报告、课件、产品研发。',
  243. },
  244. };
  245. // ============================================================
  246. // ESM-first 四端矩阵
  247. // ============================================================
  248. export const RUNTIMES = {
  249. cli: {
  250. key: 'cli',
  251. label: 'CLI',
  252. entry: 'bin/<name>.mjs',
  253. usage: 'npx --yes <skill>@latest <command>',
  254. supported: true,
  255. },
  256. sdk: {
  257. key: 'sdk',
  258. label: 'SDK (Node ESM)',
  259. entry: 'lib/index.mjs',
  260. usage: "import { ... } from '<skill>'",
  261. supported: true,
  262. },
  263. browser: {
  264. key: 'browser',
  265. label: 'Browser',
  266. entry: 'browser/index.mjs',
  267. usage: '<script type="module" src="...">',
  268. supported: true,
  269. constraint: '禁止 import 任何 node: 内置模块;仅可用 fetch / Web Crypto / URL 等 Web 标准 API。',
  270. },
  271. server: {
  272. key: 'server',
  273. label: 'Server (CJS require)',
  274. entry: null,
  275. usage: "require('<skill>')",
  276. supported: false,
  277. constraint: 'ESM only —— 团队共识,不提供 CJS 入口。Node 侧请用 import() 动态导入。',
  278. },
  279. };
  280. /** 必需的文件清单(包结构模板) */
  281. export const REQUIRED_LAYOUT = [
  282. 'package.json',
  283. 'lib/index.mjs',
  284. 'bin/<name>.mjs',
  285. 'skills/<skill-name>/SKILL.md',
  286. 'README.md',
  287. 'LICENSE',
  288. 'skill-package-manifest.json',
  289. ];
  290. /** package.json 必须满足的字段约束 */
  291. export const PACKAGE_RULES = {
  292. requiredFields: ['name', 'version', 'description', 'type', 'main', 'exports', 'bin', 'files', 'license'],
  293. type: 'module',
  294. main: './lib/index.mjs',
  295. /** exports['.'] 必须同时提供 import 与 default */
  296. exportConditions: ['import', 'default'],
  297. /** files 白名单必须覆盖的目录 */
  298. filesMustInclude: ['lib/', 'bin/', 'skills/', 'README.md', 'LICENSE', 'skill-package-manifest.json'],
  299. /** 禁止出现的字段(ESM only 纪律) */
  300. forbiddenFields: ['require'],
  301. };