me2lon 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +62 -2
- package/dist/api.d.ts +35 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +286 -0
- package/dist/api.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +261 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +23 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +272 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +173 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +31 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/presentation.d.ts +12 -0
- package/dist/presentation.d.ts.map +1 -0
- package/dist/presentation.js +50 -0
- package/dist/presentation.js.map +1 -0
- package/dist/safe-output.d.ts +20 -0
- package/dist/safe-output.d.ts.map +1 -0
- package/dist/safe-output.js +770 -0
- package/dist/safe-output.js.map +1 -0
- package/dist/setup.d.ts +14 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +92 -0
- package/dist/setup.js.map +1 -0
- package/dist/skill.d.ts +2 -0
- package/dist/skill.d.ts.map +1 -0
- package/dist/skill.js +30 -0
- package/dist/skill.js.map +1 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/workspace-source.d.ts +11 -0
- package/dist/workspace-source.d.ts.map +1 -0
- package/dist/workspace-source.js +387 -0
- package/dist/workspace-source.js.map +1 -0
- package/package.json +43 -4
- package/skill/LICENSE +21 -0
- package/skill/SKILL.md +18 -0
- package/src/api.ts +426 -0
- package/src/cli.ts +305 -0
- package/src/client.ts +413 -0
- package/src/config.ts +252 -0
- package/src/errors.ts +40 -0
- package/src/index.ts +37 -0
- package/src/presentation.ts +39 -0
- package/src/safe-output.ts +826 -0
- package/src/setup.ts +95 -0
- package/src/skill.ts +22 -0
- package/src/types.ts +85 -0
- package/src/workspace-source.ts +526 -0
- package/tsconfig.build.json +18 -0
- package/tsconfig.json +14 -0
package/src/api.ts
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { Me2lonApiError, Me2lonClientError } from "./errors.js";
|
|
4
|
+
import { SOURCE_SCHEMA_VERSION, type JsonValue, type WorkspaceSource, type WorkspaceSnapshot } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const API_PREFIX = "/api/agent/v1";
|
|
7
|
+
const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
function transportError(error: unknown, signal: AbortSignal): Me2lonClientError {
|
|
10
|
+
if (signal.aborted || (error instanceof Error && error.name === "TimeoutError")) {
|
|
11
|
+
return new Me2lonClientError(
|
|
12
|
+
"ME2LON_API_TIMEOUT",
|
|
13
|
+
"The Me2lon API did not finish responding within 30 seconds.",
|
|
14
|
+
{ cause: error },
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
const cause = error instanceof Error ? error.cause : undefined;
|
|
18
|
+
const code = cause && typeof cause === "object" && "code" in cause ? cause.code : undefined;
|
|
19
|
+
const detail = code === "ECONNREFUSED" ? " The connection was refused."
|
|
20
|
+
: code === "ENOTFOUND" || code === "EAI_AGAIN" ? " The server hostname could not be resolved."
|
|
21
|
+
: "";
|
|
22
|
+
return new Me2lonClientError(
|
|
23
|
+
"ME2LON_API_UNREACHABLE",
|
|
24
|
+
`The connection to the Me2lon API failed.${detail}`,
|
|
25
|
+
{ cause: error },
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function encodeSegment(value: string): string {
|
|
30
|
+
if (!value) {
|
|
31
|
+
throw new Me2lonClientError(
|
|
32
|
+
"ME2LON_API_INPUT_INVALID",
|
|
33
|
+
"A Me2lon API resource id cannot be empty.",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return encodeURIComponent(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function idempotencyKey(kind: string, value: unknown): string {
|
|
40
|
+
const digest = createHash("sha256")
|
|
41
|
+
.update(JSON.stringify(value))
|
|
42
|
+
.digest("hex");
|
|
43
|
+
return `me2lon-${kind}-${digest}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** One logical mutation attempt; reuse it for every retry within that attempt. */
|
|
47
|
+
export function mutationAttemptId(): string {
|
|
48
|
+
return randomUUID();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function mutationIdempotencyKey(
|
|
52
|
+
kind: string,
|
|
53
|
+
attemptId: string,
|
|
54
|
+
value: unknown,
|
|
55
|
+
): string {
|
|
56
|
+
return idempotencyKey(kind, { attemptId, request: value });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assertIdempotencyKey(value: string): void {
|
|
60
|
+
if (value.length > 255 || !/^[A-Za-z0-9._:-]+$/u.test(value)) {
|
|
61
|
+
throw new Me2lonClientError(
|
|
62
|
+
"ME2LON_IDEMPOTENCY_KEY_INVALID",
|
|
63
|
+
"The generated Me2lon idempotency key has an invalid shape.",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function asJsonObject(value: unknown): Record<string, unknown> {
|
|
69
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
70
|
+
throw new Me2lonClientError(
|
|
71
|
+
"ME2LON_API_RESPONSE_INVALID",
|
|
72
|
+
"The Me2lon API returned an invalid JSON response.",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return value as Record<string, unknown>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type ApiRequestOptions = Readonly<{
|
|
79
|
+
method?: "GET" | "POST";
|
|
80
|
+
body?: JsonValue | Record<string, unknown>;
|
|
81
|
+
idempotencyKey?: string;
|
|
82
|
+
sourceSchema?: boolean | string;
|
|
83
|
+
}>;
|
|
84
|
+
|
|
85
|
+
export class Me2lonAgentApi {
|
|
86
|
+
readonly #baseUrl: URL;
|
|
87
|
+
readonly #token: string;
|
|
88
|
+
readonly #repositoryId: string | undefined;
|
|
89
|
+
readonly #fetch: typeof fetch;
|
|
90
|
+
|
|
91
|
+
constructor(
|
|
92
|
+
baseUrl: URL,
|
|
93
|
+
token: string,
|
|
94
|
+
repositoryId?: string,
|
|
95
|
+
fetchImplementation: typeof fetch = fetch,
|
|
96
|
+
) {
|
|
97
|
+
this.#baseUrl = baseUrl;
|
|
98
|
+
this.#token = token;
|
|
99
|
+
this.#repositoryId = repositoryId;
|
|
100
|
+
this.#fetch = fetchImplementation;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async request(
|
|
104
|
+
path: string,
|
|
105
|
+
options: ApiRequestOptions = {},
|
|
106
|
+
): Promise<Record<string, unknown>> {
|
|
107
|
+
// Retry only transport failures, and only when replay cannot create new
|
|
108
|
+
// work. Reuse the exact options (including the mutation's idempotency key).
|
|
109
|
+
const canRetry = (options.method ?? "GET") === "GET" || Boolean(options.idempotencyKey);
|
|
110
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
111
|
+
try {
|
|
112
|
+
return await this.#requestOnce(path, options);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (!(error instanceof Me2lonClientError) ||
|
|
115
|
+
!["ME2LON_API_TIMEOUT", "ME2LON_API_UNREACHABLE"].includes(error.code)) throw error;
|
|
116
|
+
if (canRetry && attempt === 0) continue;
|
|
117
|
+
const submissionHint = options.method === "POST" && options.idempotencyKey
|
|
118
|
+
? " The submission may already have been accepted. Check the project dashboard before starting another job."
|
|
119
|
+
: "";
|
|
120
|
+
throw new Me2lonClientError(error.code, error.message + submissionHint, { cause: error });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async #requestOnce(
|
|
126
|
+
path: string,
|
|
127
|
+
options: ApiRequestOptions,
|
|
128
|
+
): Promise<Record<string, unknown>> {
|
|
129
|
+
const method = options.method ?? "GET";
|
|
130
|
+
const headers = new Headers({
|
|
131
|
+
Accept: "application/json",
|
|
132
|
+
"User-Agent": "me2lon-agent/0.1.0",
|
|
133
|
+
});
|
|
134
|
+
if (this.#token) headers.set("Authorization", `Bearer ${this.#token}`);
|
|
135
|
+
if (this.#repositoryId) {
|
|
136
|
+
headers.set("X-Me2lon-Repository-Id", this.#repositoryId);
|
|
137
|
+
}
|
|
138
|
+
let body: string | undefined;
|
|
139
|
+
if (options.body !== undefined) {
|
|
140
|
+
headers.set("Content-Type", "application/json");
|
|
141
|
+
body = JSON.stringify(options.body);
|
|
142
|
+
}
|
|
143
|
+
if (options.idempotencyKey) {
|
|
144
|
+
assertIdempotencyKey(options.idempotencyKey);
|
|
145
|
+
headers.set("Idempotency-Key", options.idempotencyKey);
|
|
146
|
+
}
|
|
147
|
+
if (options.sourceSchema) {
|
|
148
|
+
headers.set("X-Me2lon-Source-Schema", typeof options.sourceSchema === "string" ? options.sourceSchema : SOURCE_SCHEMA_VERSION);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const signal = AbortSignal.timeout(30_000);
|
|
152
|
+
let response: Response;
|
|
153
|
+
try {
|
|
154
|
+
response = await this.#fetch(new URL(`${API_PREFIX}${path}`, this.#baseUrl), {
|
|
155
|
+
method,
|
|
156
|
+
headers,
|
|
157
|
+
...(body === undefined ? {} : { body }),
|
|
158
|
+
redirect: "error",
|
|
159
|
+
signal,
|
|
160
|
+
});
|
|
161
|
+
} catch (error) {
|
|
162
|
+
throw transportError(error, signal);
|
|
163
|
+
}
|
|
164
|
+
const requestId = response.headers.get("x-request-id") ?? undefined;
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
await response.body?.cancel().catch(() => undefined);
|
|
167
|
+
throw new Me2lonApiError(response.status, requestId);
|
|
168
|
+
}
|
|
169
|
+
if (response.status === 204) {
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
173
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_RESPONSE_BYTES) {
|
|
174
|
+
await response.body?.cancel().catch(() => undefined);
|
|
175
|
+
throw new Me2lonClientError(
|
|
176
|
+
"ME2LON_API_RESPONSE_LIMIT",
|
|
177
|
+
"The Me2lon API response exceeded the client safety limit.",
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
let bytes: Uint8Array;
|
|
181
|
+
try {
|
|
182
|
+
bytes = new Uint8Array(await response.arrayBuffer());
|
|
183
|
+
} catch (error) {
|
|
184
|
+
throw transportError(error, signal);
|
|
185
|
+
}
|
|
186
|
+
if (bytes.byteLength > MAX_RESPONSE_BYTES) {
|
|
187
|
+
throw new Me2lonClientError(
|
|
188
|
+
"ME2LON_API_RESPONSE_LIMIT",
|
|
189
|
+
"The Me2lon API response exceeded the client safety limit.",
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
return asJsonObject(JSON.parse(new TextDecoder().decode(bytes)));
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error instanceof Me2lonClientError) {
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
throw new Me2lonClientError(
|
|
199
|
+
"ME2LON_API_RESPONSE_INVALID",
|
|
200
|
+
"The Me2lon API returned invalid JSON.",
|
|
201
|
+
{ cause: error },
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
capabilities(): Promise<Record<string, unknown>> {
|
|
207
|
+
return this.request("/capabilities");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
repositories(): Promise<Record<string, unknown>> {
|
|
211
|
+
return this.request("/repositories");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
createWorkspace(
|
|
215
|
+
repositoryId: string,
|
|
216
|
+
attemptId = mutationAttemptId(),
|
|
217
|
+
): Promise<Record<string, unknown>> {
|
|
218
|
+
const body = { repositoryId };
|
|
219
|
+
return this.request("/workspaces", {
|
|
220
|
+
method: "POST",
|
|
221
|
+
body,
|
|
222
|
+
idempotencyKey: mutationIdempotencyKey("workspace", attemptId, body),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
createRevision(
|
|
227
|
+
workspaceId: string,
|
|
228
|
+
source: WorkspaceSource,
|
|
229
|
+
attemptId = mutationAttemptId(),
|
|
230
|
+
): Promise<Record<string, unknown>> {
|
|
231
|
+
const body = {
|
|
232
|
+
baseCommitSha: source.baseCommitSha,
|
|
233
|
+
changes: source.changes,
|
|
234
|
+
};
|
|
235
|
+
return this.request(`/workspaces/${encodeSegment(workspaceId)}/revisions`, {
|
|
236
|
+
method: "POST",
|
|
237
|
+
body,
|
|
238
|
+
idempotencyKey: mutationIdempotencyKey("revision", attemptId, {
|
|
239
|
+
workspaceId,
|
|
240
|
+
digest: source.digest,
|
|
241
|
+
}),
|
|
242
|
+
sourceSchema: true,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
createCheckJob(
|
|
247
|
+
workspaceRevisionId: string,
|
|
248
|
+
attemptId = mutationAttemptId(),
|
|
249
|
+
): Promise<Record<string, unknown>> {
|
|
250
|
+
const body = { workspaceRevisionId };
|
|
251
|
+
return this.request("/check-jobs", {
|
|
252
|
+
method: "POST",
|
|
253
|
+
body,
|
|
254
|
+
idempotencyKey: mutationIdempotencyKey("check", attemptId, body),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
createAnalysisJob(workspaceRevisionId: string, projectRoot?: string, attemptId = mutationAttemptId()): Promise<Record<string, unknown>> {
|
|
259
|
+
const body = { workspaceRevisionId, ...(projectRoot === undefined ? {} : { projectRoot }) };
|
|
260
|
+
return this.request('/analysis-jobs', { method: 'POST', body,
|
|
261
|
+
idempotencyKey: mutationIdempotencyKey('analysis', attemptId, body) });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
createSnapshot(workspaceId: string, source: WorkspaceSnapshot, previousRevisionId: string | undefined, attemptId: string): Promise<Record<string, unknown>> {
|
|
265
|
+
const body = {
|
|
266
|
+
schemaVersion: source.schemaVersion, baseCommitSha: source.baseCommitSha,
|
|
267
|
+
branchLabel: source.branchLabel, files: source.files,
|
|
268
|
+
...(previousRevisionId ? { previousRevisionId } : {}),
|
|
269
|
+
};
|
|
270
|
+
return this.request(`/workspaces/${encodeSegment(workspaceId)}/revisions`, {
|
|
271
|
+
method: "POST", body, sourceSchema: source.schemaVersion,
|
|
272
|
+
idempotencyKey: mutationIdempotencyKey("snapshot", attemptId, { workspaceId, ...body }),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
job(jobId: string): Promise<Record<string, unknown>> {
|
|
277
|
+
return this.request(`/jobs/${encodeSegment(jobId)}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
jobDiagnostics(
|
|
281
|
+
jobId: string,
|
|
282
|
+
cursor = 0,
|
|
283
|
+
limit = 500,
|
|
284
|
+
): Promise<Record<string, unknown>> {
|
|
285
|
+
if (
|
|
286
|
+
!Number.isSafeInteger(cursor) ||
|
|
287
|
+
cursor < 0 ||
|
|
288
|
+
!Number.isSafeInteger(limit) ||
|
|
289
|
+
limit < 1 ||
|
|
290
|
+
limit > 500
|
|
291
|
+
) {
|
|
292
|
+
throw new Me2lonClientError(
|
|
293
|
+
"ME2LON_API_INPUT_INVALID",
|
|
294
|
+
"Me2lon diagnostic pagination is outside the supported range.",
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
return this.request(
|
|
298
|
+
`/jobs/${encodeSegment(jobId)}/diagnostics?cursor=${cursor}&limit=${limit}`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
createTranslationJob(
|
|
303
|
+
checkJobId: string,
|
|
304
|
+
projectId: string,
|
|
305
|
+
attemptId = mutationAttemptId(),
|
|
306
|
+
): Promise<Record<string, unknown>> {
|
|
307
|
+
const body = { checkJobId, projectId };
|
|
308
|
+
return this.request("/translation-jobs", {
|
|
309
|
+
method: "POST",
|
|
310
|
+
body,
|
|
311
|
+
idempotencyKey: mutationIdempotencyKey("translation", attemptId, body),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
createVerificationJob(
|
|
316
|
+
translationJobId: string,
|
|
317
|
+
expectedPolicySetDigest: string,
|
|
318
|
+
attemptId = mutationAttemptId(),
|
|
319
|
+
): Promise<Record<string, unknown>> {
|
|
320
|
+
const body = { translationJobId, expectedPolicySetDigest };
|
|
321
|
+
return this.request("/verification-jobs", {
|
|
322
|
+
method: "POST",
|
|
323
|
+
body,
|
|
324
|
+
idempotencyKey: mutationIdempotencyKey("verification", attemptId, body),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
policyUnits(repositoryId: string): Promise<Record<string, unknown>> {
|
|
329
|
+
return this.request(
|
|
330
|
+
`/repositories/${encodeSegment(repositoryId)}/policy-units`,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
policyUnit(policyUnitId: string): Promise<Record<string, unknown>> {
|
|
335
|
+
return this.request(`/policy-units/${encodeSegment(policyUnitId)}`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
createPolicyDraft(
|
|
339
|
+
policyUnitId: string,
|
|
340
|
+
expectedPolicySetDigest: string,
|
|
341
|
+
attemptId = mutationAttemptId(),
|
|
342
|
+
): Promise<Record<string, unknown>> {
|
|
343
|
+
const body = { expectedPolicySetDigest };
|
|
344
|
+
return this.request(`/policy-units/${encodeSegment(policyUnitId)}/drafts`, {
|
|
345
|
+
method: "POST",
|
|
346
|
+
body,
|
|
347
|
+
idempotencyKey: mutationIdempotencyKey(
|
|
348
|
+
"policy-draft",
|
|
349
|
+
attemptId,
|
|
350
|
+
{ policyUnitId, ...body },
|
|
351
|
+
),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
createPolicyRevision(
|
|
356
|
+
draftId: string,
|
|
357
|
+
expectedPolicySetDigest: string,
|
|
358
|
+
policy: JsonValue,
|
|
359
|
+
rationale?: string,
|
|
360
|
+
attemptId = mutationAttemptId(),
|
|
361
|
+
): Promise<Record<string, unknown>> {
|
|
362
|
+
const body = {
|
|
363
|
+
expectedPolicySetDigest,
|
|
364
|
+
policy,
|
|
365
|
+
...(rationale ? { rationale } : {}),
|
|
366
|
+
};
|
|
367
|
+
return this.request(`/policy-drafts/${encodeSegment(draftId)}/revisions`, {
|
|
368
|
+
method: "POST",
|
|
369
|
+
body,
|
|
370
|
+
idempotencyKey: mutationIdempotencyKey(
|
|
371
|
+
"policy-revision",
|
|
372
|
+
attemptId,
|
|
373
|
+
{ draftId, ...body },
|
|
374
|
+
),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
validatePolicyRevision(
|
|
379
|
+
revisionId: string,
|
|
380
|
+
attemptId = mutationAttemptId(),
|
|
381
|
+
): Promise<Record<string, unknown>> {
|
|
382
|
+
return this.request(
|
|
383
|
+
`/policy-revisions/${encodeSegment(revisionId)}/validate`,
|
|
384
|
+
{
|
|
385
|
+
method: "POST",
|
|
386
|
+
body: {},
|
|
387
|
+
idempotencyKey: mutationIdempotencyKey(
|
|
388
|
+
"policy-validation",
|
|
389
|
+
attemptId,
|
|
390
|
+
{ revisionId },
|
|
391
|
+
),
|
|
392
|
+
},
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
policyRevisionDiff(revisionId: string): Promise<Record<string, unknown>> {
|
|
397
|
+
return this.request(`/policy-revisions/${encodeSegment(revisionId)}/diff`);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
submitPolicyRevision(
|
|
401
|
+
revisionId: string,
|
|
402
|
+
expectedPolicySetDigest: string,
|
|
403
|
+
attemptId = mutationAttemptId(),
|
|
404
|
+
): Promise<Record<string, unknown>> {
|
|
405
|
+
const body = { expectedPolicySetDigest };
|
|
406
|
+
return this.request(
|
|
407
|
+
`/policy-revisions/${encodeSegment(revisionId)}/submit-for-review`,
|
|
408
|
+
{
|
|
409
|
+
method: "POST",
|
|
410
|
+
body,
|
|
411
|
+
idempotencyKey: mutationIdempotencyKey("policy-submit", attemptId, {
|
|
412
|
+
revisionId,
|
|
413
|
+
...body,
|
|
414
|
+
}),
|
|
415
|
+
},
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
cancelJob(jobId: string): Promise<Record<string, unknown>> {
|
|
420
|
+
return this.request(`/jobs/${encodeSegment(jobId)}/cancel`, {
|
|
421
|
+
method: "POST",
|
|
422
|
+
body: {},
|
|
423
|
+
idempotencyKey: idempotencyKey("job-cancel", { jobId }),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|