@kanyun-ai-infra/agenthub 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -416
- package/dist/index.cjs +4180 -2150
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3985 -827
- package/dist/index.d.ts +3985 -827
- package/dist/index.js +4325 -2308
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,17 @@
|
|
|
15
15
|
两者使用不同的 token audience:Portal 登录态 token 不能调用 Control Plane,反过来也一样。
|
|
16
16
|
请始终把管理门户签发的 platform-api token 和 `agenthub-plane` Control Plane URL 配套使用。
|
|
17
17
|
|
|
18
|
+
## 如果你是 agent
|
|
19
|
+
|
|
20
|
+
不需要读完这份 README——已经发布了对应的 skill,装完直接照着写代码即可:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx rush-ai skill install @kanyun/agenthub-sdk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
skill 内容和这份 README 保持同步;改了 public API 时必须同步改 skill。公开方法与示例见
|
|
27
|
+
[AgentHub API 与 SDK 文档](https://agenthub.zhenguanyu.com/docs/api-and-sdk#sdk-methods)。剩下的正文是给人类读者的完整参考。
|
|
28
|
+
|
|
18
29
|
## 安装
|
|
19
30
|
|
|
20
31
|
```bash
|
|
@@ -24,9 +35,9 @@ pnpm add @kanyun-ai-infra/agenthub
|
|
|
24
35
|
|
|
25
36
|
需要 Node 18+,因为 SDK 使用全局 `fetch`。
|
|
26
37
|
|
|
27
|
-
如果调用方是 Java、Python
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
如果调用方是 Java、Python 或 Shell(curl),请优先看
|
|
39
|
+
<https://agenthub.zhenguanyu.com/docs/api-and-sdk/multi-language-examples>(Python / Java)
|
|
40
|
+
或 <https://agenthub.zhenguanyu.com/docs/api-and-sdk#rest-flow>(curl)。
|
|
30
41
|
|
|
31
42
|
## 快速开始
|
|
32
43
|
|
|
@@ -80,15 +91,22 @@ const version = await agenthub.agents.publishVersion({
|
|
|
80
91
|
},
|
|
81
92
|
});
|
|
82
93
|
|
|
83
|
-
// 4. Session
|
|
94
|
+
// 4. Session:通过 stage 槽位分配一次运行时。setDefault:true 首次发布已自动
|
|
95
|
+
// 创建 test 槽位,直接用即可(stage 与 versionId 互斥,见下方「完整参考」)。
|
|
84
96
|
const session = await agenthub.sessions.create({
|
|
85
97
|
projectId: project.id,
|
|
86
|
-
agentId: version.agentId,
|
|
98
|
+
agentId: version.agentId,
|
|
99
|
+
stage: "test",
|
|
87
100
|
});
|
|
88
101
|
|
|
89
102
|
const ready = await agenthub.sessions.wait(session.sessionId); // status === "ready" 时返回
|
|
90
103
|
```
|
|
91
104
|
|
|
105
|
+
> Environment(k8s 配置底座:资源规格 / envVars / TTL)上面还没有登场——test 槽位
|
|
106
|
+
> 目前落在裸 revision 快照上,本地联调、smoke test 够用。真实上线到 production,
|
|
107
|
+
> 要额外给槽位绑一个 Environment,才有资源规格定制、canary 灰度、drift 检测、
|
|
108
|
+
> 一键回滚这些能力:完整走一遍见下方「完整参考」里的发布闭环六幕示例。
|
|
109
|
+
|
|
92
110
|
## 核心模型
|
|
93
111
|
|
|
94
112
|
```text
|
|
@@ -102,428 +120,37 @@ Project → Agent → Version → Session
|
|
|
102
120
|
标记 project/agent 默认版本。
|
|
103
121
|
- **Session**:基于某个 Version 发起的一次 startup/runtime allocation。
|
|
104
122
|
|
|
105
|
-
上面这条链路是"定义"
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
上面这条链路是"定义"层面的(发布了什么);Session 已经通过 **Stage** 槽位
|
|
124
|
+
(`test`/`production`,指向当前生效的 Version,可选绑定 **Environment**)
|
|
125
|
+
跑起来了——但这个槽位目前没绑 Environment(k8s 配置底座:资源规格 / envVars /
|
|
126
|
+
TTL),落的是裸 revision 快照。给槽位绑定 Environment、晋级到 production、
|
|
127
|
+
canary 灰度、drift 检测、一键回滚——完整闭环见下方「完整参考」的发布闭环六幕示例。
|
|
109
128
|
|
|
110
129
|
Agents 和 runtime templates 都是 **project-scoped**。Version 必须在同一个 Project
|
|
111
|
-
内解析到 Agent 和 runtime source
|
|
130
|
+
内解析到 Agent 和 runtime source;唯一例外是下方「完整参考」提到的 platform-shared templates。
|
|
112
131
|
|
|
113
132
|
> 本文一律用主词汇 **agent / version**;它们是底层 wire 名 `workload` / `revision`
|
|
114
|
-
> 的别名,SDK
|
|
115
|
-
|
|
116
|
-
### 术语别名(agent / version)
|
|
117
|
-
|
|
118
|
-
| 主词汇(推荐) | 等价的底层名 | 说明 |
|
|
119
|
-
| --- | --- | --- |
|
|
120
|
-
| `agenthub.agents` | `agenthub.workloads` | **同一个资源实例**(`agents === workloads`),方法两边都能调。 |
|
|
121
|
-
| `agents.publishVersion({ agentSlug })` | `workloads.publishRevision({ workloadSlug })` | 同一个调用;wire body 完全一致。 |
|
|
122
|
-
| `sessions.create({ agentId, versionId })` | `sessions.create({ workloadId, revisionId })` | 入参别名,内部映射回规范名后再发出。 |
|
|
123
|
-
| 返回字段 `result.agentId` / `result.versionId` | `result.workloadId` / `result.revisionId` | 返回对象**同时带**两套字段(别名 = 规范值)。 |
|
|
124
|
-
|
|
125
|
-
规则:
|
|
126
|
-
|
|
127
|
-
- **互斥**:一对里只能传一个,规范名和别名同传会抛 `ALIAS_CONFLICT`,避免笔误被静默忽略
|
|
128
|
-
(`sessions.create` 的 `agentId`/`workloadId`、`versionId`/`revisionId`,
|
|
129
|
-
`applyRevisionManifest` 的 `agentSlug`/`workloadSlug`)。
|
|
130
|
-
- 入参别名**不进 wire**:发请求前映射回 `workloadId`/`revisionId`,body 永远是规范名。
|
|
131
|
-
- 返回别名是 SDK 派生的可枚举字段(`JSON.stringify` / 日志都带得上),不回传服务端。
|
|
132
|
-
- 底层名(`workloads` / `publishRevision` / `workloadId`)一直保留、行为不变,挑读着顺的那套写即可。
|
|
133
|
-
- 异步事件 payload、日志 / Trace 里的关联 ID 仍是 `workloadId`/`revisionId`(属 control-plane 范畴,不在 SDK 改名范围内)。
|
|
134
|
-
|
|
135
|
-
## API 参考
|
|
136
|
-
|
|
137
|
-
### `agenthub.projects`
|
|
138
|
-
|
|
139
|
-
| 方法 | 说明 |
|
|
140
|
-
| --- | --- |
|
|
141
|
-
| `create({ slug, displayName, description?, defaultRuntimeTemplateRefName? })` | 创建 Project。slug 重复时返回 `409 PROJECT_SLUG_CONFLICT`。 |
|
|
142
|
-
| `list()` | 列出 token 有效 scope 内的 Project。 |
|
|
143
|
-
| `get(projectId)` | 查询单个 Project。 |
|
|
144
|
-
| `delete(projectId)` | 软删(停用)Project:清空 owner / members、关闭集成。**owner-only**(需 `project:settings`)。系统项目不可删。返回 `{ action: "deactivated", projectId }`。 |
|
|
145
|
-
| `getWorkbench(projectId)` | 工作台聚合(AgentHub lifecycle console 同款读模型):`workloadCount` / `sessionStats` / `stageDrift` / `recentChanges`。 |
|
|
146
|
-
| `getReleaseBoard(projectId)` | 发布板聚合:每个 Agent 一行,含 test/production 槽位摘要与 `drift`。 |
|
|
147
|
-
|
|
148
|
-
### `agenthub.agents`(别名 `agenthub.workloads`)
|
|
149
|
-
|
|
150
|
-
| 方法 | 说明 |
|
|
151
|
-
| --- | --- |
|
|
152
|
-
| `create(req, { dryRun? })` | 创建 Agent。返回 `{ mode, status, agentId?/workloadId?, workload }`。 |
|
|
153
|
-
| `get({ projectId, agentSlug \| workloadSlug })` | 读回单个 Agent(含 `defaultRevisionId` 与内嵌 `defaultRevision`;发布过 `setDefault` Version 前为 `null`)。不存在返回 `404 WORKLOAD_NOT_FOUND`;`projectId` 超出 token scope 返回 `403 PROJECT_NOT_IN_TOKEN_SCOPE`。 |
|
|
154
|
-
| `getVersion({ projectId, agentSlug \| workloadSlug, version })` | 读回单个 Version(别名 `getRevision`)。返回 `{ id, projectId, workloadId, workloadSlug, version, runtimeSnapshotHash, createdAt }`(SDK 额外附 `agentId` 镜像)。不存在返回 `404 WORKLOAD_NOT_FOUND / REVISION_NOT_FOUND`。 |
|
|
155
|
-
| `publishVersion(req, { dryRun? })` | 发布 Version(别名 `publishRevision`,用 `workloadSlug`)。返回 `{ mode, status, agentId/workloadId, versionId?/revisionId?, runtimeTemplateId, resolved }`。 |
|
|
156
|
-
| `applyManifest({ projectId, manifest, dryRun? })` | 高级 / GitOps:apply raw agent manifest。 |
|
|
157
|
-
| `applyRevisionManifest({ projectId, agentSlug \| workloadSlug, manifest, dryRun? })` | 高级 / GitOps:apply raw version manifest。两个 slug 拼写都接受(互斥)。 |
|
|
158
|
-
|
|
159
|
-
`create` / `publishVersion` 的 `req` 结构和 managed-agent 全部配置项(`model` /
|
|
160
|
-
`systemPrompt` / `enabledSkills` / `enabledMcpServers` / `agentPackage` /
|
|
161
|
-
`agentOptions` 安全约束 / `runtime` / `startup` 等),见
|
|
162
|
-
[`specs/features/agenthub-sdk.md`](../../specs/features/agenthub-sdk.md)
|
|
163
|
-
的 "Managed-Agent Configuration"、"Workload API Contract"、"Revision Publish Contract" 章节。
|
|
164
|
-
|
|
165
|
-
> **`agentOptions` 安全约束(高频踩坑)**:`agentOptions` **只能收紧 / 限额**,不能放宽。
|
|
166
|
-
> `allowedTools` 与平台基线**取交集**(只会更窄)、`disallowedTools` 叠加平台黑名单且
|
|
167
|
-
> **不能移除**平台守卫工具;`permissionMode` / `env` / `mcpServers` / `hooks` /
|
|
168
|
-
> `plugins` / 原始 `sdkOptions` 等可能越权或逃逸 sandbox 的字段**一律不开放**(见 issue #840)。
|
|
169
|
-
> 字段全表见上述 spec。
|
|
170
|
-
|
|
171
|
-
`apply*` 方法里的 `manifest` 可以是 manifest 对象、`{ yaml }` 或 `{ json }`。
|
|
172
|
-
|
|
173
|
-
#### `agenthub.agents.stages` + `previewStage`
|
|
174
|
-
|
|
175
|
-
Stage 槽位(`test` / `production`)——把一个 Version 部署到某个槽位,槽位把
|
|
176
|
-
「version 指针」和「environment 绑定」当成两个独立维度组合在一起。概念全貌与
|
|
177
|
-
完整走一遍的例子见 [发布闭环](#发布闭环)。
|
|
178
|
-
|
|
179
|
-
| 方法 | 说明 |
|
|
180
|
-
| --- | --- |
|
|
181
|
-
| `stages.list(workloadId)` | 列出该 Agent 已部署的槽位;从未部署的 stage 直接不出现(无隐式创建)。 |
|
|
182
|
-
| `stages.set(workloadId, stage, { revisionId, environmentId?, runtimeOverrides?, configValues?, expectedUpdatedAt? })` | 创建/更新槽位(upsert,部署动作)。 |
|
|
183
|
-
| `stages.promote(workloadId, { environmentId?, expectedUpdatedAt?, sourceRevisionId?, expectedTestUpdatedAt? }?)` | 把 production 指针挪到 test 槽位当前指向的 revision(原子操作;只挪指针,production 自己的 overrides/config 不变)。 |
|
|
184
|
-
| `previewStage({ agentId \| workloadId, versionId \| revisionId, runtimeOverrides?, configValues?, environmentId? })` | UI-5 解析预览:只读计算部署后会解析成什么,不写任何行。晋级 / 部署前的确认步骤。 |
|
|
185
|
-
|
|
186
|
-
`set` / `promote` 的 `environmentId` 都是**三态**语义:
|
|
187
|
-
|
|
188
|
-
- **省略**(`undefined`)—— 保留槽位当前绑定;槽位第一次创建时没有"现值"可保留,
|
|
189
|
-
于是落成 `environmentId: null`(未绑定)。
|
|
190
|
-
- **`null`** —— 清除绑定(槽位回退到 revision 快照,不叠加任何 environment 字段组)。
|
|
191
|
-
- **UUID** —— 绑定该 Environment(服务端校验:存在 / 同项目或 platform-shared /
|
|
192
|
-
未 archived),并钉住它当前的 tip 版本。
|
|
193
|
-
|
|
194
|
-
⚠️ **`environmentId` "必须显式传,没有继承 revision 环境这回事"这条规则,只适用于
|
|
195
|
-
`stages.set` / `stages.promote` 这两个显式调用——`publishVersion` / `publishRevision`
|
|
196
|
-
本身没有 `environmentId` 这个入参,传不了。** `publishVersion({ setDefault: true })`
|
|
197
|
-
首次发布时自动创建的那条 **test** 槽位(2026-07-13 起的新行为,`production` 不会
|
|
198
|
-
被自动创建)同样落地成未绑定状态,但那是 upsert 时"省略"分支的结果,不是你对
|
|
199
|
-
publishVersion 传了什么参数。给这条自动创建的 test 槽位补环境绑定,要另外显式调用
|
|
200
|
-
`agenthub.agents.stages.set(workloadId, "test", { revisionId, environmentId })`。
|
|
201
|
-
见下方 [发布闭环](#发布闭环) 的 ⚠️ 说明。
|
|
202
|
-
|
|
203
|
-
### `agenthub.environments`
|
|
204
|
-
|
|
205
|
-
Environment = agent 运行的 **k8s 配置底座**(provider / 资源规格 / envVars /
|
|
206
|
-
TTL)——它本身不是"部署目标";`test`/`production` 才是部署目标(stage 槽位,
|
|
207
|
-
`agents.stages`),槽位钉住一对 `(environmentId, environmentVersion)`。
|
|
208
|
-
|
|
209
|
-
| 方法 | 说明 |
|
|
210
|
-
| --- | --- |
|
|
211
|
-
| `list(projectId, { includeArchived? })` | 列出项目可见的 Environment(含 platform-shared)。 |
|
|
212
|
-
| `get(id, projectId)` | 查询单个 Environment(含 config 全貌 + `referencedBy`——当前所有引用它的槽位)。 |
|
|
213
|
-
| `create(projectId, { name, base, description?, config? })` | 创建(从 platform-shared 底座拷贝 provider 字段)。定义域操作,日常建议在管理门户完成;这里是给 CI/IaC 场景开的口子。 |
|
|
214
|
-
| `update(id, projectId, { name?, description?, config?, repointReferences? })` | 编辑。`config` 面变更(`resources`/`envVars`/`defaultTtlSeconds`)冻结一个新版本,默认(`repointReferences` 省略或 `true`)批量把所有引用槽位拨到新版本;传 `false` 只建版本、槽位不动(配合 `repoint` 手动灰度)。返回 `{ environment, createdVersion, repointedSlotCount }`;改名/描述这类 metadata-only 写不产生新版本(`createdVersion: null`)。 |
|
|
215
|
-
| `archive(id, projectId)` | 归档(终态,无法撤销)。存量引用继续解析;任何新引用一律拒绝。 |
|
|
216
|
-
| `versions(id, projectId)` | 版本历史(每条自带完整 config 快照,不需要单独的详情调用)+ repoint 审计账(哪个槽位、何时、被谁从哪个版本拨到哪个版本)。 |
|
|
217
|
-
| `repoint(id, projectId, { toVersion, slots?, expectedSlots? })` | 拨动槽位的版本指针——回滚=拨回旧版本,灰度=只拨 `slots` 里点的槽位。省略 `slots` = 项目内所有引用该 Environment 的槽位。槽位选择器接受 `agentId`(同 `workloadId`)。 |
|
|
133
|
+
> 的别名,SDK 两套都接受,规范名与别名同传会抛 `ALIAS_CONFLICT`,细节见下方「完整参考」。
|
|
218
134
|
|
|
219
|
-
|
|
220
|
-
长任务,调用直接拿到结果,不需要轮询。
|
|
135
|
+
## 完整参考
|
|
221
136
|
|
|
222
|
-
|
|
137
|
+
下面这些内容日常写业务代码用不到,需要时查
|
|
138
|
+
[AgentHub 文档站](https://agenthub.zhenguanyu.com/docs/api-and-sdk) 对应小节
|
|
139
|
+
(这份 README 会随 npm 包分发到别的项目里,下面全部是可以直接点开的网址,不是仓库内相对路径):
|
|
223
140
|
|
|
224
|
-
|
|
|
141
|
+
| 你想找 | 链接 |
|
|
225
142
|
| --- | --- |
|
|
226
|
-
|
|
|
227
|
-
|
|
|
228
|
-
|
|
|
229
|
-
|
|
|
230
|
-
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
### `agenthub.files`
|
|
235
|
-
|
|
236
|
-
| 方法 | 说明 |
|
|
237
|
-
| --- | --- |
|
|
238
|
-
| `upload({ projectId, file, filename?, contentType? })` | 上传单个文件,返回 `{ ossUrl, url, filename, size, mediaType, mimeType }`。`file` 接受 `Blob` / `File` / `Uint8Array` / `ArrayBuffer`;非具名 `File` 时必须传 `filename`。 |
|
|
239
|
-
|
|
240
|
-
文件落在 `pilot/attachments/<projectId>/`,与 Session 资源 scope 校验同一 keyspace —— 为某 Project
|
|
241
|
-
上传的文件无法挂到另一个 Project 的 Session。`url` 是 ~1h 的 presigned GET(仅预览用);
|
|
242
|
-
要持久引用的是 `ossUrl`。
|
|
243
|
-
|
|
244
|
-
#### Model A 只读输入文件(session resources)
|
|
245
|
-
|
|
246
|
-
把文件作为 **Session 级只读输入**挂进 sandbox 的 `/workspace/.session/inputs/`(每次
|
|
247
|
-
provision / revival 重新物化),用 `files.upload` + `sessions.create({ resources })`:
|
|
248
|
-
|
|
249
|
-
```ts
|
|
250
|
-
const up = await agenthub.files.upload({
|
|
251
|
-
projectId,
|
|
252
|
-
file: new Blob([await fs.promises.readFile("spec.md")]),
|
|
253
|
-
filename: "spec.md",
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// `up` 是 SessionFileResource 的超集,只多一个预览用的 `url`;解构掉它,
|
|
257
|
-
// 剩下的 `meta`(ossUrl/filename/size/mediaType/mimeType)正是一个资源条目。
|
|
258
|
-
const { url, ...meta } = up;
|
|
259
|
-
|
|
260
|
-
const { sessionId } = await agenthub.sessions.create({
|
|
261
|
-
projectId,
|
|
262
|
-
agentId,
|
|
263
|
-
resources: {
|
|
264
|
-
version: "v1",
|
|
265
|
-
files: [{ ...meta, mountPath: "docs/spec.md" }], // mountPath 可省略 → 用文件名
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
// agent 在 /workspace/.session/inputs/docs/spec.md 只读看到该文件
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
约束:每 Session ≤ 20 个文件;`mountPath` 必须是 `.session/inputs/` 下的相对子路径
|
|
272
|
-
(拒绝绝对路径与 `..`);只读。
|
|
273
|
-
|
|
274
|
-
## 发布闭环
|
|
275
|
-
|
|
276
|
-
**正名**:Environment 对应 k8s 的「配置」——agent 跑在什么样的容器里(provider /
|
|
277
|
-
资源规格 / envVars / TTL),是**底座**,不是部署目标本身。`test` 和
|
|
278
|
-
`production` 才是部署目标,叫 **stage**(部署槽位)。槽位
|
|
279
|
-
= `{ version 指针, environment 绑定 }` 两个互相独立的维度:一个槽位可以只钉了
|
|
280
|
-
version、没绑 environment(回退到 revision 快照),也可以两者都钉。**"分别创建
|
|
281
|
-
测试环境和线上环境"**这个诉求,落到 API 上不是造两个 Environment,而是给
|
|
282
|
-
`test` 和 `production` 两个槽位各自 `stages.set` 一次——两个槽位可以指向
|
|
283
|
-
**同一个** Environment(同配置跑两份),也可以指向**不同**的 Environment
|
|
284
|
-
(比如 production 用更大的资源规格)。
|
|
285
|
-
|
|
286
|
-
下面完整走一遍"发布 → 部署 → 预览 → 晋级 → canary 灰度出真实 drift → repoint
|
|
287
|
-
回滚",全程用 agent 词汇;每段代码都是照实测过的 j8 SDK 旅程
|
|
288
|
-
(`tests/e2e/j8-agenthub-sdk-journey.spec.ts`)抄的真实调用序列,不是省略号带过
|
|
289
|
-
的伪代码——复制到 strict TS 项目里应该能直接编译。
|
|
290
|
-
|
|
291
|
-
### 第一幕:创建 Agent,发布 v1(setDefault:true 自动落地 test)
|
|
292
|
-
|
|
293
|
-
```ts
|
|
294
|
-
const project = await agenthub.projects.create({ slug: "acme", displayName: "Acme" });
|
|
295
|
-
await agenthub.agents.create({
|
|
296
|
-
projectId: project.id,
|
|
297
|
-
slug: "support-bot",
|
|
298
|
-
displayName: "Support Bot",
|
|
299
|
-
kind: "managed_agent",
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// setDefault:true —— 首发用它是安全的:2026-07-13 起的新行为只会自动创建
|
|
303
|
-
// **test** 槽位(environmentId 仍是 null,未绑定)+ 翻转 defaultRevisionId,
|
|
304
|
-
// 不会误建一个"看起来已经上线其实没配置"的 production(见文末 ⚠️)。存量
|
|
305
|
-
// 在此之前已经发布过、已经有 production 槽位的老 workload 走的是另一条
|
|
306
|
-
// 分支,不受这次改动影响(同样见文末 ⚠️ 的存量说明)。
|
|
307
|
-
const v1 = await agenthub.agents.publishVersion({
|
|
308
|
-
projectId: project.id,
|
|
309
|
-
agentSlug: "support-bot",
|
|
310
|
-
version: "v1",
|
|
311
|
-
workerContract: { kind: "managed_agent" },
|
|
312
|
-
setDefault: true,
|
|
313
|
-
});
|
|
314
|
-
// `revisionId` 在 RevisionApplyResult 里是 optional(dry-run / manifest 面共用
|
|
315
|
-
// 同一个类型);status:"created" 时必然有值,但 strict TS 下仍需要先断言/守卫
|
|
316
|
-
// 才能当必填参数传下去——不要在类型上偷懒直接 `v1.revisionId!` 到处传。
|
|
317
|
-
if (!v1.revisionId) throw new Error("publishVersion did not return a revisionId");
|
|
318
|
-
const revisionId = v1.revisionId;
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
### 第二幕:给已存在的 test 槽位补上环境绑定
|
|
322
|
-
|
|
323
|
-
```ts
|
|
324
|
-
const env = await agenthub.environments.create(project.id, {
|
|
325
|
-
name: "acme-standard",
|
|
326
|
-
base: { platformTemplateName: "pilot-v2-pod-agent" },
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// test 槽位在第一幕已经由 setDefault:true 自动创建(environmentId 是 null,
|
|
330
|
-
// 未绑定)——这里不是"首次部署",是确认/补上环境绑定。set 是 upsert,同一个
|
|
331
|
-
// revisionId 再传一次不会产生新状态,只补 environmentId 这一个字段。
|
|
332
|
-
// production 槽位此刻还不存在,留到第四幕用 promote 显式创建它。
|
|
333
|
-
await agenthub.agents.stages.set(v1.workloadId, "test", {
|
|
334
|
-
revisionId,
|
|
335
|
-
environmentId: env.id,
|
|
336
|
-
});
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
### 第三幕:晋级前先 preview
|
|
340
|
-
|
|
341
|
-
```ts
|
|
342
|
-
const preview = await agenthub.agents.previewStage({
|
|
343
|
-
agentId: v1.workloadId,
|
|
344
|
-
versionId: revisionId,
|
|
345
|
-
environmentId: env.id,
|
|
346
|
-
});
|
|
347
|
-
// 把 preview.runtimeSnapshot / preview.environment 展示给操作者确认,
|
|
348
|
-
// 无误后再提交第四幕的晋级请求。
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
### 第四幕:晋级 production(第一次创建它)
|
|
352
|
-
|
|
353
|
-
```ts
|
|
354
|
-
await agenthub.agents.stages.promote(v1.workloadId, {
|
|
355
|
-
// TOCTOU 防护:确保确认弹窗打开期间 test 槽位没有被别人改过。
|
|
356
|
-
sourceRevisionId: revisionId,
|
|
357
|
-
// production 此刻并不存在(新行为下首发只写 test)——promote 把它的指针
|
|
358
|
-
// 挪到 test 当前指向的 revision,但 environmentId 是独立三态、不会从
|
|
359
|
-
// test 继承绑定:首次创建 production 必须显式传 environmentId,否则会
|
|
360
|
-
// 带着 environmentId:null 落地(同一个坑,见文末 ⚠️)。
|
|
361
|
-
environmentId: env.id,
|
|
362
|
-
});
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
此刻 production 才第一次出现,且与 test 完全同步(同 revision、同
|
|
366
|
-
environment、同 environment 版本)——`getReleaseBoard` 这一行的 `drift` 是
|
|
367
|
-
`false`,这是正常稳态,不是 bug。
|
|
368
|
-
|
|
369
|
-
### 第五幕:环境升级到 v2,canary 灰度到 test(这才是真实 drift 的由来)
|
|
370
|
-
|
|
371
|
-
```ts
|
|
372
|
-
// defaultTtlSeconds 是"永不物化"字段(不需要 Kanyun template gateway)——
|
|
373
|
-
// config 面变更冻结出 v2;repointReferences:false 让两个槽位暂时都留在 v1,
|
|
374
|
-
// 不要一步就把所有槽位都拨过去。
|
|
375
|
-
const updated = await agenthub.environments.update(env.id, project.id, {
|
|
376
|
-
config: { defaultTtlSeconds: 7200 },
|
|
377
|
-
repointReferences: false,
|
|
378
|
-
});
|
|
379
|
-
// `createdVersion` is `number | null` — metadata-only writes create no
|
|
380
|
-
// version. A config-face write always does, but the type doesn't know that;
|
|
381
|
-
// guard before treating it as a required `toVersion`.
|
|
382
|
-
if (updated.createdVersion === null) throw new Error("update() did not create a new version");
|
|
383
|
-
const canaryVersion = updated.createdVersion;
|
|
384
|
-
|
|
385
|
-
// canary:只把 test 槽位拨到 v2,production 留在 v1。
|
|
386
|
-
const canary = await agenthub.environments.repoint(env.id, project.id, {
|
|
387
|
-
toVersion: canaryVersion,
|
|
388
|
-
slots: [{ agentId: v1.workloadId, stage: "test" }],
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
// 这才是 drift 真正出现的时刻——不是假设,是两次 get 读回来的真实状态。
|
|
392
|
-
const afterCanary = await agenthub.environments.get(env.id, project.id);
|
|
393
|
-
const testRef = afterCanary.referencedBy.find((ref) => ref.stage === "test");
|
|
394
|
-
const productionRef = afterCanary.referencedBy.find((ref) => ref.stage === "production");
|
|
395
|
-
// testRef.environmentVersion === 2, productionRef.environmentVersion === 1
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### 第六幕:canary 效果不理想,回滚(真实 409 重试,不是省略号带过)
|
|
399
|
-
|
|
400
|
-
```ts
|
|
401
|
-
import { PilotPlatformApiError } from "@kanyun-ai-infra/agenthub";
|
|
402
|
-
|
|
403
|
-
async function rollbackTestSlot(): Promise<void> {
|
|
404
|
-
// 重新拉一次,把 test 槽位的当前状态当成"我现在看到的影响面"传给
|
|
405
|
-
// expectedSlots——不要复用第五幕观察时的旧快照。
|
|
406
|
-
const environment = await agenthub.environments.get(env.id, project.id);
|
|
407
|
-
const testRef = environment.referencedBy.find((ref) => ref.stage === "test");
|
|
408
|
-
if (!testRef) return; // 槽位已经不再引用这个 environment,没什么可回滚的。
|
|
409
|
-
|
|
410
|
-
try {
|
|
411
|
-
await agenthub.environments.repoint(env.id, project.id, {
|
|
412
|
-
toVersion: 1, // 回到 canary 之前的版本
|
|
413
|
-
slots: [{ workloadId: testRef.workloadId, stage: "test" }],
|
|
414
|
-
expectedSlots: [{ workloadId: testRef.workloadId, stage: "test" }],
|
|
415
|
-
});
|
|
416
|
-
} catch (err) {
|
|
417
|
-
if (err instanceof PilotPlatformApiError && err.code === "ENVIRONMENT_REPOINT_IMPACT_SET_CHANGED") {
|
|
418
|
-
// 有人在你上面这次 get 之后又动过这个槽位——重新拉最新状态再试,
|
|
419
|
-
// 不能对着同一份 expectedSlots 硬重试。
|
|
420
|
-
return rollbackTestSlot();
|
|
421
|
-
}
|
|
422
|
-
throw err;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
await rollbackTestSlot();
|
|
427
|
-
// production 全程没有被 canary 影响过——回滚只是撤销 test 侧的灰度尝试。
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
---
|
|
431
|
-
|
|
432
|
-
⚠️ **`publishVersion({ setDefault: true })` 首次发布只会自动写 `test` 槽位
|
|
433
|
-
(`environmentId` 仍是 `null`,未绑定)+ 翻转 `defaultRevisionId`;`production`
|
|
434
|
-
永远不会被自动创建,必须显式 `stages.set` / `stages.promote`。** 这是
|
|
435
|
-
2026-07-13 拍板落地的行为(spec `pilot-v2-agent-stage-environment.md` §5.1
|
|
436
|
-
STG-M5~M8;读代码 + 实测确认,不是猜的):`applySetDefault`
|
|
437
|
-
(`packages/control-plane/src/workload/workload-manifest-apply.ts`)判定
|
|
438
|
-
"该 workload 此刻一个 stage 槽位都没有"(首发)时,只 upsert `test` 槽位
|
|
439
|
-
(`environmentId: undefined` 落在还不存在的槽位上,等价于插入 `null`——没有
|
|
440
|
-
"现值"可继承)+ 调用新写端点 `setWorkloadDefaultRevisionPointer` 挪
|
|
441
|
-
`default_revision_id`;`production` 完全不碰。**非首发**(该 workload 已经
|
|
442
|
-
有 ≥1 个槽位,不管是 `test` 还是 `production`)的 `setDefault` 写只挪
|
|
443
|
-
`default_revision_id` 指针,同样不碰任何槽位——上面六幕如果给第二次发布也传
|
|
444
|
-
`setDefault: true`,也只是指针翻转,不会自动同步任何槽位。
|
|
445
|
-
|
|
446
|
-
> **存量兼容**:2026-07-13 之前已经首发过、因而已经有 `production` 槽位的老
|
|
447
|
-
> workload 判定为"非首发",行为不受这次改动影响、不做迁移——`production`
|
|
448
|
-
> 槽位继续存在,只是后续 `setDefault` 写不会再挪动它的 revision 指针(这本身
|
|
449
|
-
> 是一处行为变化:改动前"非首发"的 `setDefault` 也会挪 `production` 指针)。
|
|
450
|
-
|
|
451
|
-
## Client 配置
|
|
452
|
-
|
|
453
|
-
`new PilotPlatformClient(config)`
|
|
454
|
-
|
|
455
|
-
| 选项 | 必填 | 说明 |
|
|
456
|
-
| --- | --- | --- |
|
|
457
|
-
| `controlPlaneUrl` | 是¹ | Control Plane 源地址。相对路径会拼到它下面;跨 origin 路径会在发 token 前被拒绝。 |
|
|
458
|
-
| `token` | 是¹ | Platform-api token。每个请求都会作为 `Authorization: Bearer <token>` 发送。 |
|
|
459
|
-
| `fetch` | 否 | 自定义 `fetch` 实现,默认使用全局 `fetch`。 |
|
|
460
|
-
| `signal` | 否 | 应用于每个请求的默认 `AbortSignal`。 |
|
|
461
|
-
| `defaultHeaders` | 否 | 合并到每个请求的额外 header。 |
|
|
462
|
-
| `userAgent` | 否 | 覆盖默认 `@kanyun-ai-infra/agenthub/<version>` UA。 |
|
|
463
|
-
| `pilotApiVersion` | 否 | 固定 `x-pilot-api-version` header,默认使用 v2 wire version。 |
|
|
464
|
-
|
|
465
|
-
¹ `controlPlaneUrl` 会依次回退到 `PILOT_CONTROL_PLANE_URL`、
|
|
466
|
-
`KANYUN_PILOT_CONTROL_PLANE_URL`;`token` 会回退到 `PILOT_TOKEN`。任意一项缺失时,
|
|
467
|
-
SDK 会在发送请求前抛出类型化错误。
|
|
468
|
-
|
|
469
|
-
### 获取 token
|
|
470
|
-
|
|
471
|
-
登录 **管理门户** <https://agenthub.zhenguanyu.com> 并签发 **platform-api token**。
|
|
472
|
-
该 token 的作用域是 Control Plane API(`agenthub-plane.zhenguanyu.com`);可以存为
|
|
473
|
-
`PILOT_TOKEN`,也可以显式传给 `token`。Token 是 Bearer 凭据,请不要提交到代码仓库;
|
|
474
|
-
泄露后需要在门户里轮换。
|
|
475
|
-
|
|
476
|
-
## Runtime template
|
|
477
|
-
|
|
478
|
-
managed-agent Version 运行在一个 **runtime template**(已注册的 runtime source)上。
|
|
479
|
-
Control Plane 按以下顺序解析:
|
|
480
|
-
|
|
481
|
-
1. version `runtime.profileRef`(structured)/ `spec.runtime.profileRef`
|
|
482
|
-
2. version `spec.runtimeTemplateRef`(manifest)
|
|
483
|
-
3. agent defaults `spec.defaults.runtimeTemplateRef`(manifest)
|
|
484
|
-
4. project `defaultRuntimeTemplateRefName`
|
|
485
|
-
|
|
486
|
-
如果都解析不到,返回 `PROJECT_DEFAULT_RUNTIME_MISSING`。如果指定模板在当前 Project
|
|
487
|
-
或 platform-shared templates 中不存在,返回 `RUNTIME_TEMPLATE_NOT_FOUND`。
|
|
488
|
-
|
|
489
|
-
主路径建议在创建 Project 时设置默认模板,这样发布 Version 时不需要手选:
|
|
490
|
-
|
|
491
|
-
```ts
|
|
492
|
-
await agenthub.projects.create({
|
|
493
|
-
slug: "acme",
|
|
494
|
-
displayName: "Acme",
|
|
495
|
-
defaultRuntimeTemplateRefName: "pilot-v2-pod-agent",
|
|
496
|
-
});
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
否则需要在每个 Version 上显式指定:`runtime: { profileRef: { name: "..." } }`。
|
|
500
|
-
|
|
501
|
-
**Platform-shared templates。** Platform Default project 拥有的模板(例如
|
|
502
|
-
`pilot-v2-pod-agent`)是共享基础设施:当调用方 Project 内找不到同名模板时,解析会回退到
|
|
503
|
-
Platform Default,因此任意 Project 的 Version 都可以按名称引用它们。`echo-bot`
|
|
504
|
-
是内置 echo-only 模板,适合 smoke test;它**不会**运行 LLM。
|
|
505
|
-
|
|
506
|
-
## 模型
|
|
507
|
-
|
|
508
|
-
`model` 接受完整 id(`claude-opus-4-7`、`claude-sonnet-4-6`、`claude-haiku-4-5`),
|
|
509
|
-
也接受会映射到完整 id 的历史别名:
|
|
510
|
-
|
|
511
|
-
| Alias | 解析为 |
|
|
512
|
-
| --- | --- |
|
|
513
|
-
| `opus` | `claude-opus-4-7` |
|
|
514
|
-
| `sonnet` | `claude-sonnet-4-6` |
|
|
515
|
-
| `haiku` | `claude-haiku-4-5` |
|
|
516
|
-
|
|
517
|
-
## Dry runs
|
|
518
|
-
|
|
519
|
-
`agents.create`、`publishVersion` 和 `apply*` 方法都接受 `{ dryRun: true }`。
|
|
520
|
-
dry run 会校验并解析请求,但不持久化;返回 `mode: "dry_run"` 和
|
|
521
|
-
`would_create` / `would_update` 状态;Version 已存在且内容一致时可能返回
|
|
522
|
-
`existing`。正式发布前可以用它确认 runtime template 能正确解析。
|
|
143
|
+
| 资源方法速查(projects / agents / stages / environments / sessions / files / profiles) | <https://agenthub.zhenguanyu.com/docs/api-and-sdk#sdk-methods> |
|
|
144
|
+
| "发布 → 部署 → 晋级 → 灰度 → 回滚"完整六幕示例 | <https://agenthub.zhenguanyu.com/docs/api-and-sdk#release-loop> |
|
|
145
|
+
| Client 配置全部选项、获取 token | <https://agenthub.zhenguanyu.com/docs/api-and-sdk#sdk-client-config> |
|
|
146
|
+
| Runtime template 解析顺序 | <https://agenthub.zhenguanyu.com/docs/api-and-sdk#runtime-template> |
|
|
147
|
+
| Model 别名、Dry runs | <https://agenthub.zhenguanyu.com/docs/api-and-sdk#model-dry-run> |
|
|
148
|
+
| 调用方是 Java / Python(最后手段,官方只推荐这份 TS SDK) | <https://agenthub.zhenguanyu.com/docs/api-and-sdk/multi-language-examples> |
|
|
149
|
+
| 调用方是 Shell(curl) | <https://agenthub.zhenguanyu.com/docs/api-and-sdk#rest-flow> |
|
|
523
150
|
|
|
524
151
|
## 错误
|
|
525
152
|
|
|
526
|
-
|
|
153
|
+
API 请求失败都会抛出类型化错误,错误对象带 `status`、`code` 和 `requestId`——但请求被 `AbortSignal` 取消是例外,那条路径抛的是原生 `AbortError`,没有这些字段,需要单独判断:
|
|
527
154
|
|
|
528
155
|
| Class | HTTP |
|
|
529
156
|
| --- | --- |
|