Skip to content

后端与模型 Provider 实现 ​

本页面向希望理解或扩展 osdk 下载能力的维护者。SDK 与模型共享网络、来源选择和 CAS 等基础设施,但它们采用两套不同的领域接口:SDK 实现 Backend,模型仓库实现 ModelProvider。模型不是伪装成 SDK 的特殊 backend。

SDK backend 合约 ​

Backend 是所有 SDK 的统一边界。实现需要提供规范 ID、可选别名、默认来源、探测 URL、远端版本列表、安装逻辑、可执行文件目录与名称;还可以覆盖版本解析、卸载、安装后处理、激活环境变量和惯用版本文件。默认解析器把 latest、前缀、范围或精确版本解析为 ToolVersion,并始终保留 -o/--opt 选项。

Ctx 把目录布局、目标平台、合并后的配置、HTTP client、CAS 和进度显示传给实现。归档型 backend 通常只负责生成 InstallPlan,然后交给共享安装流水线:

  1. 根据 pin、顺序或测速缓存得到 best-first 来源列表;
  2. 下载到共享缓存,失败时按来源顺序切换;
  3. 验证 SHA-256、SHA-512、SRI 或已认证 attestation;
  4. 安全解压到临时目录;
  5. 内容写入 BLAKE3 CAS,以 hardlink、reflink 或 copy 物化;
  6. 写 artifact receipt 和 .osdk-complete,使安装幂等且支持离线重装。

Registry 注册内置 backend 和别名,并动态识别 github:owner/repo、npm:<package>、严格的 http:https://...{version}...、cargo:<crate-or-https-url> 与 go:<module-or-command-path> ID。它还从用户配置目录和数据目录的 plugins/*.toml 加载声明式 backend;重复 ID 或别名会直接报错,外部定义不能覆盖内置实现。

这些动态命名空间对 osdk 自有安装共用选项身份合约。解析或安装前,osdk 把受支持的 公开选项投影成 规范 map,拒绝未知公开 key,排除内部 __osdk_* lock 重放 metadata,并对 backend ID 与 规范选项计算与顺序无关、带 domain separation 的 BLAKE3 b3-v2: 身份。该身份覆盖 tool、 精确 version、platform、scope、规范 material_options、dependencies 与 materials。 .osdk-install.json schema 1 在嵌套 identity 中保存这些字段及 install_id,并把指纹用于 物理安装根,因此相同 backend/version 的多个身份可以共存。复用、activation、shim 执行、 where、uninstall 与 reshim 都要求配置精确匹配,绝不会回退到其他 fingerprint。旧 .osdk-tool.json 无论 schema 1 还是 2,都只用于遗留识别,不能授权复用或执行。项目管理的 npm 包不属于该 osdk 自有安装身份。

内置 backend 矩阵 ​

Backend解析与获取方式完整性与安装语义重要特性或限制
node (nodejs)合并官方、npmmirror、TUNA、USTC 的可达 Node indexSHASUMS256.txt;共享归档流水线逐源验证可选 arch 与 corepack;Corepack 是安装后动作
npmnpm registry packument/tarballnpm SRI,强制校验;生成 npm/npx launcher独立于 Node 版本安装,但运行时仍需要活动 Node
pnpm完整 pnpm JavaScript distributionnpm SRI;osdk 生成 Node launcher自动加入受管 Node;按 major 设置 pnpm store 变量
yarn1.x 用 yarn,2+ 用 @yarnpkg/cli-distnpm SRI;生成 Node launcher原生管理 Classic 与 Berry,不委托 Corepack
go (golang)go.dev JSON index;镜像可复用官方 indexindex 中的 SHA-256;归档流水线激活时设置 GOROOT
python (py, cpython)内置 PBS release index;按 source 合并同一 tag 的 SHA256SUMS每个 release 的 SHA-256;逐源归档回退支持 CPython、PyPy、GraalPy、Pyodide 与 variant;历史版本可用 tag 固定
java (jdk, openjdk)逐个查询 Foojay-compatible source,Temurin 为默认 distribution逐源 checksum detail;JDK/JRE 归档distribution、package-type=jdk|jre;激活时设置 JAVA_HOME
maven (mvn)内置单版本 release;有效 source 按探测结果排序固定 SHA-512;逐 source 验证回退当前 catalog 只包含一个版本
gradle排序后的 Gradle 版本 index;绝对 distribution URL 重映射到各 sourceindex SHA-256;逐 source 验证回退支持 index 中的正式版与显式预览版
kotlin (kotlinc)内置单版本 GitHub release;有效 source 按探测结果排序固定 SHA-256;逐 source 验证回退当前 catalog 只包含一个版本
rust (rustup)rustup channel/version;官方、rsproxy、TUNArustup-init SHA-256;随后对每个排序源运行隔离 rustup,完整命令成功才选中toolchain 不走归档 CAS;支持 profile、components、targets,设置隔离的 RUSTUP_HOME/CARGO_HOME
denodeno packument + @deno/<platform>npm SRI平台包;设置 DENO_DIR
bunbun packument + @oven/bun-<platform>npm SRI平台包;设置 BUN_INSTALL_CACHE_DIR
zig排序后的 index.json;绝对 tarball URL 重映射到各 source来自索引条目的 SHA-256;逐 source 验证回退平台键使用 LLVM CPU token;归档名从索引读取而非拼接(0.14 期间命名布局发生过变化);master 暴露为预发布;设置 ZIG_GLOBAL_CACHE_DIR
npm:<package>npm packument;隔离安装使用受管 npm 子进程,项目/全局 use 可规划 npm 或 pnpm原生 lock 携带传递 integrity;默认禁脚本;.osdk-install.json schema 1 在指纹化 osdk 自有隔离/全局根中绑定 installer/build 身份动态发现 .bin;自动加入受管 Node;lock schema 4 记录 scope、installer、可选原生 lock 身份与公开选项
cargo:<crate-or-https-url>crates.io 兼容 metadata 与配套 sparse index,或规范 HTTPS Git URLRegistry 在选源时额外探测精确 .crate 下载端点;依赖精确 osdk 受管 Rust;隔离执行 cargo-binstall/cargo installRegistry 精确/latest/前缀,或 Git latest/tag/branch/完整 revision;schema 4 记录 runtime、replay 分类与最终 Registry source
go:<module-or-command-path>Go proxy 的 @latest、版本列表与精确 .info metadata,并发现最长 module root依赖精确 osdk 受管 Go;全新解析把排序源组成 `分隔的原生GOPROXY,隔离执行一次 go install`
github:owner/repoGitHub API,限流时回退 Atom/公开 release 页面;也支持静态 catalogchecksum、可选 minisign、GitHub artifact attestation;.osdk-install.json schema 1 在指纹化根中绑定 asset/layout/material 身份自动选择 host asset;支持归档或裸二进制;复杂命名可用 regex/template/bin/rename/strip 规则

上述实现位于 backend/。npm 系列共用 npm.rs 的 packument、版本与 SRI 解析。通用来源排序位于 source/select.rs。 动态 npm backend 的项目/全局/隔离安装、缓存、metadata-only lock、旧 lock schema 2 sidecar 兼容与 shim 边界见 npm 开发工具实现。 严格 selector、精确 Rust 绑定、受控 provider fallback 与原生发布见 Cargo 开发工具实现。 module-root 发现、proxy 路由、构建环境策略、runtime 绑定与重放边界见 Go 开发工具实现。

声明式与 GitHub backend ​

DeclarativeBackend 是受限的 schema 1 TOML 扩展点。它支持静态或 URL 版本列表、平台模板变量、tar.gz/tar.xz/tar.zst/zip/7z、固定或远端 checksum、strip_root、bin 路径和惯用版本文件。平台模板同时提供 osdk 短 token {arch} 与 LLVM target triple 的 CPU 部分 {arch_llvm},因为编译器与工具链归档通常以 x86_64/aarch64 而非 x64/arm64 发布;后者复用 Arch::llvm_token,而不是引入第二套命名表。定义文件最大 1 MiB,远端版本最多 10,000 个,并严格验证 URL、文件名、相对路径和 checksum。它刻意不执行 hook 或任意命令;所有安装必须经过共享验证与 CAS 流水线。 项目 lock 提供通用 artifact receipt 时,backend 会优先使用其中记录的 URL、文件名、 checksum 与子目录,再考虑当前模板。因此声明式工具与内置归档 backend 具有相同的 无 metadata 离线重装契约。 可选的 [env] 表允许定义描述其工具链所需的环境,这正是编译器能被用起来的前提: 构建系统通过 CC、SYSROOT 等变量而不是 PATH 定位交叉编译器。取值只从 {install_path}、{version}、{id} 渲染,若渲染后仍残留占位符,exec_env 会 失败关闭。变量名按常规环境变量标识符校验;PATH 与动态加载器变量 (LD_PRELOAD、LD_LIBRARY_PATH、DYLD_INSERT_LIBRARIES、DYLD_LIBRARY_PATH) 不区分大小写地保留,绝对路径、.. 和控制字符在解析阶段即被拒绝,因此数据式定义 无法把子进程指向安装根之外。 可选的 [[archive.overrides]] 列表之所以存在,是因为单一模板无法表达"上游改名"。 LLVM 是塑造该设计的真实案例:Linux x86-64 在 19.1.0 从 clang+llvm-<version>-x86_64-linux-gnu-ubuntu-18.04 改为 LLVM-<version>-Linux-X64, 而 Windows 保持旧命名,且其中嵌入的发行版号无法从任何平台信息推导——也就是说改名是 按平台发生且部分不可预测的。因此每条 override 以 versions(semver::VersionReq) 与 os、arch、libc 联合匹配,并且替换整个字段(url、file、kind、 strip_root、checksum)而不是片段,未设置的字段回退到 [archive]。解析时按各条 override 约束的条件数量排序,因此结果与声明顺序无关;同等具体则报错而不是按顺序取其 一,因为顺序很容易被无意改动。arch 条件同时接受 {arch} 与 {arch_llvm} 两种写法以 避免第二套词汇;非 semver 的版本只是匹配不上需求,而不会中断安装。凡是无需具体平台即 可检查的问题——空条件集、什么都不替换的条目、非法需求、无法随版本变化的模板——都在解析 期拒绝,使损坏的定义在加载时失败,而不是在恰好匹配到它的那台机器上失败。 archive.checksum.attestation 之所以存在,是因为有些上游根本不发布摘要文件:LLVM 带 .sig、从 19.1.0 起带 .jsonl sigstore bundle,但没有 .sha256。bundle 的 in-toto subject 本身就含该制品的 SHA-256,因此它同时是签名与摘要来源;而 verify_github_attestation 早已实现了 gh attestation verify --repo <owner>/<repo> 所做的检查,且不依赖 gh CLI。流水线本来就把 attestation 证据当作 authenticated checksum,因此 backend 的 checksum 返回 Ok(None)——摘要在字节存在之前确实未知——转而传入一个 GithubAttestation;归档仍然不会在未验证的情况下被解压。策略固定为 Required 而不是 继承 settings.attestations(默认 off):当 attestation 就是摘要来源时,顺从全局 off 会装上毫无完整性证据的归档,因此由 attestation_request 自行设定策略,并有测试 断言它不跟随全局默认值。repo 会成为 GitHubWorkflowRepository 证书身份策略,因此按 owner/repo 解析校验而非原样插值。覆盖范围并不完整、也不能假设:只有上游启用之后由工 作流构建的制品才有 attestation,对 LLVM 即 19.1.0 及之后且每个 release 只有部分制品, 因此"按版本与平台切换摘要来源"是 [[archive.overrides]] 的正常用例而非边缘情况。

GithubBackend 是运行时创建的命名空间 backend。它最多分页读取 1,000 个 release,忽略 draft,并按预发布策略过滤;随后按 OS、架构和 libc 为 asset 评分。显式规则可解决非标准 asset 名称。在线且启用签名校验时,可用的可信 minisign checksum manifest 会覆盖预载的静态摘要;否则使用静态摘要,再回退到普通 sidecar/shared checksum。配置的 GitHub attestation 策略独立应用。GitHub API、网页、Raw、release asset 和 attestation URL 都通过同一组规范化来源候选,但 token 只发给官方 API host。 其受支持的 asset、平台、catalog 摘要、rename、bin 与 strip 选项会先作为公开身份输入 校验,再写入 schema 1 动态安装 manifest。catalog-url 可用于获取,但会被刻意排除;必填的 catalog-sha256 在不把 catalog 位置写入动态 inventory 时标识内容,且含 userinfo、查询参数或 fragment 的 HTTP(S) catalog URL 会被拒绝。因此,单有完成 标记不能复用由不同选项或旧 inventory 生成的 GitHub 安装;锁定重放还会核对已持久化 artifact receipt 的文件名、checksum 与子目录。身份不匹配时必须先卸载再重新安装该版本。 inventory 会先于完成标记发布,因此中断的收尾过程不会被误认为可复用安装。

模型是独立且 provider-specific 的 ​

模型引用必须带 provider:仓库型来源使用 hf:owner/repo@revision 或 ms:owner/repo@revision,Civitai 使用精确的 civitai:model-id@model-version-id。ProviderId 与 ModelRef 将 provider 作为身份的一部分;ModelProvider 只统一输出“已解析 revision + 文件 manifest”,不假设服务 API 相同。

语义Hugging FaceModelScopeCivitai
实现huggingface.rsmodelscope.rscivitai.rs
元数据 API/api/models/{repo}/revision/{revision}?blobs=true/api/v1/models/{repo}/repo/files?Revision=...&Recursive=true/api/v1/model-versions/{version-id}
文件 URL/{repo}/resolve/{commit}/{path}/api/v1/models/{repo}/repo?Revision=...&FilePath=...API 返回的 downloadUrl,可跳转到 CDN
不可变 revision服务返回的 commit SHA请求 revision 加排序后的 path/size/SHA-256 manifest 的 BLAKE3 摘要精确 model version ID
文件选择与摘要LFS 文件带 SHA-256;普通 blob 缺失时下载后计算API 必须为每个文件返回合法 SHA-256在 Model 权重中按 SafeTensor、primary、响应顺序选择一个;必须有 SHA-256,路径规范为 loras/<filename>
tokenOSDK_HF_TOKEN → HF_TOKEN → HUGGING_FACE_HUB_TOKEN;BearerOSDK_MODELSCOPE_TOKEN → MODELSCOPE_API_TOKEN;Bearer + m_session_id cookieOSDK_CIVITAI_TOKEN → CIVITAI_API_TOKEN → CIVITAI_TOKEN;Bearer,跨源跳转移除
默认 endpointhttps://huggingface.co优先 https://modelscope.cn,回退 https://www.modelscope.aihttps://civitai.com 与 https://civitai.red 两个官方入口

三个 provider 的元数据结构、下载 URL、认证和不可变身份不同;实现会拒绝解析属于另一 provider 的 ModelRef。自动测速和失败切换只在同一 provider 的 endpoint 集合内进行。Civitai 将 .com(SFW 入口)与 .red(完整目录入口)都视为可携带凭据的官方 source,保留旧 official ID 并新增 official-red;两者写 lock 时统一归一到 .com,但只有目标版本实际可见的入口才会通过探测。Civitai provider 只接收 ogen 等上层已经选定的精确 ID,不承担搜索、排序或 trigger word 匹配。

模型解析、下载与物化 ​

osdk model sync [name] 的实现入口在 commands/runtimes.rs,核心流程在 model/pull.rs:

  1. 解析显式 --endpoint 或 provider 环境变量;否则使用 provider 自己的默认/自定义来源。
  2. 在 auto 模式下,model/source.rs 对真实目标先取 manifest,再对最小的非空文件执行最多 64 KiB 的 Range 请求;结果按 provider、repo、revision 和来源配置缓存。
  3. provider 解析远端 manifest;--include/--exclude glob 选择文件。variant 与语义元数据 kind/family/derived_from 一起进入快照身份;Civitai 未显式声明 kind 时统一取 lora,其余 provider 不猜测。family 与 derived_from 由调用方提供并经过长度、trim 和控制字符校验。
  4. 文件按 settings.jobs 并发、可续传下载到 provider/repository/revision 隔离的 cache;校验声明 size 和 SHA-256。
  5. ModelStore 再次校验文件,写入共享 CAS,在隐藏临时目录完成 snapshot 后 rename 到 <models>/<logical-name>/snapshots/<snapshot-key>,再以临时文件加 rename 更新 current.json;这些 rename 没有跨平台替换原子性或 durability 保证。随后把 <models>/<logical-name>/current 这个目录链接重指向新快照:快照目录名由内容哈希决定(包含文件选择),因此换 --include 就会换目录,外部配置里写死的路径会静默失效,而 ComfyUI、llama.cpp、vLLM 都只接受一个会被保存下来的路径。Windows 上用 junction 而非符号链接,因为符号链接需要 Developer Mode 或提权,junction 不需要;重指向时如果 current 位置是真实目录会显式报错,不会静默删除用户数据。链接创建失败只记 warning 不中断发布——此时快照与 current.json 已经落盘,为一个链接丢弃整次下载并不合理,model path(不带 --stable)仍可从 current.json 作答。
  6. 把 provider、repo、requested/resolved revision、endpoint、variant、kind/family/derived_from 以及每个文件的 size/SHA-256 写入 osdk.lock 的顶层 [models];同样的语义元数据已写入 .osdk-model.json,机器 JSON 直接从 manifest 回读。token 和短期下载 URL 不落盘。

本地导入走 model/local.rs,不进入 ModelProvider:它用 symlink_metadata 逐级遍历,进入子目录前拒绝 symlink、Windows reparse point/junction 和特殊文件;路径分隔符归一为 / 后拒绝绝对路径、空段、./..、冒号与控制字符。每个普通文件先计算 SHA-256,排序后的 path/size/SHA-256 生成 local-<24 hex> revision,再由同一个 ModelStore::publish 复制进 CAS 并原子发布。ProviderId::Local 只用于 manifest/机器输出,ModelRef::parse、source/provider/env 路径都不接受它。

本地 snapshot 的 manifest 不保存输入绝对路径;lock 读写两端都拒绝 provider=local,因为另一台机器无法按任意本地路径复现。CLI 在发布前拒绝同名声明/lock 和 hf-cache view;成功后可渲染 ComfyUI view,重复导入会按持久化 view state 重渲染既有视图。--json 复用 schema 1 ModelShowOutput,stdout 不混入人类报告。

model list/show/path/verify/remove 操作当前逻辑名。verify 同时检查 CAS BLAKE3 hash 和 SHA-256;remove 删除该逻辑名的全部 snapshot,再以 SDK installs 与 models 为 root 做 CAS GC。离线 sync 仍需已有 provider metadata cache 和逐文件 download cache,之后可重新物化已删除的 snapshot。

机器协议集中在 model_output.rs,与快照、lock 和 view state 的磁盘 schema 分离。import --json、list/show/path/verify 及只读 view 命令输出单个 schema 1 JSON,sync --jsonl 通过统一 emitter 输出逐行事件;人类模式仍使用原文案。所有机器 stdout 写入都经过同一序列化入口,view reconcile 在 JSONL 模式下仍执行但不打印普通报告,错误继续由顶层写 stderr 并返回非零。协议中的绝对路径是本机位置,保留原生分隔符;manifest 相对路径来自跨平台产物,保持 /。

声明式 [models]、视图与信任分类 ​

model use 会受管写入项目 osdk.toml 的 [models.<name>] 声明,结构在 config/mod.rs 的 ModelDeclaration(deny_unknown_fields,与 tasks 一样在 install feature 之后, shim 的依赖图不带它)。每个声明含 source/include/exclude/variant/kind/family/derived_from/when 与 views: consumer -> ModelViewDeclaration{profile, map}。sync [name] 时从合并后的 Config.models 取这份声明:拉取后把视图经 locked_views_from_declaration 写入 lock, 并调用 model_view::reconcile_declared_views 立即渲染。

lock 侧 LockedModel.views 是 consumer -> LockedModelView{profile, map},#[serde(default, skip_serializing_if)], 空时不落盘;schema 仍为 4,旧二进制读到未知字段会忽略(已用旧版二进制实测)。 set_model_views 提供与写入对称的读取更新路径,避免「只写不读」(AGENTS.md 记过 npm 的同一族坑)。model sync 复现快照后同样调用 reconcile,所以视图声明在另一台 机器上靠 sync 就能重建,不必重跑 model view add。

信任分类在 trust.rs: 模型配置不属于任何作用域,任何键都不触发信任,包括 endpoint。 模型字节是 内容,osdk 不执行它们,下载仍按锁定摘要校验,换端点无法把内容拉取变成代码 执行。模型键也不进 normalized hash,所以怎么改模型条目都不会重新提示或使记录 失效。affects_tool_dispatch 对任何 models.. key 返回 false。

Provider 环境持久化 ​

osdk model env enable [provider] [--force] 只把 sources.<provider>.env 和可选 env_force 写入用户级配置,项目配置不能覆盖这两个开关。激活逻辑在 model/env.rs:

  • Hugging Face:HF_ENDPOINT、HF_HOME、HF_HUB_CACHE、HF_XET_CACHE、HF_ASSETS_CACHE;osdk 离线模式还设置官方支持的 HF_HUB_OFFLINE=1。
  • ModelScope:MODELSCOPE_ENDPOINT、MODELSCOPE_CACHE;没有虚构 MODELSCOPE_OFFLINE。
  • Civitai 没有标准下游环境适配协议;model env civitai 会在写配置前拒绝。
  • 默认保留用户已设置的变量;--force 才覆盖。shell activation 会记录原值,disable/deactivate 时恢复。
  • 自定义 endpoint 默认 forward_credentials=false。当 osdk 管理该 endpoint 时,会清空 provider token,并把 home 指向隔离的 anonymous 目录,避免本地登录 cookie/token 泄漏。只有官方 endpoint 或用户明确 --forward-credentials 才允许下载请求携带凭据。token 本身从不写入 osdk 配置。

边界与注意事项 ​

  • SDK lock 按平台保存;model lock 位于顶层,因为模型文件通常与平台无关。模型 variant 是用户标签,不会自动推导量化格式,也不会改变文件选择。
  • 在线 provider 身份贯穿引用、metadata/ranking/download cache、snapshot key、manifest 与 lock;本地 import 则以 provider=local 留在 manifest/机器输出中,明确不进入 source 或 lock。但顶层 models map 和本地 current.json 以用户提供的逻辑名为键。用同一逻辑名拉取另一 provider 会切换该名字的 current snapshot,并覆盖 lock 中该名字的记录。
  • Hugging Face 非 LFS blob 可以没有远端 SHA-256;osdk 会在下载后计算并锁定,但这不等同于服务端提供的独立摘要。ModelScope 要求 API manifest 给出合法 SHA-256;Civitai 也要求所选权重提供合法 SHA-256。
  • metadata 在线请求失败时可以回退到 stale cache。自定义 endpoint 必须实现所选 provider 的真实 API;仅兼容文件 host 或替换域名并不足够。
  • GitHub backend 的自动 asset 评分是启发式;命名含糊或一个 release 含多个相似产物时应使用显式 asset 规则或可信静态 catalog。
  • Rust 是委托型 backend,toolchain 由隔离 rustup 管理,不享受普通 archive backend 的逐文件 CAS 去重。Maven、Gradle、Kotlin 当前使用内置单版本 catalog,并非完整远端版本索引。

基于 MIT 许可发布