接入
SDK
TypeScript、Python、Go 和 Rust 客户端,通过同一套测试。用路径代替 id,流式上传和按范围下载,安全写入、局部修改、附加信息、搜索、变更、Webhook 和动作。
| 语言 | 包名 | 目录 | 运行环境 |
|---|---|---|---|
| TypeScript / JavaScript | @sidespace/sdk | sdks/typescript | Node 18+、Deno、Bun、浏览器(有 fetch 就行) |
| Python | sidespace | sdks/python | Python 3.9+,只用标准库 |
| Go | github.com/blendduck/sidespace/sdks/go | sdks/go | Go 1.22+,只用标准库 |
| Rust | sidespace | sdks/rust | 异步,基于 reqwest(rustls)和 tokio |
四个 SDK 都是 Apache-2.0 许可,文件相关的功能一致。它们还没有发布到 npm、PyPI、Go 模块代理和 crates.io:从仓库的检出目录使用,或者把文件夹拷进你的项目。
npm install ./sdks/typescriptpip install ./sdks/python# 在你的模块里,把导入路径指向检出目录
go mod edit -replace github.com/blendduck/sidespace/sdks/go=../sidespace/sdks/go
go get github.com/blendduck/sidespace/sdks/go# Cargo.toml
[dependencies]
sidespace = { path = "../sidespace/sdks/rust" }初始化
import { SideSpace } from "@sidespace/sdk";
const side = new SideSpace({ baseUrl: "https://side.example.com", token: process.env.SIDESPACE_TOKEN! });from sidespace import SideSpace
side = SideSpace("https://side.example.com", token="ssp_…") # 或者设好 SIDESPACE_URL 和 SIDESPACE_TOKEN 后直接 SideSpace()import sidespace "github.com/blendduck/sidespace/sdks/go"
side := sidespace.New("https://side.example.com", os.Getenv("SIDESPACE_TOKEN"))
hits, err := side.Search(ctx, "harbor night", nil)use sidespace::SideSpace;
let side = SideSpace::new("https://side.example.com", &std::env::var("SIDESPACE_TOKEN")?);
let hits = side.search("harbor night", &Default::default()).await?;空间可以用名字或 id,文件用路径。错误里带服务器返回的 status、code 和 message:TypeScript 和 Python 抛出 SideSpaceError;Go 和 Rust 的错误可以用 IsPreconditionFailed / is_precondition_failed() 判断安全写入是否冲突,并拿到当前版本。完整方法列表见各自目录的 README。
覆盖范围
| 功能 | TypeScript | Python |
|---|---|---|
| 空间和文件夹 | spaces()、space()、node()、list()、mkdir() | spaces()、space()、node()、list()、mkdir() |
| 上传和下载 | upload()、download()、带 range 的 open() | upload()(文件对象流式上传)、download()、带 range 的 open() |
| 版本 | versions()、restoreVersion() | versions()、restore_version() |
| 只改一部分 | edit()、appendText() | edit()、append_text() |
| 过去的样子 | node()、list()、open()、download() 带 asOf;rewind() | node()、list()、open() 带 as_of=;rewind() |
| 附加信息 | getSidecar()、putSidecar() | get_sidecar()、put_sidecar() |
| 搜索 | search(q, { space, type, mode }) | search(q, space=, type=, mode=) |
| 变更 | journal()、changes()(异步迭代器) | journal()、changes()(生成器) |
| Webhook | createWebhook(),@sidespace/sdk/webhooks 里的 verifyWebhookSignature() | create_webhook()、verify_webhook_signature() |
| 动作和流水线 | actions()、run()、startRun()、waitRun()、runs()、createPipeline() | actions()、run()、start_run()、wait_run()、runs()、create_pipeline() |
| 评论 | comments()、addComment()、editComment()、resolveComment() | comments()、add_comment()、edit_comment()、resolve_comment() |
| 分支 | fork()、forks()、forkChanges()、applyFork() | fork()、forks()、fork_changes()、apply_fork() |
| 插件 | installPlugin()、PluginRun | install_plugin()、PluginRun |
Go 和 Rust 覆盖同样的文件操作(空间、路径、流式上传和按范围下载、安全写入、按字节修改、版本、附加信息、搜索、评论、变更、动作和 Webhook),方法名按各自语言的习惯(UploadText、upload_text)。
API 参考里的其他接口,用 request() 一行就能调(Go 里是 Do):
const film = await side.space("Film A");
const presence = await side.request("GET", `/v1/spaces/${film.id}/presence`);film = side.space("Film A")
presence = side.request("GET", f"/v1/spaces/{film['id']}/presence")四个 SDK 一套测试
sdks/conformance/cases.json 是一份 34 步的清单(保存、按范围读、安全写入冲突、局部修改、恢复、文件夹、附加信息、评论、搜索和错误),每个 SDK 用自己的执行器对着真实服务器跑一遍,check.py 对比结果,所以四个 SDK 不会各走各的。四个都全部通过。想自己对着开发服务器检查一个:
SIDESPACE_SERVER=… SIDE_TOKEN=… SPACE=… python3 sdks/conformance/runner.py | python3 sdks/conformance/check.py --sdk python任何语言的新 SDK,跑通全部步骤就算完成。
完整示例
给 /dailies 里还没打分的每个视频,用你自己的模型打分(只读每个文件的前 8 MB),结果存进附加信息:
import { SideSpace, SideSpaceError } from "@sidespace/sdk";
const side = new SideSpace({ baseUrl: process.env.SIDESPACE_URL!, token: process.env.SIDESPACE_TOKEN! });
for (const file of await side.list("Film A", "/dailies")) {
if (!file.mime?.startsWith("video/")) continue;
const path = `/dailies/${file.name}`;
const done = await side.getSidecar("Film A", path, "x-mymodel").catch((e) => {
if (e instanceof SideSpaceError && e.status === 404) return null;
throw e;
});
if (done) continue;
const head = await side.open("Film A", path, { range: [0, 8 * 1024 * 1024 - 1] });
const score = await myModel(await head.arrayBuffer());
await side.putSidecar("Film A", path, "x-mymodel", { score, model: "mymodel-2" });
}from sidespace import SideSpace, SideSpaceError
side = SideSpace()
for file in side.list("Film A", "/dailies"):
if not (file.get("mime") or "").startswith("video/"):
continue
path = f"/dailies/{file['name']}"
try:
side.get_sidecar("Film A", path, "x-mymodel")
continue # 已经打过分
except SideSpaceError as e:
if e.status != 404:
raise
with side.open("Film A", path, range=(0, 8 * 1024 * 1024 - 1)) as r:
score = my_model(r.read())
side.put_sidecar("Film A", path, "x-mymodel", {"score": score, "model": "mymodel-2"})要随着新素材到来持续处理,把同样的代码放进 Webhook 处理函数,或者打包成插件再配一条流水线:两者都会记住进度并自动重试。