@smithers-orchestrator/gateway 0.16.9
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/openapi.yaml +6849 -0
- package/package.json +39 -0
- package/src/auth/scopes.ts +88 -0
- package/src/index.ts +2 -0
- package/src/rpc/index.ts +622 -0
package/src/rpc/index.ts
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
import type { GatewayScope } from "../auth/scopes.ts";
|
|
2
|
+
import { GATEWAY_SCOPE_VALUES } from "../auth/scopes.ts";
|
|
3
|
+
|
|
4
|
+
export const SMITHERS_API_VERSION = "v1" as const;
|
|
5
|
+
export const GATEWAY_EVENT_WINDOW_DEFAULT = 10_000;
|
|
6
|
+
|
|
7
|
+
export type SmithersApiVersion = typeof SMITHERS_API_VERSION;
|
|
8
|
+
|
|
9
|
+
export type JsonSchema = {
|
|
10
|
+
readonly type?: string | readonly string[];
|
|
11
|
+
readonly description?: string;
|
|
12
|
+
readonly enum?: readonly unknown[];
|
|
13
|
+
readonly const?: unknown;
|
|
14
|
+
readonly format?: string;
|
|
15
|
+
readonly minimum?: number;
|
|
16
|
+
readonly maximum?: number;
|
|
17
|
+
readonly default?: unknown;
|
|
18
|
+
readonly nullable?: boolean;
|
|
19
|
+
readonly items?: JsonSchema;
|
|
20
|
+
readonly properties?: Record<string, JsonSchema>;
|
|
21
|
+
readonly required?: readonly string[];
|
|
22
|
+
readonly additionalProperties?: boolean | JsonSchema;
|
|
23
|
+
readonly oneOf?: readonly JsonSchema[];
|
|
24
|
+
readonly anyOf?: readonly JsonSchema[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type GatewayRpcErrorCode =
|
|
28
|
+
| "InvalidRequest"
|
|
29
|
+
| "InvalidInput"
|
|
30
|
+
| "Unauthorized"
|
|
31
|
+
| "Forbidden"
|
|
32
|
+
| "RunNotFound"
|
|
33
|
+
| "NodeNotFound"
|
|
34
|
+
| "IterationNotFound"
|
|
35
|
+
| "NodeHasNoOutput"
|
|
36
|
+
| "FrameOutOfRange"
|
|
37
|
+
| "SeqOutOfRange"
|
|
38
|
+
| "Busy"
|
|
39
|
+
| "RateLimited"
|
|
40
|
+
| "PayloadTooLarge"
|
|
41
|
+
| "BackpressureDisconnect"
|
|
42
|
+
| "UnsupportedSandbox"
|
|
43
|
+
| "VcsError"
|
|
44
|
+
| "RewindFailed"
|
|
45
|
+
| "Internal";
|
|
46
|
+
|
|
47
|
+
export type GatewayRpcErrorDefinition = {
|
|
48
|
+
readonly version: SmithersApiVersion;
|
|
49
|
+
readonly code: GatewayRpcErrorCode;
|
|
50
|
+
readonly httpStatus: number;
|
|
51
|
+
readonly description: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type GatewayRpcDefinition = {
|
|
55
|
+
readonly version: SmithersApiVersion;
|
|
56
|
+
readonly method: GatewayRpcMethod;
|
|
57
|
+
readonly title: string;
|
|
58
|
+
readonly description: string;
|
|
59
|
+
readonly maturity: "stable";
|
|
60
|
+
readonly transport: "http" | "websocket" | "http+websocket";
|
|
61
|
+
readonly requiredScope: GatewayScope;
|
|
62
|
+
readonly requestSchema: JsonSchema;
|
|
63
|
+
readonly responseSchema: JsonSchema;
|
|
64
|
+
readonly errors: readonly GatewayRpcErrorCode[];
|
|
65
|
+
readonly exampleRequest: unknown;
|
|
66
|
+
readonly exampleResponse: unknown;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type GatewayRpcMethod =
|
|
70
|
+
| "launchRun"
|
|
71
|
+
| "resumeRun"
|
|
72
|
+
| "cancelRun"
|
|
73
|
+
| "hijackRun"
|
|
74
|
+
| "rewindRun"
|
|
75
|
+
| "submitApproval"
|
|
76
|
+
| "submitSignal"
|
|
77
|
+
| "getRun"
|
|
78
|
+
| "listRuns"
|
|
79
|
+
| "streamRunEvents"
|
|
80
|
+
| "streamDevTools"
|
|
81
|
+
| "getNodeOutput"
|
|
82
|
+
| "getNodeDiff"
|
|
83
|
+
| "cronList"
|
|
84
|
+
| "cronCreate"
|
|
85
|
+
| "cronDelete"
|
|
86
|
+
| "cronRun";
|
|
87
|
+
|
|
88
|
+
export type LaunchRunRequest = {
|
|
89
|
+
workflow: string;
|
|
90
|
+
input?: Record<string, unknown>;
|
|
91
|
+
options?: {
|
|
92
|
+
runId?: string;
|
|
93
|
+
idempotencyKey?: string;
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type LaunchRunResponse = {
|
|
98
|
+
runId: string;
|
|
99
|
+
workflow: string;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type ResumeRunRequest = {
|
|
103
|
+
runId: string;
|
|
104
|
+
options?: {
|
|
105
|
+
force?: boolean;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type ResumeRunResponse = {
|
|
110
|
+
runId: string;
|
|
111
|
+
status: "resume_requested" | "already_terminal";
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export type CancelRunRequest = {
|
|
115
|
+
runId: string;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export type CancelRunResponse = {
|
|
119
|
+
runId: string;
|
|
120
|
+
status: "cancelling";
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export type HijackRunRequest = {
|
|
124
|
+
runId: string;
|
|
125
|
+
options?: Record<string, unknown>;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export type HijackRunResponse = {
|
|
129
|
+
runId: string;
|
|
130
|
+
status: "hijack-ready";
|
|
131
|
+
sessionId: string;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type RewindRunRequest = {
|
|
135
|
+
runId: string;
|
|
136
|
+
frameNo: number;
|
|
137
|
+
confirm: true;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type SubmitApprovalRequest = {
|
|
141
|
+
runId: string;
|
|
142
|
+
nodeId: string;
|
|
143
|
+
iteration?: number;
|
|
144
|
+
decision: {
|
|
145
|
+
approved: boolean;
|
|
146
|
+
value?: unknown;
|
|
147
|
+
note?: string;
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export type SubmitApprovalResponse = {
|
|
152
|
+
runId: string;
|
|
153
|
+
nodeId: string;
|
|
154
|
+
iteration: number;
|
|
155
|
+
approved: boolean;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export type SubmitSignalRequest = {
|
|
159
|
+
runId: string;
|
|
160
|
+
correlationKey: string;
|
|
161
|
+
payload?: unknown;
|
|
162
|
+
signalName?: string;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export type GetRunRequest = {
|
|
166
|
+
runId: string;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export type ListRunsRequest = {
|
|
170
|
+
filter?: {
|
|
171
|
+
status?: string;
|
|
172
|
+
limit?: number;
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export type StreamRunEventsRequest = {
|
|
177
|
+
runId: string;
|
|
178
|
+
afterSeq?: number;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export type StreamRunEventsResponse = {
|
|
182
|
+
streamId: string;
|
|
183
|
+
runId: string;
|
|
184
|
+
afterSeq: number | null;
|
|
185
|
+
currentSeq: number;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export type StreamDevToolsRequest = {
|
|
189
|
+
runId: string;
|
|
190
|
+
afterSeq?: number;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export type NodeRequest = {
|
|
194
|
+
runId: string;
|
|
195
|
+
nodeId: string;
|
|
196
|
+
iteration?: number;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export type CronListRequest = {
|
|
200
|
+
filter?: {
|
|
201
|
+
workflow?: string;
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export type CronCreateRequest = {
|
|
206
|
+
workflow: string;
|
|
207
|
+
pattern: string;
|
|
208
|
+
cronId?: string;
|
|
209
|
+
enabled?: boolean;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export type CronDeleteRequest = {
|
|
213
|
+
cronId: string;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export type CronRunRequest = {
|
|
217
|
+
cronId?: string;
|
|
218
|
+
workflow?: string;
|
|
219
|
+
input?: Record<string, unknown>;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const stringSchema = (description: string): JsonSchema => ({ type: "string", description });
|
|
223
|
+
const booleanSchema = (description: string): JsonSchema => ({ type: "boolean", description });
|
|
224
|
+
const integerSchema = (description: string, minimum = 0): JsonSchema => ({
|
|
225
|
+
type: "integer",
|
|
226
|
+
minimum,
|
|
227
|
+
description,
|
|
228
|
+
});
|
|
229
|
+
const objectSchema = (
|
|
230
|
+
properties: Record<string, JsonSchema>,
|
|
231
|
+
required: readonly string[] = [],
|
|
232
|
+
description?: string,
|
|
233
|
+
additionalProperties: boolean | JsonSchema = false,
|
|
234
|
+
): JsonSchema => ({
|
|
235
|
+
type: "object",
|
|
236
|
+
...(description ? { description } : {}),
|
|
237
|
+
properties,
|
|
238
|
+
required,
|
|
239
|
+
additionalProperties,
|
|
240
|
+
});
|
|
241
|
+
const arraySchema = (items: JsonSchema, description: string): JsonSchema => ({
|
|
242
|
+
type: "array",
|
|
243
|
+
description,
|
|
244
|
+
items,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
export const anyJsonSchema: JsonSchema = {
|
|
248
|
+
description: "Any JSON value.",
|
|
249
|
+
nullable: true,
|
|
250
|
+
oneOf: [
|
|
251
|
+
{ type: "object", additionalProperties: true },
|
|
252
|
+
{ type: "array", items: { nullable: true } },
|
|
253
|
+
{ type: "string" },
|
|
254
|
+
{ type: "number" },
|
|
255
|
+
{ type: "integer" },
|
|
256
|
+
{ type: "boolean" },
|
|
257
|
+
{ type: "null" },
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const runId = stringSchema("Stable run identifier.");
|
|
262
|
+
const workflow = stringSchema("Registered Gateway workflow key.");
|
|
263
|
+
const nodeId = stringSchema("Workflow node id.");
|
|
264
|
+
const iteration = integerSchema("Node iteration.", 0);
|
|
265
|
+
const afterSeq = integerSchema("Replay events with sequence numbers greater than this value.", 0);
|
|
266
|
+
const runSummary = objectSchema(
|
|
267
|
+
{
|
|
268
|
+
runId,
|
|
269
|
+
workflowKey: workflow,
|
|
270
|
+
status: stringSchema("Current run status."),
|
|
271
|
+
createdAtMs: integerSchema("Unix epoch milliseconds.", 0),
|
|
272
|
+
},
|
|
273
|
+
["runId", "status"],
|
|
274
|
+
"Run summary view.",
|
|
275
|
+
true,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
export const GATEWAY_RPC_ERRORS: Record<GatewayRpcErrorCode, GatewayRpcErrorDefinition> = {
|
|
279
|
+
InvalidRequest: { version: SMITHERS_API_VERSION, code: "InvalidRequest", httpStatus: 400, description: "The request shape is invalid." },
|
|
280
|
+
InvalidInput: { version: SMITHERS_API_VERSION, code: "InvalidInput", httpStatus: 400, description: "The request input failed validation." },
|
|
281
|
+
Unauthorized: { version: SMITHERS_API_VERSION, code: "Unauthorized", httpStatus: 401, description: "Authentication failed or the token expired." },
|
|
282
|
+
Forbidden: { version: SMITHERS_API_VERSION, code: "Forbidden", httpStatus: 403, description: "The token is missing the required scope." },
|
|
283
|
+
RunNotFound: { version: SMITHERS_API_VERSION, code: "RunNotFound", httpStatus: 404, description: "The run does not exist." },
|
|
284
|
+
NodeNotFound: { version: SMITHERS_API_VERSION, code: "NodeNotFound", httpStatus: 404, description: "The node does not exist on the run." },
|
|
285
|
+
IterationNotFound: { version: SMITHERS_API_VERSION, code: "IterationNotFound", httpStatus: 404, description: "The requested node iteration does not exist." },
|
|
286
|
+
NodeHasNoOutput: { version: SMITHERS_API_VERSION, code: "NodeHasNoOutput", httpStatus: 404, description: "The node has not produced output." },
|
|
287
|
+
FrameOutOfRange: { version: SMITHERS_API_VERSION, code: "FrameOutOfRange", httpStatus: 400, description: "The requested frame is outside the available range." },
|
|
288
|
+
SeqOutOfRange: { version: SMITHERS_API_VERSION, code: "SeqOutOfRange", httpStatus: 400, description: "The requested stream sequence is in the future." },
|
|
289
|
+
Busy: { version: SMITHERS_API_VERSION, code: "Busy", httpStatus: 409, description: "Another conflicting mutation is in progress." },
|
|
290
|
+
RateLimited: { version: SMITHERS_API_VERSION, code: "RateLimited", httpStatus: 429, description: "The caller exceeded a configured quota." },
|
|
291
|
+
PayloadTooLarge: { version: SMITHERS_API_VERSION, code: "PayloadTooLarge", httpStatus: 413, description: "The response exceeds the configured payload limit." },
|
|
292
|
+
BackpressureDisconnect: { version: SMITHERS_API_VERSION, code: "BackpressureDisconnect", httpStatus: 429, description: "A stream subscriber exceeded the bounded outbound queue." },
|
|
293
|
+
UnsupportedSandbox: { version: SMITHERS_API_VERSION, code: "UnsupportedSandbox", httpStatus: 501, description: "A sandbox cannot be rewound safely." },
|
|
294
|
+
VcsError: { version: SMITHERS_API_VERSION, code: "VcsError", httpStatus: 500, description: "A version-control operation failed." },
|
|
295
|
+
RewindFailed: { version: SMITHERS_API_VERSION, code: "RewindFailed", httpStatus: 500, description: "The rewind failed and the run may need attention." },
|
|
296
|
+
Internal: { version: SMITHERS_API_VERSION, code: "Internal", httpStatus: 500, description: "The Gateway encountered an internal error." },
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export const GATEWAY_RPC_LEGACY_METHOD_ALIASES: Record<string, GatewayRpcMethod> = {
|
|
300
|
+
"runs.create": "launchRun",
|
|
301
|
+
"runs.get": "getRun",
|
|
302
|
+
"runs.list": "listRuns",
|
|
303
|
+
"runs.cancel": "cancelRun",
|
|
304
|
+
"approvals.decide": "submitApproval",
|
|
305
|
+
"signals.send": "submitSignal",
|
|
306
|
+
jumpToFrame: "rewindRun",
|
|
307
|
+
"devtools.jumpToFrame": "rewindRun",
|
|
308
|
+
"devtools.getNodeOutput": "getNodeOutput",
|
|
309
|
+
"devtools.getNodeDiff": "getNodeDiff",
|
|
310
|
+
"cron.list": "cronList",
|
|
311
|
+
"cron.add": "cronCreate",
|
|
312
|
+
"cron.remove": "cronDelete",
|
|
313
|
+
"cron.trigger": "cronRun",
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export const GATEWAY_RPC_DEFINITIONS: readonly GatewayRpcDefinition[] = [
|
|
317
|
+
{
|
|
318
|
+
version: SMITHERS_API_VERSION,
|
|
319
|
+
method: "launchRun",
|
|
320
|
+
title: "Launch Run",
|
|
321
|
+
description: "Start a registered workflow run.",
|
|
322
|
+
maturity: "stable",
|
|
323
|
+
transport: "http+websocket",
|
|
324
|
+
requiredScope: "run:write",
|
|
325
|
+
requestSchema: objectSchema({
|
|
326
|
+
workflow,
|
|
327
|
+
input: objectSchema({}, [], "Workflow input.", true),
|
|
328
|
+
options: objectSchema({
|
|
329
|
+
runId: stringSchema("Optional caller-supplied run id."),
|
|
330
|
+
idempotencyKey: stringSchema("Optional caller idempotency key."),
|
|
331
|
+
}, [], "Launch options."),
|
|
332
|
+
}, ["workflow"]),
|
|
333
|
+
responseSchema: objectSchema({ runId, workflow }, ["runId", "workflow"]),
|
|
334
|
+
errors: ["InvalidRequest", "InvalidInput", "Unauthorized", "Forbidden", "Internal"],
|
|
335
|
+
exampleRequest: { workflow: "deploy", input: { sha: "abc123" }, options: { runId: "deploy-abc123" } },
|
|
336
|
+
exampleResponse: { runId: "deploy-abc123", workflow: "deploy" },
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
version: SMITHERS_API_VERSION,
|
|
340
|
+
method: "resumeRun",
|
|
341
|
+
title: "Resume Run",
|
|
342
|
+
description: "Resume a waiting or interrupted run.",
|
|
343
|
+
maturity: "stable",
|
|
344
|
+
transport: "http+websocket",
|
|
345
|
+
requiredScope: "run:write",
|
|
346
|
+
requestSchema: objectSchema({ runId, options: objectSchema({ force: booleanSchema("Force a resume attempt.") }) }, ["runId"]),
|
|
347
|
+
responseSchema: objectSchema({ runId, status: { type: "string", enum: ["resume_requested", "already_terminal"] } }, ["runId", "status"]),
|
|
348
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "Internal"],
|
|
349
|
+
exampleRequest: { runId: "run_01", options: { force: false } },
|
|
350
|
+
exampleResponse: { runId: "run_01", status: "resume_requested" },
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
version: SMITHERS_API_VERSION,
|
|
354
|
+
method: "cancelRun",
|
|
355
|
+
title: "Cancel Run",
|
|
356
|
+
description: "Cancel an active run.",
|
|
357
|
+
maturity: "stable",
|
|
358
|
+
transport: "http+websocket",
|
|
359
|
+
requiredScope: "run:write",
|
|
360
|
+
requestSchema: objectSchema({ runId }, ["runId"]),
|
|
361
|
+
responseSchema: objectSchema({ runId, status: { type: "string", enum: ["cancelling"] } }, ["runId", "status"]),
|
|
362
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "Busy", "Internal"],
|
|
363
|
+
exampleRequest: { runId: "run_01" },
|
|
364
|
+
exampleResponse: { runId: "run_01", status: "cancelling" },
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
version: SMITHERS_API_VERSION,
|
|
368
|
+
method: "hijackRun",
|
|
369
|
+
title: "Hijack Run",
|
|
370
|
+
description: "Create an elevated operator handoff session for a run.",
|
|
371
|
+
maturity: "stable",
|
|
372
|
+
transport: "http+websocket",
|
|
373
|
+
requiredScope: "run:admin",
|
|
374
|
+
requestSchema: objectSchema({ runId, options: objectSchema({}, [], "Hijack options.", true) }, ["runId"]),
|
|
375
|
+
responseSchema: objectSchema({ runId, status: { type: "string", enum: ["hijack-ready"] }, sessionId: stringSchema("Hijack handoff session id.") }, ["runId", "status", "sessionId"]),
|
|
376
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "Internal"],
|
|
377
|
+
exampleRequest: { runId: "run_01", options: { reason: "operator takeover" } },
|
|
378
|
+
exampleResponse: { runId: "run_01", status: "hijack-ready", sessionId: "hijack_01" },
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
version: SMITHERS_API_VERSION,
|
|
382
|
+
method: "rewindRun",
|
|
383
|
+
title: "Rewind Run",
|
|
384
|
+
description: "Rewind a run to a prior frame.",
|
|
385
|
+
maturity: "stable",
|
|
386
|
+
transport: "http+websocket",
|
|
387
|
+
requiredScope: "run:admin",
|
|
388
|
+
requestSchema: objectSchema({ runId, frameNo: integerSchema("Target frame number.", 0), confirm: { const: true, description: "Must be true." } }, ["runId", "frameNo", "confirm"]),
|
|
389
|
+
responseSchema: objectSchema({}, [], "JumpResult payload.", true),
|
|
390
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "FrameOutOfRange", "Busy", "RateLimited", "UnsupportedSandbox", "VcsError", "RewindFailed"],
|
|
391
|
+
exampleRequest: { runId: "run_01", frameNo: 4, confirm: true },
|
|
392
|
+
exampleResponse: { ok: true, newFrameNo: 4, revertedSandboxes: 0, deletedFrames: 2 },
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
version: SMITHERS_API_VERSION,
|
|
396
|
+
method: "submitApproval",
|
|
397
|
+
title: "Submit Approval",
|
|
398
|
+
description: "Submit an approval decision for a waiting approval node.",
|
|
399
|
+
maturity: "stable",
|
|
400
|
+
transport: "http+websocket",
|
|
401
|
+
requiredScope: "approval:submit",
|
|
402
|
+
requestSchema: objectSchema({
|
|
403
|
+
runId,
|
|
404
|
+
nodeId,
|
|
405
|
+
iteration,
|
|
406
|
+
decision: objectSchema({
|
|
407
|
+
approved: booleanSchema("Whether the approval is granted."),
|
|
408
|
+
value: anyJsonSchema,
|
|
409
|
+
note: stringSchema("Optional decision note."),
|
|
410
|
+
}, ["approved"]),
|
|
411
|
+
}, ["runId", "nodeId", "decision"]),
|
|
412
|
+
responseSchema: objectSchema({ runId, nodeId, iteration, approved: booleanSchema("Whether the approval was granted.") }, ["runId", "nodeId", "iteration", "approved"]),
|
|
413
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "NodeNotFound", "Internal"],
|
|
414
|
+
exampleRequest: { runId: "run_01", nodeId: "approve", decision: { approved: true, note: "ship it" } },
|
|
415
|
+
exampleResponse: { runId: "run_01", nodeId: "approve", iteration: 0, approved: true },
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
version: SMITHERS_API_VERSION,
|
|
419
|
+
method: "submitSignal",
|
|
420
|
+
title: "Submit Signal",
|
|
421
|
+
description: "Deliver a signal payload to a waiting run.",
|
|
422
|
+
maturity: "stable",
|
|
423
|
+
transport: "http+websocket",
|
|
424
|
+
requiredScope: "signal:submit",
|
|
425
|
+
requestSchema: objectSchema({ runId, correlationKey: stringSchema("Signal correlation key."), signalName: stringSchema("Optional explicit signal name."), payload: anyJsonSchema }, ["runId", "correlationKey"]),
|
|
426
|
+
responseSchema: objectSchema({}, [], "Signal delivery metadata.", true),
|
|
427
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "Internal"],
|
|
428
|
+
exampleRequest: { runId: "run_01", correlationKey: "issue-42", signalName: "github.comment.created", payload: { body: "ready" } },
|
|
429
|
+
exampleResponse: { runId: "run_01", signalName: "github.comment.created", correlationId: "issue-42", seq: 1 },
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
version: SMITHERS_API_VERSION,
|
|
433
|
+
method: "getRun",
|
|
434
|
+
title: "Get Run",
|
|
435
|
+
description: "Fetch the current RunStateView for one run.",
|
|
436
|
+
maturity: "stable",
|
|
437
|
+
transport: "http+websocket",
|
|
438
|
+
requiredScope: "run:read",
|
|
439
|
+
requestSchema: objectSchema({ runId }, ["runId"]),
|
|
440
|
+
responseSchema: objectSchema({}, [], "RunStateView.", true),
|
|
441
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "Internal"],
|
|
442
|
+
exampleRequest: { runId: "run_01" },
|
|
443
|
+
exampleResponse: { runId: "run_01", status: "finished", workflowKey: "deploy" },
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
version: SMITHERS_API_VERSION,
|
|
447
|
+
method: "listRuns",
|
|
448
|
+
title: "List Runs",
|
|
449
|
+
description: "List recent runs matching an optional filter.",
|
|
450
|
+
maturity: "stable",
|
|
451
|
+
transport: "http+websocket",
|
|
452
|
+
requiredScope: "run:read",
|
|
453
|
+
requestSchema: objectSchema({ filter: objectSchema({ status: stringSchema("Optional run status filter."), limit: integerSchema("Maximum number of runs.", 1) }) }),
|
|
454
|
+
responseSchema: arraySchema(runSummary, "Run summaries."),
|
|
455
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "Internal"],
|
|
456
|
+
exampleRequest: { filter: { status: "finished", limit: 20 } },
|
|
457
|
+
exampleResponse: [{ runId: "run_01", workflowKey: "deploy", status: "finished", createdAtMs: 1710000000000 }],
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
version: SMITHERS_API_VERSION,
|
|
461
|
+
method: "streamRunEvents",
|
|
462
|
+
title: "Stream Run Events",
|
|
463
|
+
description: "Subscribe to a run event stream with bounded replay and GapResync semantics.",
|
|
464
|
+
maturity: "stable",
|
|
465
|
+
transport: "websocket",
|
|
466
|
+
requiredScope: "run:read",
|
|
467
|
+
requestSchema: objectSchema({ runId, afterSeq }, ["runId"]),
|
|
468
|
+
responseSchema: objectSchema({ streamId: stringSchema("Stream id."), runId, afterSeq: { type: ["integer", "null"] }, currentSeq: integerSchema("Current per-run event sequence.", 0) }, ["streamId", "runId", "afterSeq", "currentSeq"]),
|
|
469
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "SeqOutOfRange", "Internal"],
|
|
470
|
+
exampleRequest: { runId: "run_01", afterSeq: 41 },
|
|
471
|
+
exampleResponse: { streamId: "stream_01", runId: "run_01", afterSeq: 41, currentSeq: 45 },
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
version: SMITHERS_API_VERSION,
|
|
475
|
+
method: "streamDevTools",
|
|
476
|
+
title: "Stream DevTools",
|
|
477
|
+
description: "Subscribe to the DevTools snapshot and delta stream.",
|
|
478
|
+
maturity: "stable",
|
|
479
|
+
transport: "websocket",
|
|
480
|
+
requiredScope: "observability:read",
|
|
481
|
+
requestSchema: objectSchema({ runId, afterSeq }, ["runId"]),
|
|
482
|
+
responseSchema: objectSchema({ streamId: stringSchema("Stream id."), runId, afterSeq: { type: ["integer", "null"] } }, ["streamId", "runId"]),
|
|
483
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "SeqOutOfRange", "BackpressureDisconnect", "Internal"],
|
|
484
|
+
exampleRequest: { runId: "run_01", afterSeq: 10 },
|
|
485
|
+
exampleResponse: { streamId: "stream_01", runId: "run_01", afterSeq: 10 },
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
version: SMITHERS_API_VERSION,
|
|
489
|
+
method: "getNodeOutput",
|
|
490
|
+
title: "Get Node Output",
|
|
491
|
+
description: "Fetch a task node output payload.",
|
|
492
|
+
maturity: "stable",
|
|
493
|
+
transport: "http+websocket",
|
|
494
|
+
requiredScope: "run:read",
|
|
495
|
+
requestSchema: objectSchema({ runId, nodeId, iteration }, ["runId", "nodeId"]),
|
|
496
|
+
responseSchema: objectSchema({}, [], "NodeOutputResponse.", true),
|
|
497
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "NodeNotFound", "IterationNotFound", "NodeHasNoOutput", "PayloadTooLarge", "Internal"],
|
|
498
|
+
exampleRequest: { runId: "run_01", nodeId: "task", iteration: 0 },
|
|
499
|
+
exampleResponse: { status: "produced", row: { value: 1 }, schema: null },
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
version: SMITHERS_API_VERSION,
|
|
503
|
+
method: "getNodeDiff",
|
|
504
|
+
title: "Get Node Diff",
|
|
505
|
+
description: "Fetch a node-level diff bundle for one iteration.",
|
|
506
|
+
maturity: "stable",
|
|
507
|
+
transport: "http+websocket",
|
|
508
|
+
requiredScope: "run:read",
|
|
509
|
+
requestSchema: objectSchema({ runId, nodeId, iteration }, ["runId", "nodeId"]),
|
|
510
|
+
responseSchema: objectSchema({}, [], "Node diff response.", true),
|
|
511
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "NodeNotFound", "IterationNotFound", "PayloadTooLarge", "VcsError", "Internal"],
|
|
512
|
+
exampleRequest: { runId: "run_01", nodeId: "task", iteration: 0 },
|
|
513
|
+
exampleResponse: { summary: { filesChanged: 1 }, files: [] },
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
version: SMITHERS_API_VERSION,
|
|
517
|
+
method: "cronList",
|
|
518
|
+
title: "Cron List",
|
|
519
|
+
description: "List Gateway cron schedules.",
|
|
520
|
+
maturity: "stable",
|
|
521
|
+
transport: "http+websocket",
|
|
522
|
+
requiredScope: "cron:read",
|
|
523
|
+
requestSchema: objectSchema({ filter: objectSchema({ workflow: stringSchema("Workflow key.") }) }),
|
|
524
|
+
responseSchema: arraySchema(objectSchema({}, [], "Cron row.", true), "Cron rows."),
|
|
525
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "Internal"],
|
|
526
|
+
exampleRequest: { filter: { workflow: "deploy" } },
|
|
527
|
+
exampleResponse: [{ cronId: "cron_01", workflow: "deploy", pattern: "0 8 * * 1-5" }],
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
version: SMITHERS_API_VERSION,
|
|
531
|
+
method: "cronCreate",
|
|
532
|
+
title: "Cron Create",
|
|
533
|
+
description: "Create or replace a Gateway cron schedule.",
|
|
534
|
+
maturity: "stable",
|
|
535
|
+
transport: "http+websocket",
|
|
536
|
+
requiredScope: "cron:write",
|
|
537
|
+
requestSchema: objectSchema({ workflow, pattern: stringSchema("Cron expression."), cronId: stringSchema("Optional cron id."), enabled: booleanSchema("Whether the schedule is enabled.") }, ["workflow", "pattern"]),
|
|
538
|
+
responseSchema: objectSchema({}, [], "Created cron row.", true),
|
|
539
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "Internal"],
|
|
540
|
+
exampleRequest: { workflow: "deploy", pattern: "0 8 * * 1-5" },
|
|
541
|
+
exampleResponse: { cronId: "cron_01", workflow: "deploy", pattern: "0 8 * * 1-5", enabled: true },
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
version: SMITHERS_API_VERSION,
|
|
545
|
+
method: "cronDelete",
|
|
546
|
+
title: "Cron Delete",
|
|
547
|
+
description: "Delete a Gateway cron schedule.",
|
|
548
|
+
maturity: "stable",
|
|
549
|
+
transport: "http+websocket",
|
|
550
|
+
requiredScope: "cron:write",
|
|
551
|
+
requestSchema: objectSchema({ cronId: stringSchema("Cron id.") }, ["cronId"]),
|
|
552
|
+
responseSchema: objectSchema({ cronId: stringSchema("Cron id."), removed: booleanSchema("True when removed.") }, ["cronId", "removed"]),
|
|
553
|
+
errors: ["InvalidRequest", "Unauthorized", "Forbidden", "RunNotFound", "Internal"],
|
|
554
|
+
exampleRequest: { cronId: "cron_01" },
|
|
555
|
+
exampleResponse: { cronId: "cron_01", removed: true },
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
version: SMITHERS_API_VERSION,
|
|
559
|
+
method: "cronRun",
|
|
560
|
+
title: "Cron Run",
|
|
561
|
+
description: "Trigger a cron schedule or workflow immediately.",
|
|
562
|
+
maturity: "stable",
|
|
563
|
+
transport: "http+websocket",
|
|
564
|
+
requiredScope: "cron:write",
|
|
565
|
+
requestSchema: objectSchema({ cronId: stringSchema("Cron id."), workflow, input: objectSchema({}, [], "Workflow input.", true) }),
|
|
566
|
+
responseSchema: objectSchema({ runId, workflow }, ["runId", "workflow"]),
|
|
567
|
+
errors: ["InvalidRequest", "InvalidInput", "Unauthorized", "Forbidden", "RunNotFound", "Internal"],
|
|
568
|
+
exampleRequest: { cronId: "cron_01", input: { dryRun: true } },
|
|
569
|
+
exampleResponse: { runId: "run_02", workflow: "deploy" },
|
|
570
|
+
},
|
|
571
|
+
] as const;
|
|
572
|
+
|
|
573
|
+
const definitionByMethod = new Map<string, GatewayRpcDefinition>(
|
|
574
|
+
GATEWAY_RPC_DEFINITIONS.map((definition) => [definition.method, definition]),
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
export function canonicalGatewayRpcMethod(method: string): GatewayRpcMethod | undefined {
|
|
578
|
+
if (definitionByMethod.has(method)) {
|
|
579
|
+
return method as GatewayRpcMethod;
|
|
580
|
+
}
|
|
581
|
+
return GATEWAY_RPC_LEGACY_METHOD_ALIASES[method];
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export function getGatewayRpcDefinition(method: string): GatewayRpcDefinition | undefined {
|
|
585
|
+
const canonical = canonicalGatewayRpcMethod(method);
|
|
586
|
+
return canonical ? definitionByMethod.get(canonical) : undefined;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export function getRequiredScopeForGatewayMethod(method: string): GatewayScope | undefined {
|
|
590
|
+
if (method === "health") {
|
|
591
|
+
return "run:read";
|
|
592
|
+
}
|
|
593
|
+
if (method === "approvals.list") {
|
|
594
|
+
return "run:read";
|
|
595
|
+
}
|
|
596
|
+
if (method === "runs.diff" || method === "frames.list" || method === "frames.get" || method === "attempts.list" || method === "attempts.get") {
|
|
597
|
+
return "run:read";
|
|
598
|
+
}
|
|
599
|
+
if (method === "getDevToolsSnapshot") {
|
|
600
|
+
return "observability:read";
|
|
601
|
+
}
|
|
602
|
+
if (method === "runs.rerun") {
|
|
603
|
+
return "run:write";
|
|
604
|
+
}
|
|
605
|
+
if (method === "approve") {
|
|
606
|
+
return "approval:submit";
|
|
607
|
+
}
|
|
608
|
+
const definition = getGatewayRpcDefinition(method);
|
|
609
|
+
return definition?.requiredScope;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export function listGatewayRpcMethods(): readonly GatewayRpcMethod[] {
|
|
613
|
+
return GATEWAY_RPC_DEFINITIONS.map((definition) => definition.method);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function isGatewayRpcMethod(method: string): method is GatewayRpcMethod {
|
|
617
|
+
return definitionByMethod.has(method);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export function getGatewayScopeValues(): readonly GatewayScope[] {
|
|
621
|
+
return GATEWAY_SCOPE_VALUES;
|
|
622
|
+
}
|