【免费下载链接】OpenShell
OpenShell is the safe, private runtime for autonomous AI agents.
本指南以sdk/go/README.md为骨架,结合仓库源码,系统讲解 OpenShell Go SDK 的设计动机、客户端架构、认证体系、沙箱生命周期管理与命令执行等核心能力。读完本文,你将掌握如何用与 Kubernetes client-go 同构的 Go 客户端连接 OpenShell 网关、创建并等待沙箱就绪、执行/流式/交互式命令、处理分页与令牌自动刷新,以及如何借助内置 Fake 客户端进行无网关测试。
为什么需要 Go SDK
OpenShell 是面向自治 AI Agent 的安全、私有运行时。如果你要构建 Operator、Controller 或任何将 OpenShell 资源当作原生 Kubernetes 对象来管理的自动化程序,就需要一个 Go 客户端——Go 正是 Kubernetes 生态的语言。
因此,这个 SDK 刻意对标k8s.io/client-go(每个 Kubernetes Operator 开发者都熟悉的标准客户端库),模式高度一致(见 client.go 中的ClientInterface定义):
- 按资源划分的类型化子客户端:
client.Sandboxes()、client.Providers()、client.Exec(),就像clientset.CoreV1().Pods()一样; - 领域类型与线上格式分离:干净的 Go 结构体集中在
types包,公共 API 不泄露 proto 细节(类似k8s.io/api),源码中的类型别名集中定义在 types_reexport.go; - Watch 原语:基于 channel 的 Watcher,提供
ResultChan()与Stop(),与 client-go 的watch.Interface行为一致; - 函数式选项:列表过滤、分页、watch 配置都通过变参 option 传递;每个入口点对 nil option 静默忽略,所以条件性构造的 option 切片可以原样传入,无需预先过滤 nil;
- 可组合的带令牌刷新的认证:封装
oauth2.TokenSource实现令牌自动缓存与合并刷新(合并并发刷新调用),对应 client-go 的cachingTokenSource模式; - Fake 客户端:完整客户端接口的内存实现(对标
k8s.io/client-go/kubernetes/fake),无需真实网关即可测试 Operator。
架构总览:Client 子客户端树
从 client.go 的接口定义可以看到 SDK 顶层形态。NewClient创建 gRPC 连接后,把全部子客户端一次性接线完毕(client.go):
Client ├── Sandboxes() → SandboxInterface (create, get, list, delete, watch, wait, logs) ├── SandboxTemplates() → SandboxTemplateInterface (可复用工作负载模板) ├── Exec() → ExecInterface (run, stream, interactive) ├── Files() → FileInterface (upload, download) ├── Health() → HealthInterface (health check, gateway info, current user) ├── Services() → ServiceInterface (expose, get, list, delete) ├── Providers() → ProviderInterface (CRUD + ensure) │ ├── Profiles() → ProfileInterface (list, get, import, update, lint, delete) │ └── Refresh() → RefreshInterface (configure, status, rotate, delete) ├── Workspaces() → WorkspaceInterface (create, get, list, delete, members) ├── Config() → ConfigInterface (网关/沙箱配置读写) ├── Policy() → PolicyInterface (draft review, approve, reject, merge, status) ├── SSH() → SSHInterface (SSH 会话) └── TCP() → TCPInterface (TCP 端口转发)所有领域类型都位于openshell/v1/types/包,proto 到 Go 的转换发生在内部 converter 层(sdk/go/openshell/v1/internal/converter/),公共 API 通过类型别名保持单包导入。Close()是幂等的(内部用sync.Once保护),可安全多次调用(client.go)。
快速开始:连接、建沙箱、跑命令
最小可运行示例(也是 README.md 的核心示例)如下:
import v1 "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1" // Connect to a gateway client, err := v1.NewClient(v1.Config{ Address: "gateway.example.com:443", Auth: v1.StaticToken("my-token"), }) if err != nil { log.Fatal(err) } defer client.Close() // Create a sandbox and wait until it's ready sandbox, err := client.Sandboxes().Create(ctx, "default", "my-sandbox", &v1.SandboxSpec{ Template: &v1.SandboxTemplate{Image: "python:3.12"}, }, nil) if err != nil { log.Fatal(err) } sandbox, err = client.Sandboxes().WaitReady(ctx, "default", sandbox.Name) if err != nil { log.Fatal(err) } // Run a command result, err := client.Exec().Run(ctx, "default", sandbox.Name, []string{"python3", "-c", "print('hello from sandbox')"}, v1.ExecOptions{}, ) if err != nil { log.Fatal(err) } fmt.Println(string(result.Stdout))几个值得注意的底层行为:
NewClient要求cfg.Address非空,否则返回ErrorInvalidArgument类型的StatusError;cfg.Auth为 nil 时自动降级为NoAuth()(client.go)。Config还支持TLS(CertFile/KeyFile/CAFile/Insecure)、Timeout、RetryPolicy、Logger等字段。SandboxInterface的完整能力见 sandbox.go:除Create/Get/Delete外,还有Start/Stop、WaitReady/WaitStopped、Watch、GetLogs,以及AttachProvider/DetachProvider和ListProviders等 provider 挂载操作。ExecInterface提供三种执行模式(exec.go):Run(收集型,返回完整ExecResult)、Stream(逐块输出迭代器)、Interactive(双向 I/O 的 PTY 会话)。
分页:惰性 Pager
List 方法返回一个惰性分页器,不会立即发出请求。NextPage用传入的 context 拉取一页,ListAll显式耗尽所有页。PageSize是每页请求上限,PageToken用于从之前的查询位置续读:
pager, err := client.Sandboxes().List("default", v1.ListOptions{PageSize: 100}) if err != nil { log.Fatal(err) } for { page, err := pager.NextPage(ctx) if err != nil { log.Fatal(err) } if page == nil { break } for _, sandbox := range page.Items { fmt.Println(sandbox.Name) } }认证体系:从静态令牌到 OIDC 与边缘代理
SDK 的认证通过实现 gRPCcredentials.PerRPCCredentials接口的AuthProvider抽象,可任意组合。基础实现见 auth.go:StaticToken发送Authorization: Bearer <token>,NoAuth不发送任何凭证。
自动令牌刷新:RefreshableToken
针对 OIDC 网关,RefreshableToken包装任意oauth2.TokenSource,提供自动缓存与合并刷新。其实现位于 auth_refresh.go:
import "golang.org/x/oauth2" tokenSource := oauth2Config.TokenSource(ctx, initialToken) auth, err := v1.RefreshableToken(tokenSource, v1.WithLeeway(30*time.Second), ) if err != nil { log.Fatal(err) } client, err := v1.NewClient(v1.Config{ Address: "gateway.example.com:443", Auth: auth, }) if err != nil { log.Fatal(err) } defer client.Close()关键实现细节(全部可在源码中验证):
- Leeway:
WithLeeway设置在令牌到期前多少时间触发主动刷新,默认 10 秒(defaultLeeway = 10 * time.Second);负值会被截为 0。 - 合并刷新:并发调用者共享同一次刷新,通过
singleflight.Group的DoChan实现(auth_refresh.go),避免令牌端点雪崩。 - 失败降级与退避:令牌源刷新失败时,SDK 会回退到已缓存令牌并记录警告日志(需用
WithLogger配置 logger,否则警告静默丢弃);同时进入指数退避(1s、2s、4s……上限 30s),避免放大令牌端点故障。退避期间若无缓存令牌,会直接返回错误。 - 日志接口:
WithLogger(l types.Logger)可注入结构化 logger,用于输出刷新失败警告与隧道事件。
边缘代理:extra headers、Cloudflare Access 与 WebSocket 隧道
当网关位于零信任反向代理之后时,用WithExtraHeaders在标准认证之外附加代理专用请求头:
base := v1.StaticToken("my-gateway-token") auth, err := v1.WithExtraHeaders(base, map[string]string{ "x-proxy-auth": "proxy-secret", }) if err != nil { log.Fatal(err) } client, err := v1.NewClient(v1.Config{ Address: "gateway.example.com:443", Auth: auth, })对于 Cloudflare Access,edge包提供便捷构造器(cloudflare.go),它会设置cf-access-jwt-assertion头与CF_Authorizationcookie:
import "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/edge" auth, err := edge.CloudflareAccess(base, os.Getenv("CF_ACCESS_TOKEN"))对于拒绝 HTTP/2 的边缘代理,可以使用 WebSocket 隧道。edge.NewTunnelProxy(tunnel.go)在127.0.0.1上监听一个临时端口,每个被接受的连接都通过携带边缘令牌的 WebSocket 握手拨号到网关,并双向拷贝数据。gRPC 客户端改拨tunnel.Addr()即可:
tunnel, err := edge.NewTunnelProxy( "wss://gateway.example.com/ws", os.Getenv("CF_ACCESS_TOKEN"), ) if err != nil { log.Fatal(err) } defer tunnel.Close() client, err := v1.NewClient(v1.Config{ Address: tunnel.Addr(), Auth: v1.StaticToken("my-token"), TLS: &v1.TLSConfig{Insecure: true}, // local tunnel, no TLS })隧道实现细节:ws://仅允许 loopback 主机,携带边缘令牌的远程连接必须使用wss://;每条桥接连接设置 64 MiB 的读取上限以容纳 gRPC 帧;Close()默认等待在途连接最多 5 秒排空(WithCloseTimeout可调),超时后强制关闭。
OIDC 登录与可再生服务认证
oidc包提供网关感知的 OIDC 认证,涵盖浏览器、键盘、设备码和客户端凭证四种流程:
import "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/oidc" // Gateway-aware login: reads OIDC config from gateway metadata token, err := oidc.Login(ctx, "my-gateway") if err != nil { log.Fatal(err) } // Use the token with the SDK client client, err := v1.NewClient(v1.Config{ Address: "gateway.example.com:443", Auth: v1.StaticToken(token.AccessToken), })无头环境使用设备码流程:
token, err := oidc.DeviceLogin(ctx, oidc.WithIssuer("https://auth.example.com"), oidc.WithClientID("my-app"), )一次性服务账号令牌交换用ClientCredentials;长驻 SDK 客户端则挂载可再生、仅内存的认证提供者:
auth, err := oidc.NewClientCredentialsAuth( oidc.WithGateway("my-gateway"), oidc.WithClientSecretProvider(func(context.Context) (string, error) { return os.Getenv("OPENSHELL_OIDC_CLIENT_SECRET"), nil }), ) if err != nil { log.Fatal(err) } client, err := v1.NewClient(v1.Config{ Address: "gateway.example.com:443", Auth: auth, TLS: tlsConfig, }) if err != nil { log.Fatal(err) } defer client.Close()除WithGateway(从网关元数据自动读取 OIDC 配置)外,也可显式指定WithIssuer、WithClientID、WithScopes、WithAudience。该提供者会在凭证到期前重复授权,且绝不把 client secret 或 access token 写入磁盘。完整选项与流程见 oidc 包源码。
从 CLI 配置自动接线:gateway 包
gateway包能直接加载 CLI 侧已保存的网关配置并自动装配认证(gateway.go)。gateway.NewClient(name)解析网关目录、读取metadata.json、惰性加载令牌,并按认证模式映射到对应的 SDKAuthProvider:AuthModeNone/AuthModePlaintext映射到NoAuth(),AuthModeCloudflareJWT映射到惰性边缘认证(令牌文件缺失时NewClient依然成功,错误在首次认证时才暴露),AuthModeOIDC映射到RefreshableToken(磁盘令牌源)。name为空时使用活动网关(由openshell gateway use设置);LoadConfig则只解析配置不建连。ListGateways枚举用户与系统目录下所有网关,同名时用户网关优先。调用方也可用WithAuth()/WithTLS()等ClientOption覆盖默认行为。
交互式会话控制与退出码语义
SDK 会话实现可选的InteractiveSessionControl接口(exec.go),在原有InteractiveSession(Read/Write/Resize/ExitCode/Close)之外新增CloseWrite()(关闭 stdin、保留输出)与Cancel()(中止 RPC):
CloseInteractiveInput(session):对支持CloseWrite的会话关闭输入;对不支持(如自定义 mock)的会话返回ErrorUnimplemented并保持会话打开。CancelInteractive(session):优先调用Cancel(),否则回退到Close()。- SDK 的关闭/取消操作是幂等的;输入关闭后的写入与 resize 返回
io.ErrClosedPipe。 ExitCode()会等待最终 gRPC 状态,返回观察到的进程退出码及随后的流错误——退出事件本身并不代表流成功结束,需要并发地Read排空输出并同时等待ExitCode()。
功能一览
| 功能 | 接口 | 源码入口 |
|---|---|---|
| 沙箱生命周期(create/get/list/delete/watch/wait/logs) | SandboxInterface | sandbox.go |
| 命令执行(收集、流式、交互式 PTY) | ExecInterface | exec.go |
| Provider 管理(CRUD + 幂等 ensure) | ProviderInterface | provider.go |
| Provider 配置(list/import/lint/update) | ProfileInterface | profile.go |
| 凭证刷新(configure/rotate/status) | RefreshInterface | refresh.go |
| 服务暴露(expose/list/delete) | ServiceInterface | service.go |
| 文件传输(受传输能力门控) | FileInterface | file.go |
| 策略管理(draft review/approve/reject/merge/global) | PolicyInterface | policy.go |
| 沙箱日志(流式获取,支持行数/时间/来源/级别过滤) | SandboxInterface | sandbox.go |
| 工作区管理(create/get/list/delete/members) | WorkspaceInterface | workspace.go |
| 沙箱 provider 挂载(attach/detach/list) | SandboxInterface | sandbox.go |
| 网关信息与当前用户身份 | HealthInterface | health.go |
| SSH 隧道与 TCP 转发 | SSHInterface,TCPInterface | ssh.go, tcp.go |
| 认证:静态令牌、可刷新令牌(oauth2.TokenSource) | AuthProvider | auth.go, auth_refresh.go |
| 边缘认证:extra headers、Cloudflare Access、WebSocket 隧道 | AuthProvider,edge.TunnelProxy | cloudflare.go, tunnel.go |
类型化错误(IsNotFound/IsAlreadyExists/IsConflict…) | StatusError | errors.go |
| 实时 watch(终态自动停止) | WatchInterface[T] | watch.go |
| 无 gRPC 服务器的 Fake 客户端 | fake.Client | fake.go |
| OIDC 登录与可再生服务认证 | oidc.Login/oidc.DeviceLogin/oidc.ClientCredentials/oidc.NewClientCredentialsAuth | oidc 包 |
| 网关配置便捷加载 | gateway.NewClient/gateway.LoadConfig | gateway.go |
用 Fake 客户端做无网关测试
fake.Client(fake.go)完整实现v1.ClientInterface,所有子客户端基于内存 store。它特别适合在 CI 中测试 Operator 逻辑:NewClient(opts...)直接构造(无网络、无 gRPC),AddSandbox/AddProvider/AddWorkspace/AddMember/AddRevision等Add*方法用于在测试开始前预置 fixture(插入时深拷贝、不触发 watch 事件),Close()停止所有 watcher 并让后续调用返回 Unavailable。也可用WithHealthResult/WithGatewayInfo/WithCurrentUser定制健康检查返回值。编译期通过var _ v1.ClientInterface = (*Client)(nil)保证接口完整。
从 v0.0.101 迁移
预 1.0 版本 SDK 刻意包含一批源码不兼容的 API 修正(README 明确声明),迁移时注意:
TCP.Listen返回ForwardListener生命周期句柄,SDK 拥有 accept 循环,调用方拨Addr()并Close(),而不是自己调Accept()或把句柄交给http.Serve;- 资源操作改为显式传工作区,携带工作区的领域类型保留该作用域;
- 若干公共结构体字段顺序变化,请使用键控结构体字面量(keyed struct literals);
- 首字母缩写采用 Go 拼写,包括
JSONRPCMaxBodyBytes; - Provider 配置时长使用精确的
RefreshBefore、MaxLifetime、CacheTTL字段,旧的整秒字段已移除。
这些是模块停留在 v1 之下的有意变更,建议作为一次整体迁移升级,而不是继续依赖 v0.0.101 的 API 形态。
前置条件与构建测试
SDK 要求Go 1.25.13 或更高版本,并推荐使用 mise)。在克隆本仓库后进入 SDK 目录即可:
cd sdk/go mise run test # Run tests with coverage mise run lint # Run golangci-lint mise run ci # Full CI pipeline (lint + build + test)许可证为 Apache-2.0,详见仓库根目录 LICENSE;Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
【免费下载链接】OpenShell
OpenShell is the safe, private runtime for autonomous AI agents.
相关推荐
OpenShell Go SDK Sandboxes 完整指南:沙箱生命周期管理 API 详解
OpenShell Go SDK Sandboxes 完整指南:沙箱生命周期管理 API 详解 本篇技术指南聚焦 OpenShell 官方 Go SDK( sd
OpenShell Go SDK TCP 端口转发指南:用 gRPC 双向流将沙箱端口直通本地
OpenShell Go SDK TCP 端口转发指南:用 gRPC 双向流将沙箱端口直通本地 OpenShell 的 Go SDK 提供了 client.TC
OpenShell Go SDK 开发指南:以原生 Go 类型驱动 OpenShell 网关的沙箱、执行与策略编排
OpenShell Go SDK 开发指南:以原生 Go 类型驱动 OpenShell 网关的沙箱、执行与策略编排 导读 OpenShell Go SDK( s
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考