@cloudbase/manager-node 5.5.2-beta.0 → 5.5.2

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.
@@ -1,1121 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.TalosSandboxService = void 0;
18
- const error_1 = require("../error");
19
- const http_request_1 = require("../utils/http-request");
20
- __exportStar(require("./type"), exports);
21
- const TALOS_BASE_URL = 'https://apidoc.talos.ren/proxy';
22
- class TalosSandboxService {
23
- constructor(environment) {
24
- this.environment = environment;
25
- }
26
- /**
27
- * 创建沙箱
28
- * 从指定模板/池分配一个预热实例并创建沙箱,支持 CBS 和 s3-hybrid 池类型
29
- * @param {CreateSandboxOptions} options 创建参数
30
- * @returns {Promise<CreateSandboxResp>}
31
- */
32
- async createSandbox(options) {
33
- const { token: optToken, templateID, allow_internet_access, autoPause, autoResume, envVars, metadata, secure, timeout } = options;
34
- // 解析鉴权 Header
35
- const authHeader = this.resolveAuthHeader(optToken);
36
- if (!templateID || typeof templateID !== 'string') {
37
- throw new error_1.CloudBaseError('templateID is required');
38
- }
39
- // timeout 校验
40
- if (timeout !== undefined && (!Number.isInteger(timeout) || timeout < 0)) {
41
- throw new Error('timeout must be an integer >= 0');
42
- }
43
- // envVars 校验
44
- if (envVars !== undefined && (typeof envVars !== 'object' || Array.isArray(envVars))) {
45
- throw new Error('envVars must be an object');
46
- }
47
- // metadata 校验
48
- if (metadata !== undefined && (typeof metadata !== 'object' || Array.isArray(metadata))) {
49
- throw new Error('metadata must be an object');
50
- }
51
- // 构造请求体
52
- const reqData = {};
53
- if (templateID !== undefined) {
54
- reqData.templateID = templateID;
55
- }
56
- if (allow_internet_access !== undefined) {
57
- reqData.allow_internet_access = allow_internet_access;
58
- }
59
- if (autoPause !== undefined) {
60
- reqData.autoPause = autoPause;
61
- }
62
- if (autoResume !== undefined) {
63
- reqData.autoResume = autoResume;
64
- }
65
- if (envVars !== undefined) {
66
- reqData.envVars = envVars;
67
- }
68
- if (metadata !== undefined) {
69
- reqData.metadata = metadata;
70
- }
71
- if (secure !== undefined) {
72
- reqData.secure = secure;
73
- }
74
- if (timeout !== undefined) {
75
- reqData.timeout = timeout;
76
- }
77
- const { proxy } = this.environment.cloudBaseContext;
78
- const url = `${TALOS_BASE_URL}/sandboxes`;
79
- const resp = await (0, http_request_1.fetchStream)(url, {
80
- method: 'POST',
81
- headers: Object.assign({ Accept: 'application/json', 'Content-Type': 'application/json' }, authHeader),
82
- body: JSON.stringify(reqData)
83
- }, proxy);
84
- const text = await resp.text();
85
- let data;
86
- try {
87
- data = text ? JSON.parse(text) : {};
88
- }
89
- catch (_a) {
90
- throw new error_1.CloudBaseError(text);
91
- }
92
- if (resp.status === 201) {
93
- return data;
94
- }
95
- if (resp.status === 400) {
96
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'bad request';
97
- throw new error_1.CloudBaseError(`Create sandbox failed: ${resp.status} - ${errMsg}`);
98
- }
99
- if (resp.status === 404) {
100
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'pool/template not found';
101
- throw new error_1.CloudBaseError(`Create sandbox failed: ${resp.status} - ${errMsg}`);
102
- }
103
- if (resp.status === 409) {
104
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'no capacity';
105
- throw new error_1.CloudBaseError(`Create sandbox failed: ${resp.status} - ${errMsg}`);
106
- }
107
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
108
- throw new error_1.CloudBaseError(`Create sandbox failed: ${resp.status} ${resp.statusText} - ${errMsg}`);
109
- }
110
- /**
111
- * 列出沙箱(支持状态过滤和分页)
112
- * @param {ListSandboxesOptions} options 查询参数
113
- * @returns {Promise<ListedSandbox[]>}
114
- */
115
- async listSandboxes(options = {}) {
116
- const { token: optToken, state, limit, nextToken } = options;
117
- const authHeader = this.resolveAuthHeader(optToken);
118
- // limit 校验
119
- if (limit !== undefined && (!Number.isInteger(limit) || limit < 1)) {
120
- throw new error_1.CloudBaseError('limit must be an integer >= 1');
121
- }
122
- // state 校验
123
- const VALID_STATES = ['running', 'paused'];
124
- if (state !== undefined) {
125
- for (const s of state) {
126
- if (!VALID_STATES.includes(s)) {
127
- throw new error_1.CloudBaseError(`Invalid state value: ${s}. Must be one of: ${VALID_STATES.join(', ')}`);
128
- }
129
- }
130
- }
131
- const { proxy } = this.environment.cloudBaseContext;
132
- const url = new URL(`${TALOS_BASE_URL}/v2/sandboxes`);
133
- // state — 数组参数,逐个追加
134
- if (state && state.length > 0) {
135
- url.searchParams.set('state', state.join(','));
136
- }
137
- // limit
138
- if (limit !== undefined) {
139
- url.searchParams.set('limit', String(limit));
140
- }
141
- // nextToken
142
- if (nextToken !== undefined) {
143
- url.searchParams.set('nextToken', nextToken);
144
- }
145
- const resp = await (0, http_request_1.fetchStream)(url.toString(), {
146
- method: 'GET',
147
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
148
- }, proxy);
149
- const text = await resp.text();
150
- let data;
151
- try {
152
- data = text ? JSON.parse(text) : [];
153
- }
154
- catch (_a) {
155
- throw new error_1.CloudBaseError(text);
156
- }
157
- if (resp.status !== 200) {
158
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
159
- throw new error_1.CloudBaseError(`List sandboxes failed: ${resp.status} - ${errMsg}`);
160
- }
161
- return data;
162
- }
163
- /**
164
- * 获取沙箱日志(V2)
165
- * @param {GetSandboxLogsOptions} options 查询参数
166
- * @returns {Promise<SandboxLogsV2Response>}
167
- */
168
- async getSandboxLogs(options) {
169
- const { token: optToken, sandboxID, cursor, limit, direction, level, search } = options;
170
- const authHeader = this.resolveAuthHeader(optToken);
171
- // sandboxID 校验
172
- if (!sandboxID) {
173
- throw new error_1.CloudBaseError('sandboxID is required');
174
- }
175
- // cursor 校验
176
- if (cursor !== undefined && (!Number.isInteger(cursor) || cursor < 0)) {
177
- throw new error_1.CloudBaseError('cursor must be an integer >= 0');
178
- }
179
- // limit 校验
180
- if (limit !== undefined && (!Number.isInteger(limit) || limit < 0 || limit > 1000)) {
181
- throw new error_1.CloudBaseError('limit must be an integer between 0 and 1000');
182
- }
183
- // direction 校验
184
- const VALID_DIRECTIONS = ['forward', 'backward'];
185
- if (direction !== undefined && !VALID_DIRECTIONS.includes(direction)) {
186
- throw new error_1.CloudBaseError(`Invalid direction: ${direction}. Must be one of: ${VALID_DIRECTIONS.join(', ')}`);
187
- }
188
- // level 校验
189
- const VALID_LEVELS = ['debug', 'info', 'warn', 'error'];
190
- if (level !== undefined && !VALID_LEVELS.includes(level)) {
191
- throw new error_1.CloudBaseError(`Invalid level: ${level}. Must be one of: ${VALID_LEVELS.join(', ')}`);
192
- }
193
- // search 校验
194
- if (search !== undefined && search.length > 256) {
195
- throw new error_1.CloudBaseError('search must be at most 256 characters');
196
- }
197
- const { proxy } = this.environment.cloudBaseContext;
198
- const url = new URL(`${TALOS_BASE_URL}/v2/sandboxes/${encodeURIComponent(sandboxID)}/logs`);
199
- if (cursor !== undefined) {
200
- url.searchParams.set('cursor', String(cursor));
201
- }
202
- if (limit !== undefined) {
203
- url.searchParams.set('limit', String(limit));
204
- }
205
- if (direction !== undefined) {
206
- url.searchParams.set('direction', direction);
207
- }
208
- if (level !== undefined) {
209
- url.searchParams.set('level', level);
210
- }
211
- if (search !== undefined) {
212
- url.searchParams.set('search', search);
213
- }
214
- const resp = await (0, http_request_1.fetchStream)(url.toString(), {
215
- method: 'GET',
216
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
217
- }, proxy);
218
- const text = await resp.text();
219
- let data;
220
- try {
221
- data = text ? JSON.parse(text) : { logs: [] };
222
- }
223
- catch (_a) {
224
- throw new error_1.CloudBaseError(text);
225
- }
226
- if (resp.status === 404) {
227
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
228
- }
229
- if (resp.status !== 200) {
230
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
231
- throw new error_1.CloudBaseError(`Get sandbox logs failed: ${resp.status} - ${errMsg}`);
232
- }
233
- return data;
234
- }
235
- /**
236
- * 获取多个沙箱的指标数据
237
- * @param {GetSandboxesMetricsOptions} options 查询参数
238
- * @returns {Promise<SandboxesWithMetrics>}
239
- */
240
- async getSandboxesMetrics(options) {
241
- const { token: optToken, sandboxIDs } = options;
242
- const authHeader = this.resolveAuthHeader(optToken);
243
- // sandboxIDs 校验
244
- if (!sandboxIDs || sandboxIDs.length === 0) {
245
- throw new error_1.CloudBaseError('sandboxIDs is required and must not be empty');
246
- }
247
- if (sandboxIDs.length > 100) {
248
- throw new error_1.CloudBaseError('sandboxIDs must contain at most 100 IDs');
249
- }
250
- const { proxy } = this.environment.cloudBaseContext;
251
- const url = new URL(`${TALOS_BASE_URL}/sandboxes/metrics`);
252
- url.searchParams.set('sandbox_ids', sandboxIDs.join(','));
253
- const resp = await (0, http_request_1.fetchStream)(url.toString(), {
254
- method: 'GET',
255
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
256
- }, proxy);
257
- const text = await resp.text();
258
- let data;
259
- try {
260
- data = text ? JSON.parse(text) : { sandboxes: {} };
261
- }
262
- catch (_a) {
263
- throw new error_1.CloudBaseError(text);
264
- }
265
- if (resp.status === 400) {
266
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'bad request';
267
- throw new error_1.CloudBaseError(`Get sandboxes metrics failed: ${resp.status} - ${errMsg}`);
268
- }
269
- if (resp.status !== 200) {
270
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
271
- throw new error_1.CloudBaseError(`Get sandboxes metrics failed: ${resp.status} - ${errMsg}`);
272
- }
273
- return data;
274
- }
275
- /**
276
- * 获取沙箱详情
277
- * @param {GetSandboxDetailOptions} options 查询参数
278
- * @returns {Promise<SandboxDetail>}
279
- */
280
- async getSandboxDetail(options) {
281
- const { token: optToken, sandboxID } = options;
282
- const authHeader = this.resolveAuthHeader(optToken);
283
- if (!sandboxID) {
284
- throw new error_1.CloudBaseError('sandboxID is required');
285
- }
286
- const { proxy } = this.environment.cloudBaseContext;
287
- const url = `${TALOS_BASE_URL}/sandboxes/${encodeURIComponent(sandboxID)}`;
288
- const resp = await (0, http_request_1.fetchStream)(url, {
289
- method: 'GET',
290
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
291
- }, proxy);
292
- const text = await resp.text();
293
- let data;
294
- try {
295
- data = text ? JSON.parse(text) : {};
296
- }
297
- catch (_a) {
298
- throw new error_1.CloudBaseError(text);
299
- }
300
- if (resp.status === 404) {
301
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
302
- }
303
- if (resp.status !== 200) {
304
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
305
- throw new error_1.CloudBaseError(`Get sandbox detail failed: ${resp.status} - ${errMsg}`);
306
- }
307
- return data;
308
- }
309
- /**
310
- * 销毁沙箱
311
- * 对于运行中的沙箱:终止实例并删除磁盘
312
- * 对于已暂停的沙箱:回收暂存实例、终止并删除磁盘
313
- * @param {DestroySandboxOptions} options 请求参数
314
- * @returns {Promise<void>}
315
- */
316
- async destroySandbox(options) {
317
- const { token: optToken, sandboxID } = options;
318
- const authHeader = this.resolveAuthHeader(optToken);
319
- if (!sandboxID) {
320
- throw new error_1.CloudBaseError('sandboxID is required');
321
- }
322
- const { proxy } = this.environment.cloudBaseContext;
323
- const url = `${TALOS_BASE_URL}/sandboxes/${encodeURIComponent(sandboxID)}`;
324
- const resp = await (0, http_request_1.fetchStream)(url, {
325
- method: 'DELETE',
326
- headers: Object.assign({}, authHeader)
327
- }, proxy);
328
- if (resp.status === 404) {
329
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
330
- }
331
- if (resp.status !== 204) {
332
- const text = await resp.text();
333
- let data;
334
- try {
335
- data = text ? JSON.parse(text) : {};
336
- }
337
- catch (_a) {
338
- throw new error_1.CloudBaseError(text);
339
- }
340
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
341
- throw new error_1.CloudBaseError(`Destroy sandbox failed: ${resp.status} - ${errMsg}`);
342
- }
343
- }
344
- /**
345
- * 获取沙箱快照列表
346
- * 非 s3-hybrid 类型的沙箱始终返回 { latest: null, history: [] }
347
- * @param {ListSandboxSnapshotsOptions} options 请求参数
348
- * @returns {Promise<SandboxSnapshotList>}
349
- */
350
- async listSandboxSnapshots(options) {
351
- const { token: optToken, sandboxID } = options;
352
- const authHeader = this.resolveAuthHeader(optToken);
353
- if (!sandboxID) {
354
- throw new error_1.CloudBaseError('sandboxID is required');
355
- }
356
- const { proxy } = this.environment.cloudBaseContext;
357
- const url = `${TALOS_BASE_URL}/sandboxes/${encodeURIComponent(sandboxID)}/snapshots`;
358
- const resp = await (0, http_request_1.fetchStream)(url, {
359
- method: 'GET',
360
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
361
- }, proxy);
362
- const text = await resp.text();
363
- let data;
364
- try {
365
- data = text ? JSON.parse(text) : { latest: null, history: [] };
366
- }
367
- catch (_a) {
368
- throw new error_1.CloudBaseError(text);
369
- }
370
- if (resp.status === 401) {
371
- throw new error_1.CloudBaseError('Authentication error');
372
- }
373
- if (resp.status === 404) {
374
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
375
- }
376
- if (resp.status !== 200) {
377
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
378
- throw new error_1.CloudBaseError(`List sandbox snapshots failed: ${resp.status} - ${errMsg}`);
379
- }
380
- return data;
381
- }
382
- /**
383
- * 创建沙箱快照(rsync)
384
- * 对沙箱的 /workspace 执行 rsync 快照,不会暂停沙箱。
385
- * 每个沙箱有冷却时间限制(当前 5 分钟),仅 s3-hybrid 池支持。
386
- * @param {CreateSandboxSnapshotOptions} options 请求参数
387
- * @returns {Promise<SandboxSnapshot>}
388
- */
389
- async createSandboxSnapshot(options) {
390
- const { token: optToken, sandboxID } = options;
391
- const authHeader = this.resolveAuthHeader(optToken);
392
- if (!sandboxID) {
393
- throw new error_1.CloudBaseError('sandboxID is required');
394
- }
395
- const { proxy } = this.environment.cloudBaseContext;
396
- const url = `${TALOS_BASE_URL}/sandboxes/${encodeURIComponent(sandboxID)}/snapshots`;
397
- const resp = await (0, http_request_1.fetchStream)(url, {
398
- method: 'POST',
399
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
400
- }, proxy);
401
- const text = await resp.text();
402
- let data;
403
- try {
404
- data = text ? JSON.parse(text) : {};
405
- }
406
- catch (_a) {
407
- throw new error_1.CloudBaseError(text);
408
- }
409
- if (resp.status === 401) {
410
- throw new error_1.CloudBaseError('Authentication error');
411
- }
412
- if (resp.status === 404) {
413
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
414
- }
415
- if (resp.status === 409) {
416
- const rejected = data;
417
- const retryInfo = (rejected === null || rejected === void 0 ? void 0 : rejected.retry_after_seconds)
418
- ? ` (retry after ${rejected.retry_after_seconds}s)`
419
- : '';
420
- throw new error_1.CloudBaseError(`Snapshot rejected: ${(rejected === null || rejected === void 0 ? void 0 : rejected.message) || 'conflict'}${retryInfo}`);
421
- }
422
- if (resp.status !== 201) {
423
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
424
- throw new error_1.CloudBaseError(`Create sandbox snapshot failed: ${resp.status} - ${errMsg}`);
425
- }
426
- return data;
427
- }
428
- /**
429
- * 列出所有快照
430
- * 可按沙箱 ID 过滤,返回跨沙箱的快照列表
431
- * @param {ListAllSnapshotsOptions} options 查询参数
432
- * @returns {Promise<SandboxSnapshot[]>}
433
- */
434
- async listAllSnapshots(options = {}) {
435
- const { token: optToken, sandboxID, limit } = options;
436
- const authHeader = this.resolveAuthHeader(optToken);
437
- // limit 校验
438
- if (limit !== undefined && (!Number.isInteger(limit) || limit < 1 || limit > 100)) {
439
- throw new error_1.CloudBaseError('limit must be an integer between 1 and 100');
440
- }
441
- const { proxy } = this.environment.cloudBaseContext;
442
- const url = new URL(`${TALOS_BASE_URL}/snapshots`);
443
- if (sandboxID !== undefined) {
444
- url.searchParams.set('sandboxID', sandboxID);
445
- }
446
- if (limit !== undefined) {
447
- url.searchParams.set('limit', String(limit));
448
- }
449
- const resp = await (0, http_request_1.fetchStream)(url.toString(), {
450
- method: 'GET',
451
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
452
- }, proxy);
453
- const text = await resp.text();
454
- let data;
455
- try {
456
- data = text ? JSON.parse(text) : [];
457
- }
458
- catch (_a) {
459
- throw new error_1.CloudBaseError(text);
460
- }
461
- if (resp.status !== 200) {
462
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
463
- throw new error_1.CloudBaseError(`List all snapshots failed: ${resp.status} - ${errMsg}`);
464
- }
465
- return data;
466
- }
467
- /**
468
- * 暂停沙箱
469
- * CBS 池:通过 CXM 暂存实例,分离磁盘,清除实例绑定
470
- * S3-hybrid 池:分离 S3 磁盘,标记实例为 DIRTY,入队重镜像任务
471
- * @param {PauseSandboxOptions} options 请求参数
472
- * @returns {Promise<void>}
473
- */
474
- async pauseSandbox(options) {
475
- const { token: optToken, sandboxID } = options;
476
- const authHeader = this.resolveAuthHeader(optToken);
477
- if (!sandboxID) {
478
- throw new error_1.CloudBaseError('sandboxID is required');
479
- }
480
- const { proxy } = this.environment.cloudBaseContext;
481
- const url = `${TALOS_BASE_URL}/sandboxes/${encodeURIComponent(sandboxID)}/pause`;
482
- const resp = await (0, http_request_1.fetchStream)(url, {
483
- method: 'POST',
484
- headers: Object.assign({}, authHeader)
485
- }, proxy);
486
- if (resp.status === 404) {
487
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
488
- }
489
- if (resp.status === 409) {
490
- throw new error_1.CloudBaseError(`Pause sandbox conflict: sandbox ${sandboxID} is not in a pausable state`);
491
- }
492
- if (resp.status === 429) {
493
- throw new error_1.CloudBaseError(`Pause sandbox rate limited: sandbox ${sandboxID} is in cooldown`);
494
- }
495
- if (resp.status !== 204) {
496
- const text = await resp.text();
497
- let data;
498
- try {
499
- data = text ? JSON.parse(text) : {};
500
- }
501
- catch (_a) {
502
- throw new error_1.CloudBaseError(text);
503
- }
504
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
505
- throw new error_1.CloudBaseError(`Pause sandbox failed: ${resp.status} - ${errMsg}`);
506
- }
507
- }
508
- /**
509
- * 克隆沙箱
510
- * 异步操作:返回 202 和 PENDING 状态的占位沙箱。
511
- * 调用方需轮询 GET /sandboxes/{id} 直到状态变为 RUNNING 或 FAILED。
512
- * 源沙箱必须为 SUSPENDED 状态且拥有 DETACHED 的 DATA_S3 磁盘。
513
- * @param {CloneSandboxOptions} options 请求参数
514
- * @returns {Promise<CloneSandboxResponse>}
515
- */
516
- async cloneSandbox(options) {
517
- const { token: optToken, sandboxID, count } = options;
518
- const authHeader = this.resolveAuthHeader(optToken);
519
- if (!sandboxID) {
520
- throw new error_1.CloudBaseError('sandboxID is required');
521
- }
522
- // count 校验
523
- if (!Number.isInteger(count) || count < 1 || count > 32) {
524
- throw new error_1.CloudBaseError('count must be an integer between 1 and 32');
525
- }
526
- const { proxy } = this.environment.cloudBaseContext;
527
- const url = `${TALOS_BASE_URL}/sandboxes/${encodeURIComponent(sandboxID)}/clone`;
528
- const resp = await (0, http_request_1.fetchStream)(url, {
529
- method: 'POST',
530
- headers: Object.assign({ Accept: 'application/json', 'Content-Type': 'application/json' }, authHeader),
531
- body: JSON.stringify({ count })
532
- }, proxy);
533
- const text = await resp.text();
534
- let data;
535
- try {
536
- data = text ? JSON.parse(text) : {};
537
- }
538
- catch (_a) {
539
- throw new error_1.CloudBaseError(text);
540
- }
541
- if (resp.status === 400) {
542
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'bad request';
543
- throw new error_1.CloudBaseError(`Clone sandbox failed: ${resp.status} - ${errMsg}`);
544
- }
545
- if (resp.status === 404) {
546
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
547
- }
548
- if (resp.status === 409) {
549
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'conflict';
550
- throw new error_1.CloudBaseError(`Clone sandbox conflict: ${errMsg}`);
551
- }
552
- if (resp.status !== 202) {
553
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
554
- throw new error_1.CloudBaseError(`Clone sandbox failed: ${resp.status} - ${errMsg}`);
555
- }
556
- return data;
557
- }
558
- /**
559
- * 连接沙箱(若已暂停则自动恢复)
560
- * 如果沙箱正在运行,返回 200;如果沙箱已暂停,会自动恢复并返回 201
561
- * @param {ConnectSandboxOptions} options 请求参数
562
- * @returns {Promise<CreateSandboxResp>}
563
- */
564
- async connectSandbox(options) {
565
- const { token: optToken, sandboxID, timeout } = options;
566
- const authHeader = this.resolveAuthHeader(optToken);
567
- if (!sandboxID) {
568
- throw new error_1.CloudBaseError('sandboxID is required');
569
- }
570
- // timeout 校验
571
- if (!Number.isInteger(timeout) || timeout < 0) {
572
- throw new error_1.CloudBaseError('timeout must be an integer >= 0');
573
- }
574
- const { proxy } = this.environment.cloudBaseContext;
575
- const url = `${TALOS_BASE_URL}/sandboxes/${encodeURIComponent(sandboxID)}/connect`;
576
- const resp = await (0, http_request_1.fetchStream)(url, {
577
- method: 'POST',
578
- headers: Object.assign({ 'Content-Type': 'application/json' }, authHeader),
579
- body: JSON.stringify({ timeout })
580
- }, proxy);
581
- const text = await resp.text();
582
- let data;
583
- try {
584
- data = text ? JSON.parse(text) : {};
585
- }
586
- catch (_a) {
587
- throw new error_1.CloudBaseError(text);
588
- }
589
- if (resp.status === 404) {
590
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
591
- }
592
- if (resp.status === 409) {
593
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'conflict';
594
- throw new error_1.CloudBaseError(`Connect sandbox conflict: ${errMsg}`);
595
- }
596
- if (resp.status === 429) {
597
- throw new error_1.CloudBaseError(`Connect sandbox rate limited: sandbox ${sandboxID} is in cooldown`);
598
- }
599
- if (resp.status !== 200 && resp.status !== 201) {
600
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
601
- throw new error_1.CloudBaseError(`Connect sandbox failed: ${resp.status} - ${errMsg}`);
602
- }
603
- return data;
604
- }
605
- /**
606
- * 获取单个沙箱的资源指标
607
- * 从沙箱的 envd 代理获取实时指标(CPU、内存、磁盘)。
608
- * 如果沙箱已暂停或 envd 不可达,会返回合成的默认值。
609
- * @param {GetSandboxMetricsOptions} options 查询参数
610
- * @returns {Promise<SandboxMetrics[]>}
611
- */
612
- async getSandboxMetrics(options) {
613
- const { token: optToken, sandboxID, start, end } = options;
614
- const authHeader = this.resolveAuthHeader(optToken);
615
- if (!sandboxID) {
616
- throw new error_1.CloudBaseError('sandboxID is required');
617
- }
618
- const { proxy } = this.environment.cloudBaseContext;
619
- const url = new URL(`${TALOS_BASE_URL}/sandboxes/${encodeURIComponent(sandboxID)}/metrics`);
620
- if (start !== undefined) {
621
- url.searchParams.set('start', String(start));
622
- }
623
- if (end !== undefined) {
624
- url.searchParams.set('end', String(end));
625
- }
626
- const resp = await (0, http_request_1.fetchStream)(url.toString(), {
627
- method: 'GET',
628
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
629
- }, proxy);
630
- const text = await resp.text();
631
- let data;
632
- try {
633
- data = text ? JSON.parse(text) : [];
634
- }
635
- catch (_a) {
636
- throw new error_1.CloudBaseError(text);
637
- }
638
- if (resp.status === 404) {
639
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
640
- }
641
- if (resp.status !== 200) {
642
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
643
- throw new error_1.CloudBaseError(`Get sandbox metrics failed: ${resp.status} - ${errMsg}`);
644
- }
645
- return data;
646
- }
647
- /**
648
- * 更新沙箱超时时间
649
- * EndAt 时间根据 created_at + timeout_seconds 重新计算,timeout 为 0 表示永不过期
650
- * @param {UpdateSandboxTimeoutOptions} options 请求参数
651
- * @returns {Promise<void>}
652
- */
653
- async updateSandboxTimeout(options) {
654
- const { token: optToken, sandboxID, timeout } = options;
655
- const authHeader = this.resolveAuthHeader(optToken);
656
- if (!sandboxID) {
657
- throw new error_1.CloudBaseError('sandboxID is required');
658
- }
659
- // timeout 校验
660
- if (!Number.isInteger(timeout) || timeout < 0) {
661
- throw new error_1.CloudBaseError('timeout must be an integer >= 0');
662
- }
663
- const { proxy } = this.environment.cloudBaseContext;
664
- const url = `${TALOS_BASE_URL}/sandboxes/${encodeURIComponent(sandboxID)}/timeout`;
665
- const resp = await (0, http_request_1.fetchStream)(url, {
666
- method: 'POST',
667
- headers: Object.assign({ Accept: 'application/json', 'Content-Type': 'application/json' }, authHeader),
668
- body: JSON.stringify({ timeout })
669
- }, proxy);
670
- // 成功:服务端可能返回 200 或 204 No Content
671
- if (resp.status === 200 || resp.status === 204) {
672
- return;
673
- }
674
- if (resp.status === 400) {
675
- const text = await resp.text();
676
- let data;
677
- try {
678
- data = text ? JSON.parse(text) : {};
679
- }
680
- catch (_a) {
681
- throw new error_1.CloudBaseError(text);
682
- }
683
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'bad request';
684
- throw new error_1.CloudBaseError(`Update sandbox timeout failed: ${resp.status} - ${errMsg}`);
685
- }
686
- if (resp.status === 404) {
687
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
688
- }
689
- const text = await resp.text();
690
- let data;
691
- try {
692
- data = text ? JSON.parse(text) : {};
693
- }
694
- catch (_b) {
695
- throw new error_1.CloudBaseError(text);
696
- }
697
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
698
- throw new error_1.CloudBaseError(`Update sandbox timeout failed: ${resp.status} - ${errMsg}`);
699
- }
700
- /**
701
- * 刷新沙箱 TTL
702
- * 通过 POST /sandboxes/{sandboxID}/refreshes 续期沙箱存活时间
703
- * @param {RefreshSandboxTTLOptions} options 请求参数
704
- * @returns {Promise<void>}
705
- */
706
- async refreshSandboxTTL(options) {
707
- const { token: optToken, sandboxID, duration } = options;
708
- const authHeader = this.resolveAuthHeader(optToken);
709
- if (!sandboxID) {
710
- throw new error_1.CloudBaseError('sandboxID is required');
711
- }
712
- // duration 校验
713
- if (!Number.isInteger(duration) || duration < 0 || duration > 3600) {
714
- throw new error_1.CloudBaseError('duration must be an integer between 0 and 3600');
715
- }
716
- const { proxy } = this.environment.cloudBaseContext;
717
- const url = `${TALOS_BASE_URL}/sandboxes/${encodeURIComponent(sandboxID)}/refreshes`;
718
- const resp = await (0, http_request_1.fetchStream)(url, {
719
- method: 'POST',
720
- headers: Object.assign({ 'Content-Type': 'application/json' }, authHeader),
721
- body: JSON.stringify({ duration })
722
- }, proxy);
723
- if (resp.status === 204) {
724
- return;
725
- }
726
- if (resp.status === 401) {
727
- throw new error_1.CloudBaseError('Authentication error');
728
- }
729
- if (resp.status === 404) {
730
- throw new error_1.CloudBaseError(`Sandbox not found: ${sandboxID}`);
731
- }
732
- const text = await resp.text();
733
- let data;
734
- try {
735
- data = text ? JSON.parse(text) : {};
736
- }
737
- catch (_a) {
738
- throw new error_1.CloudBaseError(text);
739
- }
740
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
741
- throw new error_1.CloudBaseError(`Refresh sandbox TTL failed: ${resp.status} - ${errMsg}`);
742
- }
743
- /**
744
- * 列出所有模板
745
- * 可选按团队 ID 过滤
746
- * @param {ListTemplatesOptions} options 查询参数
747
- * @returns {Promise<Template[]>}
748
- */
749
- async listTemplates(options = {}) {
750
- const { token: optToken, teamID } = options;
751
- const authHeader = this.resolveAuthHeader(optToken);
752
- // teamID 校验
753
- if (teamID !== undefined && typeof teamID !== 'string') {
754
- throw new error_1.CloudBaseError('teamID must be a string');
755
- }
756
- const { proxy } = this.environment.cloudBaseContext;
757
- const url = new URL(`${TALOS_BASE_URL}/templates`);
758
- if (teamID !== undefined) {
759
- url.searchParams.set('teamID', teamID);
760
- }
761
- const resp = await (0, http_request_1.fetchStream)(url.toString(), {
762
- method: 'GET',
763
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
764
- }, proxy);
765
- const text = await resp.text();
766
- let data;
767
- try {
768
- data = text ? JSON.parse(text) : [];
769
- }
770
- catch (_a) {
771
- throw new error_1.CloudBaseError(text);
772
- }
773
- if (resp.status !== 200) {
774
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
775
- throw new error_1.CloudBaseError(`List templates failed: ${resp.status} - ${errMsg}`);
776
- }
777
- return data;
778
- }
779
- /**
780
- * 创建新模板
781
- * 异步触发模板构建,立即返回 202 与模板基础信息;
782
- * 实际构建状态需通过其他接口查询
783
- * @param {CreateTemplateOptions} options 请求参数
784
- * @returns {Promise<Template>}
785
- */
786
- async createTemplate(options = {}) {
787
- const { token: optToken, alias, teamID, dockerfile, cpuCount, memoryMB, startCmd, readyCmd } = options;
788
- const authHeader = this.resolveAuthHeader(optToken);
789
- // 字符串字段校验
790
- if (alias !== undefined && typeof alias !== 'string') {
791
- throw new error_1.CloudBaseError('alias must be a string');
792
- }
793
- if (teamID !== undefined && typeof teamID !== 'string') {
794
- throw new error_1.CloudBaseError('teamID must be a string');
795
- }
796
- if (dockerfile !== undefined && typeof dockerfile !== 'string') {
797
- throw new error_1.CloudBaseError('dockerfile must be a string');
798
- }
799
- if (startCmd !== undefined && typeof startCmd !== 'string') {
800
- throw new error_1.CloudBaseError('startCmd must be a string');
801
- }
802
- if (readyCmd !== undefined && typeof readyCmd !== 'string') {
803
- throw new error_1.CloudBaseError('readyCmd must be a string');
804
- }
805
- // 数值字段校验(int32 正整数)
806
- if (cpuCount !== undefined && (!Number.isInteger(cpuCount) || cpuCount < 1)) {
807
- throw new error_1.CloudBaseError('cpuCount must be an integer >= 1');
808
- }
809
- if (memoryMB !== undefined && (!Number.isInteger(memoryMB) || memoryMB < 1)) {
810
- throw new error_1.CloudBaseError('memoryMB must be an integer >= 1');
811
- }
812
- // 构造请求体(仅写入显式提供的字段)
813
- const reqData = {};
814
- if (alias !== undefined)
815
- reqData.alias = alias;
816
- if (teamID !== undefined)
817
- reqData.teamID = teamID;
818
- if (dockerfile !== undefined)
819
- reqData.dockerfile = dockerfile;
820
- if (cpuCount !== undefined)
821
- reqData.cpuCount = cpuCount;
822
- if (memoryMB !== undefined)
823
- reqData.memoryMB = memoryMB;
824
- if (startCmd !== undefined)
825
- reqData.startCmd = startCmd;
826
- if (readyCmd !== undefined)
827
- reqData.readyCmd = readyCmd;
828
- const { proxy } = this.environment.cloudBaseContext;
829
- const url = `${TALOS_BASE_URL}/templates`;
830
- const resp = await (0, http_request_1.fetchStream)(url, {
831
- method: 'POST',
832
- headers: Object.assign({ Accept: 'application/json', 'Content-Type': 'application/json' }, authHeader),
833
- body: JSON.stringify(reqData)
834
- }, proxy);
835
- const text = await resp.text();
836
- let data;
837
- try {
838
- data = text ? JSON.parse(text) : {};
839
- }
840
- catch (_a) {
841
- throw new error_1.CloudBaseError(text);
842
- }
843
- if (resp.status === 400) {
844
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'bad request';
845
- throw new error_1.CloudBaseError(`Create template failed: ${resp.status} - ${errMsg}`);
846
- }
847
- if (resp.status !== 202) {
848
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
849
- throw new error_1.CloudBaseError(`Create template failed: ${resp.status} - ${errMsg}`);
850
- }
851
- return data;
852
- }
853
- /**
854
- * 根据模板 ID 获取模板详情
855
- * @param {GetTemplateOptions} options 请求参数
856
- * @returns {Promise<Template>}
857
- */
858
- async getTemplate(options) {
859
- const { token: optToken, templateID } = options;
860
- if (!templateID || typeof templateID !== 'string') {
861
- throw new error_1.CloudBaseError('templateID is required and must be a string');
862
- }
863
- const authHeader = this.resolveAuthHeader(optToken);
864
- const { proxy } = this.environment.cloudBaseContext;
865
- const url = `${TALOS_BASE_URL}/templates/${encodeURIComponent(templateID)}`;
866
- const resp = await (0, http_request_1.fetchStream)(url, {
867
- method: 'GET',
868
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
869
- }, proxy);
870
- const text = await resp.text();
871
- let data;
872
- try {
873
- data = text ? JSON.parse(text) : {};
874
- }
875
- catch (_a) {
876
- throw new error_1.CloudBaseError(text);
877
- }
878
- if (resp.status === 404) {
879
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'template not found';
880
- throw new error_1.CloudBaseError(`Get template failed: ${resp.status} - ${errMsg}`);
881
- }
882
- if (resp.status !== 200) {
883
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
884
- throw new error_1.CloudBaseError(`Get template failed: ${resp.status} - ${errMsg}`);
885
- }
886
- return data;
887
- }
888
- /**
889
- * 删除模板
890
- * 成功时服务端返回 204 No Content
891
- * @param {DeleteTemplateOptions} options 请求参数
892
- * @returns {Promise<void>}
893
- */
894
- async deleteTemplate(options) {
895
- const { token: optToken, templateID } = options;
896
- if (!templateID || typeof templateID !== 'string') {
897
- throw new error_1.CloudBaseError('templateID is required and must be a string');
898
- }
899
- const authHeader = this.resolveAuthHeader(optToken);
900
- const { proxy } = this.environment.cloudBaseContext;
901
- const url = `${TALOS_BASE_URL}/templates/${encodeURIComponent(templateID)}`;
902
- const resp = await (0, http_request_1.fetchStream)(url, {
903
- method: 'DELETE',
904
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
905
- }, proxy);
906
- if (resp.status === 204) {
907
- return;
908
- }
909
- const text = await resp.text();
910
- let data;
911
- try {
912
- data = text ? JSON.parse(text) : {};
913
- }
914
- catch (_a) {
915
- throw new error_1.CloudBaseError(text);
916
- }
917
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
918
- throw new error_1.CloudBaseError(`Delete template failed: ${resp.status} - ${errMsg}`);
919
- }
920
- /**
921
- * 更新模板
922
- * 当前仅支持修改 public 字段;服务端成功时返回 200 无 body
923
- * @param {UpdateTemplateOptions} options 请求参数
924
- * @returns {Promise<void>}
925
- */
926
- async updateTemplate(options) {
927
- const { token: optToken, templateID, public: isPublic } = options;
928
- if (!templateID || typeof templateID !== 'string') {
929
- throw new error_1.CloudBaseError('templateID is required and must be a string');
930
- }
931
- if (isPublic !== undefined && typeof isPublic !== 'boolean') {
932
- throw new error_1.CloudBaseError('public must be a boolean');
933
- }
934
- const authHeader = this.resolveAuthHeader(optToken);
935
- // 构造请求体(仅写入显式提供的字段)
936
- const reqData = {};
937
- if (isPublic !== undefined)
938
- reqData.public = isPublic;
939
- const { proxy } = this.environment.cloudBaseContext;
940
- const url = `${TALOS_BASE_URL}/templates/${encodeURIComponent(templateID)}`;
941
- const resp = await (0, http_request_1.fetchStream)(url, {
942
- method: 'PATCH',
943
- headers: Object.assign({ Accept: 'application/json', 'Content-Type': 'application/json' }, authHeader),
944
- body: JSON.stringify(reqData)
945
- }, proxy);
946
- if (resp.status === 200) {
947
- return;
948
- }
949
- const text = await resp.text();
950
- let data;
951
- try {
952
- data = text ? JSON.parse(text) : {};
953
- }
954
- catch (_a) {
955
- throw new error_1.CloudBaseError(text);
956
- }
957
- if (resp.status === 400) {
958
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'bad request';
959
- throw new error_1.CloudBaseError(`Update template failed: ${resp.status} - ${errMsg}`);
960
- }
961
- if (resp.status === 404) {
962
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'template not found';
963
- throw new error_1.CloudBaseError(`Update template failed: ${resp.status} - ${errMsg}`);
964
- }
965
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
966
- throw new error_1.CloudBaseError(`Update template failed: ${resp.status} - ${errMsg}`);
967
- }
968
- /**
969
- * 根据别名获取模板详情
970
- * @param {GetTemplateByAliasOptions} options 请求参数
971
- * @returns {Promise<Template>}
972
- */
973
- async getTemplateByAlias(options) {
974
- const { token: optToken, alias } = options;
975
- if (!alias || typeof alias !== 'string') {
976
- throw new error_1.CloudBaseError('alias is required and must be a string');
977
- }
978
- const authHeader = this.resolveAuthHeader(optToken);
979
- const { proxy } = this.environment.cloudBaseContext;
980
- const url = `${TALOS_BASE_URL}/templates/aliases/${encodeURIComponent(alias)}`;
981
- const resp = await (0, http_request_1.fetchStream)(url, {
982
- method: 'GET',
983
- headers: Object.assign({ Accept: 'application/json' }, authHeader)
984
- }, proxy);
985
- const text = await resp.text();
986
- let data;
987
- try {
988
- data = text ? JSON.parse(text) : {};
989
- }
990
- catch (_a) {
991
- throw new error_1.CloudBaseError(text);
992
- }
993
- if (resp.status === 404) {
994
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'template not found';
995
- throw new error_1.CloudBaseError(`Get template by alias failed: ${resp.status} - ${errMsg}`);
996
- }
997
- if (resp.status !== 200) {
998
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
999
- throw new error_1.CloudBaseError(`Get template by alias failed: ${resp.status} - ${errMsg}`);
1000
- }
1001
- return data;
1002
- }
1003
- /**
1004
- * 触发模板镜像滚动更新
1005
- * 服务端会对绑定该模板的所有活跃 Pool 触发独立的镜像滚动;
1006
- * 每个 Pool 的成功/失败结果汇总在 results 中返回。
1007
- * 注意:当前版本不强制 team 级隔离。
1008
- * @param {UpdateTemplateImageOptions} options 请求参数
1009
- * @returns {Promise<TemplateImageUpdateResponse>}
1010
- */
1011
- async updateTemplateImage(options) {
1012
- const { token: optToken, templateID, base_image, rollout } = options;
1013
- if (!templateID || typeof templateID !== 'string') {
1014
- throw new error_1.CloudBaseError('templateID is required and must be a string');
1015
- }
1016
- if (!base_image || typeof base_image !== 'string') {
1017
- throw new error_1.CloudBaseError('base_image is required and must be a string');
1018
- }
1019
- // rollout 字段校验
1020
- if (rollout !== undefined) {
1021
- if (typeof rollout !== 'object' || rollout === null) {
1022
- throw new error_1.CloudBaseError('rollout must be an object');
1023
- }
1024
- this.validateRolloutConfig(rollout);
1025
- }
1026
- const authHeader = this.resolveAuthHeader(optToken);
1027
- const reqData = { base_image };
1028
- if (rollout !== undefined) {
1029
- reqData.rollout = rollout;
1030
- }
1031
- const { proxy } = this.environment.cloudBaseContext;
1032
- const url = `${TALOS_BASE_URL}/templates/${encodeURIComponent(templateID)}/image`;
1033
- const resp = await (0, http_request_1.fetchStream)(url, {
1034
- method: 'POST',
1035
- headers: Object.assign({ Accept: 'application/json', 'Content-Type': 'application/json' }, authHeader),
1036
- body: JSON.stringify(reqData)
1037
- }, proxy);
1038
- const text = await resp.text();
1039
- let data;
1040
- try {
1041
- data = text ? JSON.parse(text) : {};
1042
- }
1043
- catch (_a) {
1044
- throw new error_1.CloudBaseError(text);
1045
- }
1046
- if (resp.status === 400) {
1047
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'bad request';
1048
- throw new error_1.CloudBaseError(`Update template image failed: ${resp.status} - ${errMsg}`);
1049
- }
1050
- if (resp.status === 404) {
1051
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || 'template not found';
1052
- throw new error_1.CloudBaseError(`Update template image failed: ${resp.status} - ${errMsg}`);
1053
- }
1054
- if (resp.status !== 202) {
1055
- const errMsg = (data === null || data === void 0 ? void 0 : data.message) || (data === null || data === void 0 ? void 0 : data.code) || 'unknown error';
1056
- throw new error_1.CloudBaseError(`Update template image failed: ${resp.status} - ${errMsg}`);
1057
- }
1058
- return data;
1059
- }
1060
- /**
1061
- * 校验 rollout 配置中的数值字段
1062
- * - batch_percent: 1-100
1063
- * - batch_interval_s: >= 0
1064
- * - max_failure_percent_per_batch: 1-100
1065
- * - min_failures_to_trigger: >= 1
1066
- * - stuck_timeout_s: >= 60
1067
- */
1068
- validateRolloutConfig(rollout) {
1069
- const { batch_percent, batch_interval_s, max_failure_percent_per_batch, min_failures_to_trigger, stuck_timeout_s } = rollout;
1070
- if (batch_percent !== undefined) {
1071
- if (!Number.isInteger(batch_percent) || batch_percent < 1 || batch_percent > 100) {
1072
- throw new error_1.CloudBaseError('rollout.batch_percent must be an integer in [1, 100]');
1073
- }
1074
- }
1075
- if (batch_interval_s !== undefined) {
1076
- if (!Number.isInteger(batch_interval_s) || batch_interval_s < 0) {
1077
- throw new error_1.CloudBaseError('rollout.batch_interval_s must be an integer >= 0');
1078
- }
1079
- }
1080
- if (max_failure_percent_per_batch !== undefined) {
1081
- if (!Number.isInteger(max_failure_percent_per_batch) || max_failure_percent_per_batch < 1 || max_failure_percent_per_batch > 100) {
1082
- throw new error_1.CloudBaseError('rollout.max_failure_percent_per_batch must be an integer in [1, 100]');
1083
- }
1084
- }
1085
- if (min_failures_to_trigger !== undefined) {
1086
- if (!Number.isInteger(min_failures_to_trigger) || min_failures_to_trigger < 1) {
1087
- throw new error_1.CloudBaseError('rollout.min_failures_to_trigger must be an integer >= 1');
1088
- }
1089
- }
1090
- if (stuck_timeout_s !== undefined) {
1091
- if (!Number.isInteger(stuck_timeout_s) || stuck_timeout_s < 60) {
1092
- throw new error_1.CloudBaseError('rollout.stuck_timeout_s must be an integer >= 60');
1093
- }
1094
- }
1095
- }
1096
- /**
1097
- * 解析鉴权 Token 并返回对应的 Header
1098
- * 优先使用调用方传入,其次从环境变量 TALOS_TOKEN 读取
1099
- * 按前缀自动识别 Header 类型:
1100
- * - admin_talos_* → { 'X-Admin-Token': token }
1101
- * - talos_* → { 'X-API-Key': token }
1102
- * - sk_talos_* → { 'Authorization': `Bearer ${token}` }
1103
- */
1104
- resolveAuthHeader(optToken) {
1105
- const token = optToken || process.env.TALOS_TOKEN;
1106
- if (!token) {
1107
- throw new error_1.CloudBaseError('Missing token: either pass it in options or set TALOS_TOKEN environment variable');
1108
- }
1109
- if (token.startsWith('admin_talos_')) {
1110
- return { 'X-Admin-Token': token };
1111
- }
1112
- if (token.startsWith('talos_')) {
1113
- return { 'X-API-Key': token };
1114
- }
1115
- if (token.startsWith('sk_talos_')) {
1116
- return { 'Authorization': `Bearer ${token}` };
1117
- }
1118
- throw new error_1.CloudBaseError(`Invalid token prefix: expected admin_talos_*, talos_*, or sk_talos_*, got ${token.substring(0, 10)}...`);
1119
- }
1120
- }
1121
- exports.TalosSandboxService = TalosSandboxService;