@adobe/spacecat-shared-project-engine-client 1.11.0 → 1.13.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 +4 -0
- package/package.json +1 -1
- package/src/client.js +31 -1
- package/src/index.d.ts +288 -1
- package/src/index.js +1 -0
- package/src/internal.js +35 -4
- package/src/rest-transport.js +229 -0
package/README.md
CHANGED
|
@@ -34,6 +34,10 @@ const { data, error } = await client.GET('/v1/countries');
|
|
|
34
34
|
- **Retries:** `429` is retried for any method; `5xx`/network errors only for idempotent methods
|
|
35
35
|
(so a POST is never replayed). Backoff is exponential with jitter, honours `Retry-After`, and is
|
|
36
36
|
capped at 20s/attempt. Pass `onRetry` to observe the loop.
|
|
37
|
+
- **Timeouts:** pass `requestTimeoutMs` to bound each attempt — a stalled attempt is aborted via
|
|
38
|
+
`AbortSignal.timeout` (a per-attempt deadline, combined with any caller `signal`, never
|
|
39
|
+
replacing it) and, for idempotent methods, retried. Unset (default) ⇒ no client-imposed deadline,
|
|
40
|
+
so a hung socket blocks until the platform's own limit; set this to bound it.
|
|
37
41
|
- **Shape:** this is a thin factory function rather than the `CLAUDE.md` "class + factory" client
|
|
38
42
|
pattern — the wrapper has no per-instance state or behaviour beyond what `openapi-fetch` already
|
|
39
43
|
provides, so a class would add ceremony without value. The typed surface IS the `openapi-fetch`
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -44,6 +44,11 @@ import { createRetryingFetch, toTokenGetter } from './internal.js';
|
|
|
44
44
|
* retry sleep (`{ attempt, delayMs, method, status?, error? }`), for logging/metrics. A retry
|
|
45
45
|
* loop is otherwise silent — an operator can't tell "slow upstream" from "stuck in backoff". A
|
|
46
46
|
* throwing or rejecting hook is swallowed and never affects the request.
|
|
47
|
+
* @property {number} [requestTimeoutMs] Per-attempt request deadline in ms. When set (> 0), each
|
|
48
|
+
* fetch attempt is aborted via `AbortSignal.timeout` after this many ms and — for an idempotent
|
|
49
|
+
* method — retried under the retry budget; a caller-supplied `signal` is still honoured
|
|
50
|
+
* (combined, not replaced). Unset (the default) ⇒ no client-imposed deadline, so a hung socket
|
|
51
|
+
* blocks until the platform's own limit; set this to bound it.
|
|
47
52
|
* @property {typeof globalThis.fetch} [fetch] Injectable fetch (tests, custom agents).
|
|
48
53
|
* Defaults to the global fetch.
|
|
49
54
|
*/
|
|
@@ -120,12 +125,37 @@ export function createSerenityProjectEngineApiClient(options) {
|
|
|
120
125
|
maxRetries = 2,
|
|
121
126
|
retryBaseDelayMs = 200,
|
|
122
127
|
onRetry,
|
|
128
|
+
requestTimeoutMs,
|
|
123
129
|
fetch: injectedFetch = globalThis.fetch,
|
|
124
130
|
} = options;
|
|
125
131
|
|
|
132
|
+
// Fail fast on a misconfigured timeout rather than silently disabling it: a NaN/negative value
|
|
133
|
+
// would no-op in withDeadline (leaving the caller unprotected), and Infinity would reach
|
|
134
|
+
// AbortSignal.timeout. Mirrors the defensive toTokenGetter/resolveBaseUrl guards below.
|
|
135
|
+
if (
|
|
136
|
+
requestTimeoutMs !== undefined
|
|
137
|
+
&& (typeof requestTimeoutMs !== 'number'
|
|
138
|
+
|| !Number.isFinite(requestTimeoutMs)
|
|
139
|
+
|| requestTimeoutMs <= 0)
|
|
140
|
+
) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
// Report numbers verbatim so NaN/Infinity read as themselves (JSON.stringify would render
|
|
143
|
+
// both as "null"); stringify other types so a bad string is visibly quoted.
|
|
144
|
+
`Project Engine client: requestTimeoutMs must be a positive finite number of ms, got ${
|
|
145
|
+
typeof requestTimeoutMs === 'number' ? requestTimeoutMs : JSON.stringify(requestTimeoutMs)
|
|
146
|
+
}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
126
150
|
const client = createClient({
|
|
127
151
|
baseUrl: resolveBaseUrl(baseUrl),
|
|
128
|
-
fetch: createRetryingFetch(
|
|
152
|
+
fetch: createRetryingFetch(
|
|
153
|
+
injectedFetch,
|
|
154
|
+
maxRetries,
|
|
155
|
+
retryBaseDelayMs,
|
|
156
|
+
onRetry,
|
|
157
|
+
requestTimeoutMs,
|
|
158
|
+
),
|
|
129
159
|
});
|
|
130
160
|
// Auth runs as openapi-fetch middleware, so the token getter resolves once per logical request
|
|
131
161
|
// and that token is reused across the request's retries (the retry layer clones the same Request
|
package/src/index.d.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { Client } from 'openapi-fetch';
|
|
13
|
+
import type { Client, FetchResponse, MaybeOptionalInit, MediaType } from 'openapi-fetch';
|
|
14
|
+
import type { RequiredKeysOf } from 'openapi-typescript-helpers';
|
|
14
15
|
import type { paths, components } from './generated/types.js';
|
|
15
16
|
|
|
16
17
|
/** Supplies the caller's IMS JWT — forwarded verbatim, never minted or exchanged. */
|
|
@@ -52,6 +53,13 @@ export interface SerenityProjectEngineApiClientOptions {
|
|
|
52
53
|
status?: number;
|
|
53
54
|
error?: Error;
|
|
54
55
|
}) => void | Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Per-attempt request deadline in ms. When set (> 0), each fetch attempt is aborted via
|
|
58
|
+
* `AbortSignal.timeout` after this many ms (and retried for idempotent methods under the retry
|
|
59
|
+
* budget); any caller-supplied `signal` is combined with it, never replaced. Unset ⇒ no
|
|
60
|
+
* client-imposed deadline.
|
|
61
|
+
*/
|
|
62
|
+
requestTimeoutMs?: number;
|
|
55
63
|
/** Injectable fetch (tests, custom agents). Defaults to the global fetch. */
|
|
56
64
|
fetch?: typeof globalThis.fetch;
|
|
57
65
|
}
|
|
@@ -66,5 +74,284 @@ export declare function createSerenityProjectEngineApiClient(
|
|
|
66
74
|
options: SerenityProjectEngineApiClientOptions,
|
|
67
75
|
): SerenityProjectEngineApiClient;
|
|
68
76
|
|
|
77
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
78
|
+
// Intent-named facade (transport) over the raw client.
|
|
79
|
+
//
|
|
80
|
+
// The three helpers below derive every facade method's parameter and return type
|
|
81
|
+
// straight from the generated `paths` contract, so the surface stays strictly
|
|
82
|
+
// in-spec and never degrades to `any`. They are internal to this declaration.
|
|
83
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The openapi-fetch `init` argument (params.path/query, body) accepted for a given
|
|
87
|
+
* path `P` + HTTP method `M`, derived from the generated contract.
|
|
88
|
+
*/
|
|
89
|
+
type TransportInit<P extends keyof paths, M extends keyof paths[P]> = MaybeOptionalInit<
|
|
90
|
+
paths[P],
|
|
91
|
+
M
|
|
92
|
+
>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Mirror of openapi-fetch's own (unexported) `InitParam`: the `init` argument is
|
|
96
|
+
* OPTIONAL when nothing in it is required (no path/query params, no required body),
|
|
97
|
+
* and REQUIRED otherwise — so `listLanguages()` needs no argument while
|
|
98
|
+
* `createProject({ params, body })` enforces its params + body at the call site.
|
|
99
|
+
*/
|
|
100
|
+
type TransportInitParam<Init> = RequiredKeysOf<Init> extends never
|
|
101
|
+
? [init?: Init]
|
|
102
|
+
: [init: Init];
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The value a facade method resolves with: the parsed 2xx body for path `P` +
|
|
106
|
+
* method `M`, or `null` for an empty body (e.g. a 204 / empty-body ack). Non-2xx
|
|
107
|
+
* responses never resolve — they throw at the single `unwrap` error seam.
|
|
108
|
+
*/
|
|
109
|
+
type TransportData<P extends keyof paths, M extends keyof paths[P]> =
|
|
110
|
+
| NonNullable<FetchResponse<paths[P][M], TransportInit<P, M>, MediaType>['data']>
|
|
111
|
+
| null;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Intent-named facade over {@link SerenityProjectEngineApiClient}. Wraps the 28 in-spec
|
|
115
|
+
* Project Engine operations spacecat-api-service consumes behind verb+resource methods, so
|
|
116
|
+
* consumers depend on this seam rather than the raw client's literal path strings. Each method
|
|
117
|
+
* is THIN: it forwards the caller's openapi-fetch `init` to the underlying client and resolves
|
|
118
|
+
* with the unwrapped 2xx body (or throws on a non-2xx / network error at a single seam). No
|
|
119
|
+
* caching, redaction, error→HTTP translation, or composite methods — all consumer-owned per
|
|
120
|
+
* ADR-0001. The remaining generated operations stay reachable via the raw client.
|
|
121
|
+
*/
|
|
122
|
+
export interface SerenityProjectEngineTransport {
|
|
123
|
+
/** GET /v1/languages — projects-admin-list-languages */
|
|
124
|
+
listLanguages(
|
|
125
|
+
...init: TransportInitParam<TransportInit<'/v1/languages', 'get'>>
|
|
126
|
+
): Promise<TransportData<'/v1/languages', 'get'>>;
|
|
127
|
+
/** GET /v1/ai_models — ai-list-global-models */
|
|
128
|
+
listGlobalAiModels(
|
|
129
|
+
...init: TransportInitParam<TransportInit<'/v1/ai_models', 'get'>>
|
|
130
|
+
): Promise<TransportData<'/v1/ai_models', 'get'>>;
|
|
131
|
+
|
|
132
|
+
/** GET /v1/workspaces/{id}/projects — projects-list-projects */
|
|
133
|
+
listProjects(
|
|
134
|
+
...init: TransportInitParam<TransportInit<'/v1/workspaces/{id}/projects', 'get'>>
|
|
135
|
+
): Promise<TransportData<'/v1/workspaces/{id}/projects', 'get'>>;
|
|
136
|
+
/** POST /v1/workspaces/{id}/projects — projects-post-project */
|
|
137
|
+
createProject(
|
|
138
|
+
...init: TransportInitParam<TransportInit<'/v1/workspaces/{id}/projects', 'post'>>
|
|
139
|
+
): Promise<TransportData<'/v1/workspaces/{id}/projects', 'post'>>;
|
|
140
|
+
/** GET /v1/workspaces/{id}/projects/{project_id} — projects-get-project */
|
|
141
|
+
getProject(
|
|
142
|
+
...init: TransportInitParam<TransportInit<'/v1/workspaces/{id}/projects/{project_id}', 'get'>>
|
|
143
|
+
): Promise<TransportData<'/v1/workspaces/{id}/projects/{project_id}', 'get'>>;
|
|
144
|
+
/** PATCH /v1/workspaces/{id}/projects/{project_id} — projects-patch-project */
|
|
145
|
+
updateProject(
|
|
146
|
+
...init: TransportInitParam<TransportInit<'/v1/workspaces/{id}/projects/{project_id}', 'patch'>>
|
|
147
|
+
): Promise<TransportData<'/v1/workspaces/{id}/projects/{project_id}', 'patch'>>;
|
|
148
|
+
/** DELETE /v1/workspaces/{id}/projects/{project_id} — projects-delete-project */
|
|
149
|
+
deleteProject(
|
|
150
|
+
...init: TransportInitParam<
|
|
151
|
+
TransportInit<'/v1/workspaces/{id}/projects/{project_id}', 'delete'>
|
|
152
|
+
>
|
|
153
|
+
): Promise<TransportData<'/v1/workspaces/{id}/projects/{project_id}', 'delete'>>;
|
|
154
|
+
/** POST /v1/workspaces/{id}/projects/{project_id}/publish — projects-publish-project */
|
|
155
|
+
publishProject(
|
|
156
|
+
...init: TransportInitParam<
|
|
157
|
+
TransportInit<'/v1/workspaces/{id}/projects/{project_id}/publish', 'post'>
|
|
158
|
+
>
|
|
159
|
+
): Promise<TransportData<'/v1/workspaces/{id}/projects/{project_id}/publish', 'post'>>;
|
|
160
|
+
|
|
161
|
+
/** GET /v1/workspaces/{id}/projects/{project_id}/ai_models — ai-list-models */
|
|
162
|
+
listAiModels(
|
|
163
|
+
...init: TransportInitParam<
|
|
164
|
+
TransportInit<'/v1/workspaces/{id}/projects/{project_id}/ai_models', 'get'>
|
|
165
|
+
>
|
|
166
|
+
): Promise<TransportData<'/v1/workspaces/{id}/projects/{project_id}/ai_models', 'get'>>;
|
|
167
|
+
/** DELETE /v1/workspaces/{id}/projects/{project_id}/ai_models — ai-delete-models */
|
|
168
|
+
deleteAiModels(
|
|
169
|
+
...init: TransportInitParam<
|
|
170
|
+
TransportInit<'/v1/workspaces/{id}/projects/{project_id}/ai_models', 'delete'>
|
|
171
|
+
>
|
|
172
|
+
): Promise<TransportData<'/v1/workspaces/{id}/projects/{project_id}/ai_models', 'delete'>>;
|
|
173
|
+
/** GET /v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks — ai-list-benchmarks */
|
|
174
|
+
listBenchmarks(
|
|
175
|
+
...init: TransportInitParam<
|
|
176
|
+
TransportInit<'/v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks', 'get'>
|
|
177
|
+
>
|
|
178
|
+
): Promise<
|
|
179
|
+
TransportData<'/v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks', 'get'>
|
|
180
|
+
>;
|
|
181
|
+
/** DELETE /v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks — ai-delete-benchmarks */
|
|
182
|
+
deleteBenchmarks(
|
|
183
|
+
...init: TransportInitParam<
|
|
184
|
+
TransportInit<'/v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks', 'delete'>
|
|
185
|
+
>
|
|
186
|
+
): Promise<
|
|
187
|
+
TransportData<'/v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks', 'delete'>
|
|
188
|
+
>;
|
|
189
|
+
/**
|
|
190
|
+
* PUT /v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks/{benchmark_id}
|
|
191
|
+
* — ai-update-benchmark
|
|
192
|
+
*/
|
|
193
|
+
updateBenchmark(
|
|
194
|
+
...init: TransportInitParam<
|
|
195
|
+
TransportInit<
|
|
196
|
+
'/v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks/{benchmark_id}',
|
|
197
|
+
'put'
|
|
198
|
+
>
|
|
199
|
+
>
|
|
200
|
+
): Promise<
|
|
201
|
+
TransportData<
|
|
202
|
+
'/v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks/{benchmark_id}',
|
|
203
|
+
'put'
|
|
204
|
+
>
|
|
205
|
+
>;
|
|
206
|
+
/** PUT /v1/workspaces/{id}/projects/{project_id}/ci/competitors — ci-update-competitors */
|
|
207
|
+
updateCompetitors(
|
|
208
|
+
...init: TransportInitParam<
|
|
209
|
+
TransportInit<'/v1/workspaces/{id}/projects/{project_id}/ci/competitors', 'put'>
|
|
210
|
+
>
|
|
211
|
+
): Promise<TransportData<'/v1/workspaces/{id}/projects/{project_id}/ci/competitors', 'put'>>;
|
|
212
|
+
|
|
213
|
+
/** POST /v2/workspaces/{id}/projects/{project_id}/ai_models — aio-project-create-model */
|
|
214
|
+
createAioModel(
|
|
215
|
+
...init: TransportInitParam<
|
|
216
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/ai_models', 'post'>
|
|
217
|
+
>
|
|
218
|
+
): Promise<TransportData<'/v2/workspaces/{id}/projects/{project_id}/ai_models', 'post'>>;
|
|
219
|
+
/** POST /v2/workspaces/{id}/projects/{project_id}/ai_models/benchmarks — ai-create-benchmarks-v2 */
|
|
220
|
+
createBenchmarks(
|
|
221
|
+
...init: TransportInitParam<
|
|
222
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/ai_models/benchmarks', 'post'>
|
|
223
|
+
>
|
|
224
|
+
): Promise<
|
|
225
|
+
TransportData<'/v2/workspaces/{id}/projects/{project_id}/ai_models/benchmarks', 'post'>
|
|
226
|
+
>;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* GET /v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls
|
|
230
|
+
* — aio-list-brand-urls
|
|
231
|
+
*/
|
|
232
|
+
listBrandUrls(
|
|
233
|
+
...init: TransportInitParam<
|
|
234
|
+
TransportInit<
|
|
235
|
+
'/v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls',
|
|
236
|
+
'get'
|
|
237
|
+
>
|
|
238
|
+
>
|
|
239
|
+
): Promise<
|
|
240
|
+
TransportData<
|
|
241
|
+
'/v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls',
|
|
242
|
+
'get'
|
|
243
|
+
>
|
|
244
|
+
>;
|
|
245
|
+
/**
|
|
246
|
+
* POST /v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls
|
|
247
|
+
* — aio-create-brand-urls
|
|
248
|
+
*/
|
|
249
|
+
createBrandUrls(
|
|
250
|
+
...init: TransportInitParam<
|
|
251
|
+
TransportInit<
|
|
252
|
+
'/v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls',
|
|
253
|
+
'post'
|
|
254
|
+
>
|
|
255
|
+
>
|
|
256
|
+
): Promise<
|
|
257
|
+
TransportData<
|
|
258
|
+
'/v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls',
|
|
259
|
+
'post'
|
|
260
|
+
>
|
|
261
|
+
>;
|
|
262
|
+
/**
|
|
263
|
+
* DELETE /v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls
|
|
264
|
+
* — aio-delete-brand-urls
|
|
265
|
+
*/
|
|
266
|
+
deleteBrandUrls(
|
|
267
|
+
...init: TransportInitParam<
|
|
268
|
+
TransportInit<
|
|
269
|
+
'/v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls',
|
|
270
|
+
'delete'
|
|
271
|
+
>
|
|
272
|
+
>
|
|
273
|
+
): Promise<
|
|
274
|
+
TransportData<
|
|
275
|
+
'/v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls',
|
|
276
|
+
'delete'
|
|
277
|
+
>
|
|
278
|
+
>;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* GET /v2/workspaces/{id}/projects/{project_id}/aio/init_status
|
|
282
|
+
* — aio-get-project-init-status-v2
|
|
283
|
+
*/
|
|
284
|
+
getProjectInitStatus(
|
|
285
|
+
...init: TransportInitParam<
|
|
286
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/aio/init_status', 'get'>
|
|
287
|
+
>
|
|
288
|
+
): Promise<TransportData<'/v2/workspaces/{id}/projects/{project_id}/aio/init_status', 'get'>>;
|
|
289
|
+
/** POST /v2/workspaces/{id}/projects/{project_id}/aio/prompts — aio-create-prompt-v2 */
|
|
290
|
+
createPrompts(
|
|
291
|
+
...init: TransportInitParam<
|
|
292
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/aio/prompts', 'post'>
|
|
293
|
+
>
|
|
294
|
+
): Promise<TransportData<'/v2/workspaces/{id}/projects/{project_id}/aio/prompts', 'post'>>;
|
|
295
|
+
/** DELETE /v2/workspaces/{id}/projects/{project_id}/aio/prompts — aio-delete-prompt-by-ids-v2 */
|
|
296
|
+
deletePromptsByIds(
|
|
297
|
+
...init: TransportInitParam<
|
|
298
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/aio/prompts', 'delete'>
|
|
299
|
+
>
|
|
300
|
+
): Promise<TransportData<'/v2/workspaces/{id}/projects/{project_id}/aio/prompts', 'delete'>>;
|
|
301
|
+
/**
|
|
302
|
+
* POST /v2/workspaces/{id}/projects/{project_id}/aio/prompts/by_tags
|
|
303
|
+
* — aio-list-prompts-by-tag-ids
|
|
304
|
+
*/
|
|
305
|
+
listPromptsByTagIds(
|
|
306
|
+
...init: TransportInitParam<
|
|
307
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/aio/prompts/by_tags', 'post'>
|
|
308
|
+
>
|
|
309
|
+
): Promise<TransportData<'/v2/workspaces/{id}/projects/{project_id}/aio/prompts/by_tags', 'post'>>;
|
|
310
|
+
/** PUT /v2/workspaces/{id}/projects/{project_id}/aio/prompts/tags — aio-update-prompts-batch */
|
|
311
|
+
updatePromptTags(
|
|
312
|
+
...init: TransportInitParam<
|
|
313
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/aio/prompts/tags', 'put'>
|
|
314
|
+
>
|
|
315
|
+
): Promise<TransportData<'/v2/workspaces/{id}/projects/{project_id}/aio/prompts/tags', 'put'>>;
|
|
316
|
+
/**
|
|
317
|
+
* POST /v2/workspaces/{id}/projects/{project_id}/aio/prompts/{prompt_id}/rename
|
|
318
|
+
* — aio-rename-prompt
|
|
319
|
+
*/
|
|
320
|
+
renamePrompt(
|
|
321
|
+
...init: TransportInitParam<
|
|
322
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/aio/prompts/{prompt_id}/rename', 'post'>
|
|
323
|
+
>
|
|
324
|
+
): Promise<
|
|
325
|
+
TransportData<'/v2/workspaces/{id}/projects/{project_id}/aio/prompts/{prompt_id}/rename', 'post'>
|
|
326
|
+
>;
|
|
327
|
+
/** GET /v2/workspaces/{id}/projects/{project_id}/aio/tags — aio-get-project-tags */
|
|
328
|
+
listProjectTags(
|
|
329
|
+
...init: TransportInitParam<
|
|
330
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/aio/tags', 'get'>
|
|
331
|
+
>
|
|
332
|
+
): Promise<TransportData<'/v2/workspaces/{id}/projects/{project_id}/aio/tags', 'get'>>;
|
|
333
|
+
/** POST /v2/workspaces/{id}/projects/{project_id}/aio/tags — aio-create-project-tags */
|
|
334
|
+
createProjectTags(
|
|
335
|
+
...init: TransportInitParam<
|
|
336
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/aio/tags', 'post'>
|
|
337
|
+
>
|
|
338
|
+
): Promise<TransportData<'/v2/workspaces/{id}/projects/{project_id}/aio/tags', 'post'>>;
|
|
339
|
+
/** PATCH /v2/workspaces/{id}/projects/{project_id}/aio/tags/{tag_id} — aio-update-tag */
|
|
340
|
+
updateProjectTag(
|
|
341
|
+
...init: TransportInitParam<
|
|
342
|
+
TransportInit<'/v2/workspaces/{id}/projects/{project_id}/aio/tags/{tag_id}', 'patch'>
|
|
343
|
+
>
|
|
344
|
+
): Promise<TransportData<'/v2/workspaces/{id}/projects/{project_id}/aio/tags/{tag_id}', 'patch'>>;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Builds the {@link SerenityProjectEngineTransport} facade. Takes the SAME options as
|
|
349
|
+
* {@link createSerenityProjectEngineApiClient} (it builds that client internally and adds no
|
|
350
|
+
* options of its own).
|
|
351
|
+
*/
|
|
352
|
+
export declare function createSerenityProjectEngineTransport(
|
|
353
|
+
options: SerenityProjectEngineApiClientOptions,
|
|
354
|
+
): SerenityProjectEngineTransport;
|
|
355
|
+
|
|
69
356
|
// Re-export the generated contract types for consumers that want them directly.
|
|
70
357
|
export type { paths, components };
|
package/src/index.js
CHANGED
package/src/internal.js
CHANGED
|
@@ -130,7 +130,8 @@ export function nextRetryDelayMs(completedAttempt, baseDelayMs, response) {
|
|
|
130
130
|
* @property {number} delayMs the wait before this retry
|
|
131
131
|
* @property {string} method the HTTP method
|
|
132
132
|
* @property {number} [status] the retryable response status that triggered the retry, if any
|
|
133
|
-
* @property {Error} [error] the
|
|
133
|
+
* @property {Error} [error] the error that triggered the retry, if any — a network error, or a
|
|
134
|
+
* per-attempt `AbortSignal.timeout` `TimeoutError` (both are `instanceof Error`)
|
|
134
135
|
*/
|
|
135
136
|
|
|
136
137
|
/**
|
|
@@ -164,9 +165,32 @@ function notifyRetry(onRetry, info) {
|
|
|
164
165
|
* @returns {void | Promise<void>} may be async; the return is not awaited (fire-and-forget)
|
|
165
166
|
*/
|
|
166
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Builds the per-attempt fetch `init` for {@link createRetryingFetch}. When `requestTimeoutMs`
|
|
170
|
+
* is a positive number, each attempt gets a FRESH `AbortSignal.timeout(requestTimeoutMs)` (the
|
|
171
|
+
* retry layer calls the underlying fetch once per attempt, so this is a per-attempt deadline, not
|
|
172
|
+
* a single budget spanning the whole retry loop) — combined with, never replacing, any
|
|
173
|
+
* caller-supplied signal via `AbortSignal.any`, so a caller abort and the deadline each still
|
|
174
|
+
* cancel the request. With no timeout configured the caller's `init` is returned untouched, so a
|
|
175
|
+
* caller signal continues to flow through natively.
|
|
176
|
+
* @param {RequestInit} [init]
|
|
177
|
+
* @param {AbortSignal} [callerSignal]
|
|
178
|
+
* @param {number} [requestTimeoutMs]
|
|
179
|
+
* @returns {RequestInit | undefined}
|
|
180
|
+
*/
|
|
181
|
+
export function withDeadline(init, callerSignal, requestTimeoutMs) {
|
|
182
|
+
if (!requestTimeoutMs || requestTimeoutMs <= 0) {
|
|
183
|
+
return init;
|
|
184
|
+
}
|
|
185
|
+
const timeoutSignal = AbortSignal.timeout(requestTimeoutMs);
|
|
186
|
+
const signal = callerSignal ? AbortSignal.any([callerSignal, timeoutSignal]) : timeoutSignal;
|
|
187
|
+
return { ...init, signal };
|
|
188
|
+
}
|
|
189
|
+
|
|
167
190
|
/**
|
|
168
191
|
* Wraps a fetch with bounded exponential-backoff retries. Retryable statuses follow
|
|
169
|
-
* {@link isRetryableStatus}; thrown network
|
|
192
|
+
* {@link isRetryableStatus}; thrown errors (a network error, or a per-attempt timeout) are
|
|
193
|
+
* retried only for idempotent methods.
|
|
170
194
|
* The wait between attempts is {@link nextRetryDelayMs} — jittered exponential backoff that also
|
|
171
195
|
* honours a `Retry-After` header. After exhausting retries it returns the last retryable response
|
|
172
196
|
* (so the caller still sees e.g. the final 503) or rethrows the last network error.
|
|
@@ -180,9 +204,12 @@ function notifyRetry(onRetry, info) {
|
|
|
180
204
|
* @param {number} maxRetries
|
|
181
205
|
* @param {number} baseDelayMs
|
|
182
206
|
* @param {OnRetry} [onRetry] optional best-effort retry-observability hook
|
|
207
|
+
* @param {number} [requestTimeoutMs] optional per-attempt deadline in ms; when > 0 each attempt is
|
|
208
|
+
* aborted via `AbortSignal.timeout` (combined with any caller signal) and, for idempotent
|
|
209
|
+
* methods, retried under the retry budget. Unset ⇒ no client-imposed deadline.
|
|
183
210
|
* @returns {typeof globalThis.fetch}
|
|
184
211
|
*/
|
|
185
|
-
export function createRetryingFetch(baseFetch, maxRetries, baseDelayMs, onRetry) {
|
|
212
|
+
export function createRetryingFetch(baseFetch, maxRetries, baseDelayMs, onRetry, requestTimeoutMs) {
|
|
186
213
|
return async function retryingFetch(input, init) {
|
|
187
214
|
const method = methodOf(input, init);
|
|
188
215
|
// Floor at 0: a negative maxRetries would skip the loop entirely, leaving both lastResponse
|
|
@@ -196,6 +223,9 @@ export function createRetryingFetch(baseFetch, maxRetries, baseDelayMs, onRetry)
|
|
|
196
223
|
// token is resolved per request, not per attempt; with the ceiling above the whole loop is
|
|
197
224
|
// bounded well under an IMS token's lifetime, so mid-loop expiry is a non-issue.
|
|
198
225
|
const forAttempt = () => (input instanceof Request ? input.clone() : input);
|
|
226
|
+
// Resolve any caller-supplied AbortSignal once. openapi-fetch calls us with a Request whose
|
|
227
|
+
// own `.signal` reflects a caller `signal` option; a bare-URL fetch may carry it on `init`.
|
|
228
|
+
const callerSignal = init?.signal ?? (input instanceof Request ? input.signal : undefined);
|
|
199
229
|
let lastResponse;
|
|
200
230
|
let lastError;
|
|
201
231
|
let nextDelayMs = 0;
|
|
@@ -215,8 +245,9 @@ export function createRetryingFetch(baseFetch, maxRetries, baseDelayMs, onRetry)
|
|
|
215
245
|
await sleep(nextDelayMs);
|
|
216
246
|
}
|
|
217
247
|
try {
|
|
248
|
+
const attemptInit = withDeadline(init, callerSignal, requestTimeoutMs);
|
|
218
249
|
// eslint-disable-next-line no-await-in-loop
|
|
219
|
-
const response = await baseFetch(forAttempt(),
|
|
250
|
+
const response = await baseFetch(forAttempt(), attemptInit);
|
|
220
251
|
if (!isRetryableStatus(method, response.status)) {
|
|
221
252
|
return response;
|
|
222
253
|
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// @ts-check
|
|
14
|
+
|
|
15
|
+
import { createSerenityProjectEngineApiClient } from './client.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {import('./client.js').SerenityProjectEngineApiClientOptions}
|
|
19
|
+
* SerenityProjectEngineApiClientOptions
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Intent-named facade over the raw {@link createSerenityProjectEngineApiClient} openapi-fetch
|
|
24
|
+
* client. It wraps ONLY the 28 in-spec Project Engine operations that spacecat-api-service
|
|
25
|
+
* consumes, behind verb+resource method names, so consumers depend on this seam rather than the
|
|
26
|
+
* raw client and its literal path strings. Each method is THIN: it forwards the caller's
|
|
27
|
+
* openapi-fetch `init` (params.path/query, body) to `client.<METHOD>('<literal path>', init)` and
|
|
28
|
+
* routes the result through the single {@link unwrap} error seam. There is deliberately NO caching,
|
|
29
|
+
* redaction, error→HTTP translation, or composite/convenience method here — all consumer-owned per
|
|
30
|
+
* ADR-0001. The remaining generated operations stay reachable via the raw client; add a facade
|
|
31
|
+
* method only when a real second consumer needs one.
|
|
32
|
+
*
|
|
33
|
+
* @param {SerenityProjectEngineApiClientOptions} options The SAME options as the raw client — the
|
|
34
|
+
* facade adds none of its own. Builds the underlying client via
|
|
35
|
+
* {@link createSerenityProjectEngineApiClient}.
|
|
36
|
+
* @returns {import('./index.js').SerenityProjectEngineTransport}
|
|
37
|
+
*/
|
|
38
|
+
export function createSerenityProjectEngineTransport(options) {
|
|
39
|
+
const client = createSerenityProjectEngineApiClient(options);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The SINGLE seam where a failed call becomes a throw. It awaits the openapi-fetch result
|
|
43
|
+
* promise here (so a network/timeout rejection also flows through this one point), and on a
|
|
44
|
+
* non-2xx response throws. The typed client never throws on an HTTP error — it resolves to
|
|
45
|
+
* `{ data, error, response }` with the parsed error body in `error` — so a non-2xx is turned
|
|
46
|
+
* into a throw here; a 2xx returns the parsed body (or null for an empty body).
|
|
47
|
+
*
|
|
48
|
+
* `status`, `method`, and the normalized `body` are computed locally at this site FIRST so the
|
|
49
|
+
* follow-up ticket LLMO-5978 can swap ONLY the `new Error(...)` line below for
|
|
50
|
+
* `new ProjectEngineApiError(status, method, body)` without reshaping anything else here.
|
|
51
|
+
*
|
|
52
|
+
* @template T
|
|
53
|
+
* @param {string} method the HTTP method, for the error message
|
|
54
|
+
* @param {Promise<{ data?: T, error?: unknown, response: Response }>} resultPromise the pending
|
|
55
|
+
* openapi-fetch result
|
|
56
|
+
* @returns {Promise<NonNullable<T> | null>} the parsed success body, or null for an empty body
|
|
57
|
+
* (an empty-body operation resolves with null, never undefined)
|
|
58
|
+
*/
|
|
59
|
+
async function unwrap(method, resultPromise) {
|
|
60
|
+
const { data, error, response } = await resultPromise;
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const { status } = response;
|
|
63
|
+
// openapi-fetch surfaces an empty error body as '' (not undefined); normalise it to null.
|
|
64
|
+
const rawBody = error ?? data ?? null;
|
|
65
|
+
// `body` is computed here but not consumed by the plain Error below. LLMO-5978 swaps ONLY
|
|
66
|
+
// the `new Error(...)` line for `new ProjectEngineApiError(status, method, body)` — status,
|
|
67
|
+
// method, and this normalized body are all already in scope, so nothing else here changes.
|
|
68
|
+
// eslint-disable-next-line no-unused-vars
|
|
69
|
+
const body = rawBody === '' ? null : rawBody;
|
|
70
|
+
// Message deliberately omits the request URL: it embeds path-param ids (workspace/
|
|
71
|
+
// project/etc.) that error-reporter and log consumers should not receive by default.
|
|
72
|
+
throw new Error(`Project Engine ${method} failed: ${status}`);
|
|
73
|
+
}
|
|
74
|
+
return data ?? null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
// ─── /v1 catalog ────────────────────────────────────────────────────────
|
|
79
|
+
/** GET /v1/languages — projects-admin-list-languages */
|
|
80
|
+
listLanguages(init) {
|
|
81
|
+
return unwrap('GET', client.GET('/v1/languages', init));
|
|
82
|
+
},
|
|
83
|
+
/** GET /v1/ai_models — ai-list-global-models */
|
|
84
|
+
listGlobalAiModels(init) {
|
|
85
|
+
return unwrap('GET', client.GET('/v1/ai_models', init));
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// ─── /v1 projects ───────────────────────────────────────────────────────
|
|
89
|
+
/** GET /v1/workspaces/{id}/projects — projects-list-projects */
|
|
90
|
+
listProjects(init) {
|
|
91
|
+
return unwrap('GET', client.GET('/v1/workspaces/{id}/projects', init));
|
|
92
|
+
},
|
|
93
|
+
/** POST /v1/workspaces/{id}/projects — projects-post-project */
|
|
94
|
+
createProject(init) {
|
|
95
|
+
return unwrap('POST', client.POST('/v1/workspaces/{id}/projects', init));
|
|
96
|
+
},
|
|
97
|
+
/** GET /v1/workspaces/{id}/projects/{project_id} — projects-get-project */
|
|
98
|
+
getProject(init) {
|
|
99
|
+
return unwrap('GET', client.GET('/v1/workspaces/{id}/projects/{project_id}', init));
|
|
100
|
+
},
|
|
101
|
+
/** PATCH /v1/workspaces/{id}/projects/{project_id} — projects-patch-project */
|
|
102
|
+
updateProject(init) {
|
|
103
|
+
return unwrap('PATCH', client.PATCH('/v1/workspaces/{id}/projects/{project_id}', init));
|
|
104
|
+
},
|
|
105
|
+
/** DELETE /v1/workspaces/{id}/projects/{project_id} — projects-delete-project */
|
|
106
|
+
deleteProject(init) {
|
|
107
|
+
return unwrap('DELETE', client.DELETE('/v1/workspaces/{id}/projects/{project_id}', init));
|
|
108
|
+
},
|
|
109
|
+
/** POST /v1/workspaces/{id}/projects/{project_id}/publish — projects-publish-project */
|
|
110
|
+
publishProject(init) {
|
|
111
|
+
return unwrap('POST', client.POST('/v1/workspaces/{id}/projects/{project_id}/publish', init));
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// ─── /v1 AI models + benchmarks ───────────────────────────────────────────
|
|
115
|
+
/** GET /v1/workspaces/{id}/projects/{project_id}/ai_models — ai-list-models */
|
|
116
|
+
listAiModels(init) {
|
|
117
|
+
return unwrap('GET', client.GET('/v1/workspaces/{id}/projects/{project_id}/ai_models', init));
|
|
118
|
+
},
|
|
119
|
+
/** DELETE /v1/workspaces/{id}/projects/{project_id}/ai_models — ai-delete-models */
|
|
120
|
+
deleteAiModels(init) {
|
|
121
|
+
return unwrap('DELETE', client.DELETE('/v1/workspaces/{id}/projects/{project_id}/ai_models', init));
|
|
122
|
+
},
|
|
123
|
+
/** GET /v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks — ai-list-benchmarks */
|
|
124
|
+
listBenchmarks(init) {
|
|
125
|
+
return unwrap('GET', client.GET('/v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks', init));
|
|
126
|
+
},
|
|
127
|
+
/**
|
|
128
|
+
* DELETE /v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks — ai-delete-benchmarks
|
|
129
|
+
*/
|
|
130
|
+
deleteBenchmarks(init) {
|
|
131
|
+
return unwrap('DELETE', client.DELETE('/v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks', init));
|
|
132
|
+
},
|
|
133
|
+
/**
|
|
134
|
+
* PUT /v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks/{benchmark_id}
|
|
135
|
+
* — ai-update-benchmark
|
|
136
|
+
*/
|
|
137
|
+
updateBenchmark(init) {
|
|
138
|
+
return unwrap('PUT', client.PUT('/v1/workspaces/{id}/projects/{project_id}/ai_models/benchmarks/{benchmark_id}', init));
|
|
139
|
+
},
|
|
140
|
+
/** PUT /v1/workspaces/{id}/projects/{project_id}/ci/competitors — ci-update-competitors */
|
|
141
|
+
updateCompetitors(init) {
|
|
142
|
+
return unwrap('PUT', client.PUT('/v1/workspaces/{id}/projects/{project_id}/ci/competitors', init));
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// ─── /v2 AI models + benchmarks ───────────────────────────────────────────
|
|
146
|
+
/** POST /v2/workspaces/{id}/projects/{project_id}/ai_models — aio-project-create-model */
|
|
147
|
+
createAioModel(init) {
|
|
148
|
+
return unwrap('POST', client.POST('/v2/workspaces/{id}/projects/{project_id}/ai_models', init));
|
|
149
|
+
},
|
|
150
|
+
/**
|
|
151
|
+
* POST /v2/workspaces/{id}/projects/{project_id}/ai_models/benchmarks — ai-create-benchmarks-v2
|
|
152
|
+
*/
|
|
153
|
+
createBenchmarks(init) {
|
|
154
|
+
return unwrap('POST', client.POST('/v2/workspaces/{id}/projects/{project_id}/ai_models/benchmarks', init));
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
// ─── /v2 brand URLs ───────────────────────────────────────────────────────
|
|
158
|
+
/**
|
|
159
|
+
* GET /v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls
|
|
160
|
+
* — aio-list-brand-urls
|
|
161
|
+
*/
|
|
162
|
+
listBrandUrls(init) {
|
|
163
|
+
return unwrap('GET', client.GET('/v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls', init));
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
* POST /v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls
|
|
167
|
+
* — aio-create-brand-urls
|
|
168
|
+
*/
|
|
169
|
+
createBrandUrls(init) {
|
|
170
|
+
return unwrap('POST', client.POST('/v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls', init));
|
|
171
|
+
},
|
|
172
|
+
/**
|
|
173
|
+
* DELETE /v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls
|
|
174
|
+
* — aio-delete-brand-urls
|
|
175
|
+
*/
|
|
176
|
+
deleteBrandUrls(init) {
|
|
177
|
+
return unwrap('DELETE', client.DELETE('/v2/workspaces/{id}/projects/{project_id}/aio/benchmarks/{benchmark_id}/brand_urls', init));
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// ─── /v2 AIO project state, prompts, tags ─────────────────────────────────
|
|
181
|
+
/**
|
|
182
|
+
* GET /v2/workspaces/{id}/projects/{project_id}/aio/init_status
|
|
183
|
+
* — aio-get-project-init-status-v2
|
|
184
|
+
*/
|
|
185
|
+
getProjectInitStatus(init) {
|
|
186
|
+
return unwrap('GET', client.GET('/v2/workspaces/{id}/projects/{project_id}/aio/init_status', init));
|
|
187
|
+
},
|
|
188
|
+
/** POST /v2/workspaces/{id}/projects/{project_id}/aio/prompts — aio-create-prompt-v2 */
|
|
189
|
+
createPrompts(init) {
|
|
190
|
+
return unwrap('POST', client.POST('/v2/workspaces/{id}/projects/{project_id}/aio/prompts', init));
|
|
191
|
+
},
|
|
192
|
+
/**
|
|
193
|
+
* DELETE /v2/workspaces/{id}/projects/{project_id}/aio/prompts — aio-delete-prompt-by-ids-v2
|
|
194
|
+
*/
|
|
195
|
+
deletePromptsByIds(init) {
|
|
196
|
+
return unwrap('DELETE', client.DELETE('/v2/workspaces/{id}/projects/{project_id}/aio/prompts', init));
|
|
197
|
+
},
|
|
198
|
+
/**
|
|
199
|
+
* POST /v2/workspaces/{id}/projects/{project_id}/aio/prompts/by_tags
|
|
200
|
+
* — aio-list-prompts-by-tag-ids
|
|
201
|
+
*/
|
|
202
|
+
listPromptsByTagIds(init) {
|
|
203
|
+
return unwrap('POST', client.POST('/v2/workspaces/{id}/projects/{project_id}/aio/prompts/by_tags', init));
|
|
204
|
+
},
|
|
205
|
+
/** PUT /v2/workspaces/{id}/projects/{project_id}/aio/prompts/tags — aio-update-prompts-batch */
|
|
206
|
+
updatePromptTags(init) {
|
|
207
|
+
return unwrap('PUT', client.PUT('/v2/workspaces/{id}/projects/{project_id}/aio/prompts/tags', init));
|
|
208
|
+
},
|
|
209
|
+
/**
|
|
210
|
+
* POST /v2/workspaces/{id}/projects/{project_id}/aio/prompts/{prompt_id}/rename
|
|
211
|
+
* — aio-rename-prompt
|
|
212
|
+
*/
|
|
213
|
+
renamePrompt(init) {
|
|
214
|
+
return unwrap('POST', client.POST('/v2/workspaces/{id}/projects/{project_id}/aio/prompts/{prompt_id}/rename', init));
|
|
215
|
+
},
|
|
216
|
+
/** GET /v2/workspaces/{id}/projects/{project_id}/aio/tags — aio-get-project-tags */
|
|
217
|
+
listProjectTags(init) {
|
|
218
|
+
return unwrap('GET', client.GET('/v2/workspaces/{id}/projects/{project_id}/aio/tags', init));
|
|
219
|
+
},
|
|
220
|
+
/** POST /v2/workspaces/{id}/projects/{project_id}/aio/tags — aio-create-project-tags */
|
|
221
|
+
createProjectTags(init) {
|
|
222
|
+
return unwrap('POST', client.POST('/v2/workspaces/{id}/projects/{project_id}/aio/tags', init));
|
|
223
|
+
},
|
|
224
|
+
/** PATCH /v2/workspaces/{id}/projects/{project_id}/aio/tags/{tag_id} — aio-update-tag */
|
|
225
|
+
updateProjectTag(init) {
|
|
226
|
+
return unwrap('PATCH', client.PATCH('/v2/workspaces/{id}/projects/{project_id}/aio/tags/{tag_id}', init));
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|