@driftgate/sdk 0.1.0-rc.1
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 +32 -0
- package/dist/index.d.ts +4167 -0
- package/dist/index.js +765 -0
- package/dist/index.js.map +1 -0
- package/examples/full.ts +19 -0
- package/examples/hello-world.ts +5 -0
- package/package.json +41 -0
- package/src/index.ts +1065 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,1065 @@
|
|
|
1
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
CanonicalPolicyRefSchema,
|
|
5
|
+
CanonicalRiskMetaSchema,
|
|
6
|
+
CanonicalRouteRefSchema,
|
|
7
|
+
DataBoundaryDecisionSchema,
|
|
8
|
+
EdgeInterceptorRegistrationSchema,
|
|
9
|
+
EdgeInterceptorStatusSchema,
|
|
10
|
+
FirewallEventsResponseSchema,
|
|
11
|
+
FirewallInspectRequestSchema,
|
|
12
|
+
FirewallInspectResponseSchema,
|
|
13
|
+
FirewallInspectResultSchema,
|
|
14
|
+
RunStateSchema,
|
|
15
|
+
V4ExecutionRequestSchema,
|
|
16
|
+
V4SessionResourceSchema,
|
|
17
|
+
V4SessionStartRequestSchema,
|
|
18
|
+
WorkflowVersionSchema,
|
|
19
|
+
type CanonicalPolicyRef,
|
|
20
|
+
type CanonicalRiskMeta,
|
|
21
|
+
type CanonicalRouteRef,
|
|
22
|
+
type EdgeInterceptorRegisterRequest,
|
|
23
|
+
type EdgeInterceptorRegistration,
|
|
24
|
+
type EdgeInterceptorStatus,
|
|
25
|
+
type FirewallEvent,
|
|
26
|
+
type FirewallInspectRequest,
|
|
27
|
+
type FirewallInspectResponse,
|
|
28
|
+
type PolicyDecision,
|
|
29
|
+
type WorkflowVersion
|
|
30
|
+
} from "@driftgate/contracts";
|
|
31
|
+
|
|
32
|
+
const HeadlessErrorEnvelopeSchema = z.object({
|
|
33
|
+
code: z.string(),
|
|
34
|
+
message: z.string(),
|
|
35
|
+
correlation_id: z.string().optional(),
|
|
36
|
+
details: z.unknown().optional()
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const LegacyErrorEnvelopeSchema = z.object({
|
|
40
|
+
error: z.string(),
|
|
41
|
+
message: z.string(),
|
|
42
|
+
issues: z.unknown().optional()
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const CanonicalTimingMsSchema = z.object({
|
|
46
|
+
total: z.number(),
|
|
47
|
+
policy: z.number().optional(),
|
|
48
|
+
route: z.number().optional(),
|
|
49
|
+
tool: z.number().optional()
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const CanonicalMetaSchema = z.object({
|
|
53
|
+
requestId: z.string(),
|
|
54
|
+
sessionId: z.string().optional(),
|
|
55
|
+
executionId: z.string().optional(),
|
|
56
|
+
lineageId: z.string().optional(),
|
|
57
|
+
policy: CanonicalPolicyRefSchema.optional(),
|
|
58
|
+
route: CanonicalRouteRefSchema.optional(),
|
|
59
|
+
risk: CanonicalRiskMetaSchema.optional(),
|
|
60
|
+
timingMs: CanonicalTimingMsSchema
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const CanonicalErrorSchema = z.object({
|
|
64
|
+
code: z.string(),
|
|
65
|
+
message: z.string(),
|
|
66
|
+
status: z.number(),
|
|
67
|
+
retryable: z.boolean(),
|
|
68
|
+
details: z.record(z.unknown()).optional()
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const REQUIRED_V4_ERROR_CODES = [
|
|
72
|
+
"AUTH_INVALID",
|
|
73
|
+
"POLICY_DENIED",
|
|
74
|
+
"RISK_EXCEEDED",
|
|
75
|
+
"ROUTE_UNAVAILABLE",
|
|
76
|
+
"TOOL_BLOCKED",
|
|
77
|
+
"RATE_LIMITED",
|
|
78
|
+
"TIMEOUT",
|
|
79
|
+
"INTERNAL"
|
|
80
|
+
] as const;
|
|
81
|
+
|
|
82
|
+
const RunRecordSchema = z.object({
|
|
83
|
+
id: z.string(),
|
|
84
|
+
workspaceId: z.string(),
|
|
85
|
+
workflowVersionId: z.string(),
|
|
86
|
+
state: RunStateSchema,
|
|
87
|
+
correlationId: z.string(),
|
|
88
|
+
idempotencyKey: z.string().nullable().optional(),
|
|
89
|
+
triggerSource: z.enum(["ui", "api", "sdk", "cli", "hosted", "webhook"]),
|
|
90
|
+
requestedBy: z.string(),
|
|
91
|
+
requestedAt: z.string(),
|
|
92
|
+
startedAt: z.string().nullable().optional(),
|
|
93
|
+
completedAt: z.string().nullable().optional(),
|
|
94
|
+
slaPolicyId: z.string().nullable().optional(),
|
|
95
|
+
slaDueAt: z.string().nullable().optional(),
|
|
96
|
+
slaViolatedAt: z.string().nullable().optional()
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const ApprovalSchema = z.object({
|
|
100
|
+
id: z.string(),
|
|
101
|
+
runId: z.string(),
|
|
102
|
+
requiredRole: z.string(),
|
|
103
|
+
status: z.enum(["pending", "approved", "denied"]),
|
|
104
|
+
createdAt: z.string(),
|
|
105
|
+
decidedAt: z.string().nullable().optional(),
|
|
106
|
+
decidedBy: z.string().nullable().optional()
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const RunResponseSchema = z.object({
|
|
110
|
+
run: RunRecordSchema,
|
|
111
|
+
approval: ApprovalSchema.nullable().optional(),
|
|
112
|
+
blocked: z.boolean().optional(),
|
|
113
|
+
policyDecisions: z
|
|
114
|
+
.array(
|
|
115
|
+
z.object({
|
|
116
|
+
mode: z.enum(["monitor", "enforce"]),
|
|
117
|
+
decision: z.enum(["allow", "deny"]),
|
|
118
|
+
policyId: z.string(),
|
|
119
|
+
ruleId: z.string(),
|
|
120
|
+
reasonCode: z.string(),
|
|
121
|
+
reasonText: z.string(),
|
|
122
|
+
correlationId: z.string(),
|
|
123
|
+
trace: z.record(z.unknown())
|
|
124
|
+
})
|
|
125
|
+
)
|
|
126
|
+
.optional(),
|
|
127
|
+
entitlementDecision: z
|
|
128
|
+
.object({
|
|
129
|
+
id: z.string(),
|
|
130
|
+
reasonCode: z.string(),
|
|
131
|
+
reasonText: z.string(),
|
|
132
|
+
entitled: z.boolean()
|
|
133
|
+
})
|
|
134
|
+
.optional(),
|
|
135
|
+
usageEntry: z
|
|
136
|
+
.object({
|
|
137
|
+
id: z.string(),
|
|
138
|
+
quantity: z.number()
|
|
139
|
+
})
|
|
140
|
+
.optional(),
|
|
141
|
+
boundaryDecision: DataBoundaryDecisionSchema.nullable().optional(),
|
|
142
|
+
firewallDecision: FirewallInspectResultSchema.nullable().optional()
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const V4SessionStartDataSchema = z.object({
|
|
146
|
+
session: V4SessionResourceSchema
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const CanonicalEnvelopeSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
|
|
150
|
+
z.object({
|
|
151
|
+
ok: z.boolean(),
|
|
152
|
+
data: dataSchema.nullable(),
|
|
153
|
+
meta: CanonicalMetaSchema,
|
|
154
|
+
error: CanonicalErrorSchema.nullable()
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const V4SessionStartResponseSchema = CanonicalEnvelopeSchema(V4SessionStartDataSchema);
|
|
158
|
+
const V4ExecutionResponseSchema = CanonicalEnvelopeSchema(RunResponseSchema);
|
|
159
|
+
const V4EphemeralExecuteRequestBodySchema = V4SessionStartRequestSchema.extend({
|
|
160
|
+
input: z.record(z.unknown())
|
|
161
|
+
});
|
|
162
|
+
const V4EphemeralExecuteDataSchema = z.object({
|
|
163
|
+
session: V4SessionResourceSchema,
|
|
164
|
+
execution: RunResponseSchema
|
|
165
|
+
});
|
|
166
|
+
const V4EphemeralExecutionResponseSchema = CanonicalEnvelopeSchema(V4EphemeralExecuteDataSchema);
|
|
167
|
+
const CanonicalErrorEnvelopeSchema = z.object({
|
|
168
|
+
ok: z.literal(false),
|
|
169
|
+
data: z.null(),
|
|
170
|
+
meta: CanonicalMetaSchema,
|
|
171
|
+
error: CanonicalErrorSchema
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const RunEventsResponseSchema = z.object({
|
|
175
|
+
events: z.array(
|
|
176
|
+
z.object({
|
|
177
|
+
id: z.string(),
|
|
178
|
+
runId: z.string(),
|
|
179
|
+
type: z.string(),
|
|
180
|
+
payload: z.record(z.unknown()),
|
|
181
|
+
createdAt: z.string()
|
|
182
|
+
})
|
|
183
|
+
)
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const ApprovalsListSchema = z.object({
|
|
187
|
+
approvals: z.array(
|
|
188
|
+
z.object({
|
|
189
|
+
approval: ApprovalSchema,
|
|
190
|
+
run: RunRecordSchema
|
|
191
|
+
})
|
|
192
|
+
)
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const DeployResponseSchema = z.object({
|
|
196
|
+
project: z.object({
|
|
197
|
+
id: z.string(),
|
|
198
|
+
workspaceId: z.string(),
|
|
199
|
+
name: z.string(),
|
|
200
|
+
createdBy: z.string(),
|
|
201
|
+
createdAt: z.string(),
|
|
202
|
+
updatedAt: z.string()
|
|
203
|
+
}),
|
|
204
|
+
workflow: z.object({
|
|
205
|
+
id: z.string(),
|
|
206
|
+
projectId: z.string(),
|
|
207
|
+
workspaceId: z.string(),
|
|
208
|
+
name: z.string(),
|
|
209
|
+
status: z.enum(["draft", "published", "archived"]),
|
|
210
|
+
createdBy: z.string(),
|
|
211
|
+
createdAt: z.string(),
|
|
212
|
+
updatedAt: z.string()
|
|
213
|
+
}),
|
|
214
|
+
draft: z.object({
|
|
215
|
+
workflowId: z.string(),
|
|
216
|
+
workspaceId: z.string(),
|
|
217
|
+
version: z.number(),
|
|
218
|
+
nodes: z.array(z.unknown()),
|
|
219
|
+
edges: z.array(z.unknown()),
|
|
220
|
+
viewport: z.object({ x: z.number(), y: z.number(), zoom: z.number() }),
|
|
221
|
+
updatedAt: z.string()
|
|
222
|
+
}),
|
|
223
|
+
compile: z.object({
|
|
224
|
+
checksum: z.string(),
|
|
225
|
+
mutationNodeIds: z.array(z.string()),
|
|
226
|
+
compiledPlan: z.record(z.unknown())
|
|
227
|
+
})
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const PublishResponseSchema = z.object({
|
|
231
|
+
version: WorkflowVersionSchema
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const ConnectorRecordSchema = z.object({
|
|
235
|
+
id: z.string(),
|
|
236
|
+
workspaceId: z.string(),
|
|
237
|
+
name: z.string(),
|
|
238
|
+
connectorType: z.string(),
|
|
239
|
+
status: z.enum(["active", "disabled"]),
|
|
240
|
+
config: z.record(z.unknown()),
|
|
241
|
+
createdBy: z.string(),
|
|
242
|
+
createdAt: z.string(),
|
|
243
|
+
updatedAt: z.string()
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const WorkspaceSecretRecordSchema = z.object({
|
|
247
|
+
id: z.string(),
|
|
248
|
+
workspaceId: z.string(),
|
|
249
|
+
connectorId: z.string().nullable(),
|
|
250
|
+
name: z.string(),
|
|
251
|
+
keyVersion: z.string(),
|
|
252
|
+
metadata: z.record(z.unknown()),
|
|
253
|
+
createdBy: z.string(),
|
|
254
|
+
createdAt: z.string(),
|
|
255
|
+
rotatedAt: z.string().nullable(),
|
|
256
|
+
revokedAt: z.string().nullable(),
|
|
257
|
+
maskedValue: z.string()
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const WorkspaceWebhookRecordSchema = z.object({
|
|
261
|
+
id: z.string(),
|
|
262
|
+
workspaceId: z.string(),
|
|
263
|
+
connectorId: z.string().nullable(),
|
|
264
|
+
name: z.string(),
|
|
265
|
+
path: z.string(),
|
|
266
|
+
targetWorkflowId: z.string(),
|
|
267
|
+
status: z.enum(["active", "disabled"]),
|
|
268
|
+
eventFilter: z.record(z.unknown()),
|
|
269
|
+
createdBy: z.string(),
|
|
270
|
+
createdAt: z.string(),
|
|
271
|
+
updatedAt: z.string(),
|
|
272
|
+
lastReceivedAt: z.string().nullable(),
|
|
273
|
+
revokedAt: z.string().nullable(),
|
|
274
|
+
signingSecretConfigured: z.boolean()
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const ConnectorListSchema = z.object({
|
|
278
|
+
connectors: z.array(ConnectorRecordSchema)
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const ConnectorMutationSchema = z.object({
|
|
282
|
+
connector: ConnectorRecordSchema
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const SecretListSchema = z.object({
|
|
286
|
+
secrets: z.array(WorkspaceSecretRecordSchema)
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const SecretMutationSchema = z.object({
|
|
290
|
+
secret: WorkspaceSecretRecordSchema
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const WebhookListSchema = z.object({
|
|
294
|
+
webhooks: z.array(WorkspaceWebhookRecordSchema)
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const WebhookMutationSchema = z.object({
|
|
298
|
+
webhook: WorkspaceWebhookRecordSchema
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const EdgeInterceptorListSchema = z.object({
|
|
302
|
+
registrations: z.array(EdgeInterceptorRegistrationSchema)
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const EdgeInterceptorMutationSchema = z.object({
|
|
306
|
+
registration: EdgeInterceptorRegistrationSchema
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const FirewallInspectBodySchema = FirewallInspectRequestSchema.omit({
|
|
310
|
+
workspaceId: true
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
export class DriftGateError extends Error {
|
|
314
|
+
constructor(
|
|
315
|
+
public readonly code: string,
|
|
316
|
+
message: string,
|
|
317
|
+
public readonly status: number,
|
|
318
|
+
public readonly correlationId?: string,
|
|
319
|
+
public readonly details?: unknown
|
|
320
|
+
) {
|
|
321
|
+
super(message);
|
|
322
|
+
this.name = "DriftGateError";
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export type DriftGateClientOptions = {
|
|
327
|
+
baseUrl: string;
|
|
328
|
+
sessionToken?: string;
|
|
329
|
+
apiKey?: string;
|
|
330
|
+
fetchImpl?: typeof fetch;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export type DriftGateRunInput = {
|
|
334
|
+
workspaceId: string;
|
|
335
|
+
workflowVersionId: string;
|
|
336
|
+
requiresApproval?: boolean;
|
|
337
|
+
requiredRole?: string;
|
|
338
|
+
slaPolicyId?: string;
|
|
339
|
+
idempotencyKey?: string;
|
|
340
|
+
correlationId?: string;
|
|
341
|
+
triggerSource?: "ui" | "api" | "sdk" | "cli" | "hosted" | "webhook";
|
|
342
|
+
input?: Record<string, unknown>;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
export type DriftGateCanonicalMeta = z.infer<typeof CanonicalMetaSchema>;
|
|
346
|
+
export type DriftGateCanonicalError = z.infer<typeof CanonicalErrorSchema>;
|
|
347
|
+
export type DriftGateCanonicalErrorCode = (typeof REQUIRED_V4_ERROR_CODES)[number];
|
|
348
|
+
export type DriftGateCanonicalResponse<T> = {
|
|
349
|
+
ok: boolean;
|
|
350
|
+
data: T | null;
|
|
351
|
+
meta: DriftGateCanonicalMeta;
|
|
352
|
+
error: DriftGateCanonicalError | null;
|
|
353
|
+
raw: unknown;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
export type DriftGateSessionStartInput = z.input<typeof V4SessionStartRequestSchema>;
|
|
357
|
+
export type DriftGateSessionExecuteInput = z.input<typeof V4ExecutionRequestSchema>;
|
|
358
|
+
export type DriftGateEphemeralExecuteInput = z.input<typeof V4SessionStartRequestSchema> & {
|
|
359
|
+
input: Record<string, unknown>;
|
|
360
|
+
};
|
|
361
|
+
export type DriftGateSessionStartEnvelope = z.infer<typeof V4SessionStartResponseSchema>;
|
|
362
|
+
export type DriftGateExecutionEnvelope = z.infer<typeof V4ExecutionResponseSchema>;
|
|
363
|
+
export type DriftGateEphemeralExecutionEnvelope = z.infer<typeof V4EphemeralExecutionResponseSchema>;
|
|
364
|
+
|
|
365
|
+
export type WaitForTerminalOptions = {
|
|
366
|
+
intervalMs?: number;
|
|
367
|
+
timeoutMs?: number;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
export type DeployWorkflowInput = {
|
|
371
|
+
workspaceId: string;
|
|
372
|
+
workflowYaml: string;
|
|
373
|
+
projectId?: string;
|
|
374
|
+
projectName?: string;
|
|
375
|
+
workflowId?: string;
|
|
376
|
+
workflowName?: string;
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
export type ConnectorCreateInput = {
|
|
380
|
+
name: string;
|
|
381
|
+
connectorType: string;
|
|
382
|
+
status?: "active" | "disabled";
|
|
383
|
+
config?: Record<string, unknown>;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
export type ConnectorUpdateInput = {
|
|
387
|
+
name?: string;
|
|
388
|
+
connectorType?: string;
|
|
389
|
+
status?: "active" | "disabled";
|
|
390
|
+
config?: Record<string, unknown>;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
export type SecretCreateInput = {
|
|
394
|
+
connectorId?: string | null;
|
|
395
|
+
name: string;
|
|
396
|
+
value: string;
|
|
397
|
+
keyVersion?: string;
|
|
398
|
+
metadata?: Record<string, unknown>;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
export type SecretUpdateInput = {
|
|
402
|
+
connectorId?: string | null;
|
|
403
|
+
name?: string;
|
|
404
|
+
value?: string;
|
|
405
|
+
keyVersion?: string;
|
|
406
|
+
metadata?: Record<string, unknown>;
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
export type WebhookExecutionOptions = {
|
|
410
|
+
requiresApproval?: boolean;
|
|
411
|
+
requiredRole?: string;
|
|
412
|
+
slaPolicyId?: string;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
export type WebhookCreateInput = {
|
|
416
|
+
connectorId?: string | null;
|
|
417
|
+
name: string;
|
|
418
|
+
path: string;
|
|
419
|
+
targetWorkflowId: string;
|
|
420
|
+
status?: "active" | "disabled";
|
|
421
|
+
eventFilter?: Record<string, unknown>;
|
|
422
|
+
execution?: WebhookExecutionOptions;
|
|
423
|
+
signingSecret: string;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
export type WebhookUpdateInput = {
|
|
427
|
+
connectorId?: string | null;
|
|
428
|
+
name?: string;
|
|
429
|
+
path?: string;
|
|
430
|
+
targetWorkflowId?: string;
|
|
431
|
+
status?: "active" | "disabled";
|
|
432
|
+
eventFilter?: Record<string, unknown>;
|
|
433
|
+
execution?: WebhookExecutionOptions;
|
|
434
|
+
signingSecret?: string;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
export type EdgeInterceptorRegisterInput = Omit<EdgeInterceptorRegisterRequest, "workspaceId">;
|
|
438
|
+
export type FirewallInspectInput = Omit<FirewallInspectRequest, "workspaceId">;
|
|
439
|
+
|
|
440
|
+
export type EdgeInterceptorEnforcementMode = "monitor" | "enforce";
|
|
441
|
+
|
|
442
|
+
export type EdgeInterceptorDecision = {
|
|
443
|
+
allowed: boolean;
|
|
444
|
+
reasonCode: string;
|
|
445
|
+
reasonText: string;
|
|
446
|
+
requiredCapabilities: string[];
|
|
447
|
+
grantedCapabilities: string[];
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
export type EdgeInterceptorHooks = {
|
|
451
|
+
beforeRun?: (input: DriftGateRunInput, decision: EdgeInterceptorDecision) => void | Promise<void>;
|
|
452
|
+
onBlocked?: (input: DriftGateRunInput, decision: EdgeInterceptorDecision) => void | Promise<void>;
|
|
453
|
+
afterRun?: (
|
|
454
|
+
input: DriftGateRunInput,
|
|
455
|
+
decision: EdgeInterceptorDecision,
|
|
456
|
+
response: z.infer<typeof RunResponseSchema>
|
|
457
|
+
) => void | Promise<void>;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
export type EdgeInterceptorEnableInput = {
|
|
461
|
+
workspaceId: string;
|
|
462
|
+
registration: EdgeInterceptorRegisterInput;
|
|
463
|
+
enforcement?: EdgeInterceptorEnforcementMode;
|
|
464
|
+
hooks?: EdgeInterceptorHooks;
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
export class DriftGateSessionHandle {
|
|
468
|
+
constructor(
|
|
469
|
+
private readonly client: DriftGateClient,
|
|
470
|
+
public readonly session: z.infer<typeof V4SessionResourceSchema>,
|
|
471
|
+
public readonly startEnvelope: DriftGateCanonicalResponse<z.infer<typeof V4SessionStartDataSchema>>
|
|
472
|
+
) {}
|
|
473
|
+
|
|
474
|
+
get sessionId(): string {
|
|
475
|
+
return this.session.sessionId;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
get rawEnvelope(): unknown {
|
|
479
|
+
return this.startEnvelope.raw;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async execute(
|
|
483
|
+
input: DriftGateSessionExecuteInput
|
|
484
|
+
): Promise<DriftGateCanonicalResponse<z.infer<typeof RunResponseSchema>>> {
|
|
485
|
+
return this.client.executeSession(this.session.sessionId, input);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function isTerminalState(state: z.infer<typeof RunStateSchema>): boolean {
|
|
490
|
+
return ["succeeded", "failed", "denied", "timed_out", "canceled", "aborted"].includes(state);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export class DriftGateClient {
|
|
494
|
+
private readonly baseUrl: string;
|
|
495
|
+
private readonly sessionToken?: string;
|
|
496
|
+
private readonly apiKey?: string;
|
|
497
|
+
private readonly fetchImpl: typeof fetch;
|
|
498
|
+
private edgeInterceptorState: {
|
|
499
|
+
workspaceId: string;
|
|
500
|
+
registration: EdgeInterceptorRegistration;
|
|
501
|
+
enforcement: EdgeInterceptorEnforcementMode;
|
|
502
|
+
hooks?: EdgeInterceptorHooks;
|
|
503
|
+
} | null = null;
|
|
504
|
+
readonly session: {
|
|
505
|
+
start: (input: DriftGateSessionStartInput) => Promise<DriftGateSessionHandle>;
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
readonly approvals: {
|
|
509
|
+
list: (workspaceId: string, status?: "pending" | "approved" | "denied") => Promise<z.infer<typeof ApprovalsListSchema>["approvals"]>;
|
|
510
|
+
approve: (approvalId: string) => Promise<z.infer<typeof RunResponseSchema>>;
|
|
511
|
+
deny: (approvalId: string) => Promise<z.infer<typeof RunResponseSchema>>;
|
|
512
|
+
};
|
|
513
|
+
readonly connectors: {
|
|
514
|
+
list: (workspaceId: string) => Promise<z.infer<typeof ConnectorRecordSchema>[]>;
|
|
515
|
+
create: (workspaceId: string, input: ConnectorCreateInput) => Promise<z.infer<typeof ConnectorRecordSchema>>;
|
|
516
|
+
update: (
|
|
517
|
+
workspaceId: string,
|
|
518
|
+
connectorId: string,
|
|
519
|
+
input: ConnectorUpdateInput
|
|
520
|
+
) => Promise<z.infer<typeof ConnectorRecordSchema>>;
|
|
521
|
+
delete: (workspaceId: string, connectorId: string) => Promise<z.infer<typeof ConnectorRecordSchema>>;
|
|
522
|
+
};
|
|
523
|
+
readonly secrets: {
|
|
524
|
+
list: (workspaceId: string) => Promise<z.infer<typeof WorkspaceSecretRecordSchema>[]>;
|
|
525
|
+
create: (workspaceId: string, input: SecretCreateInput) => Promise<z.infer<typeof WorkspaceSecretRecordSchema>>;
|
|
526
|
+
update: (
|
|
527
|
+
workspaceId: string,
|
|
528
|
+
secretId: string,
|
|
529
|
+
input: SecretUpdateInput
|
|
530
|
+
) => Promise<z.infer<typeof WorkspaceSecretRecordSchema>>;
|
|
531
|
+
delete: (workspaceId: string, secretId: string) => Promise<z.infer<typeof WorkspaceSecretRecordSchema>>;
|
|
532
|
+
};
|
|
533
|
+
readonly webhooks: {
|
|
534
|
+
list: (workspaceId: string) => Promise<z.infer<typeof WorkspaceWebhookRecordSchema>[]>;
|
|
535
|
+
create: (workspaceId: string, input: WebhookCreateInput) => Promise<z.infer<typeof WorkspaceWebhookRecordSchema>>;
|
|
536
|
+
update: (
|
|
537
|
+
workspaceId: string,
|
|
538
|
+
webhookId: string,
|
|
539
|
+
input: WebhookUpdateInput
|
|
540
|
+
) => Promise<z.infer<typeof WorkspaceWebhookRecordSchema>>;
|
|
541
|
+
delete: (workspaceId: string, webhookId: string) => Promise<z.infer<typeof WorkspaceWebhookRecordSchema>>;
|
|
542
|
+
};
|
|
543
|
+
readonly edgeInterceptors: {
|
|
544
|
+
list: (workspaceId: string) => Promise<EdgeInterceptorRegistration[]>;
|
|
545
|
+
register: (workspaceId: string, input: EdgeInterceptorRegisterInput) => Promise<EdgeInterceptorRegistration>;
|
|
546
|
+
setStatus: (
|
|
547
|
+
workspaceId: string,
|
|
548
|
+
registrationId: string,
|
|
549
|
+
status: EdgeInterceptorStatus
|
|
550
|
+
) => Promise<EdgeInterceptorRegistration>;
|
|
551
|
+
};
|
|
552
|
+
readonly firewall: {
|
|
553
|
+
inspect: (workspaceId: string, input: FirewallInspectInput) => Promise<FirewallInspectResponse>;
|
|
554
|
+
events: (workspaceId: string) => Promise<FirewallEvent[]>;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
constructor(options: DriftGateClientOptions) {
|
|
558
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
559
|
+
this.sessionToken = options.sessionToken;
|
|
560
|
+
this.apiKey = options.apiKey;
|
|
561
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
562
|
+
this.session = {
|
|
563
|
+
start: async (input) => {
|
|
564
|
+
const payload = V4SessionStartRequestSchema.parse(input);
|
|
565
|
+
const raw = await this.request("/v4/sessions.start", {
|
|
566
|
+
method: "POST",
|
|
567
|
+
body: JSON.stringify(payload)
|
|
568
|
+
});
|
|
569
|
+
const parsed = V4SessionStartResponseSchema.parse(raw);
|
|
570
|
+
if (!parsed.ok || !parsed.data) {
|
|
571
|
+
const canonicalCode = parsed.error?.code ?? "INTERNAL";
|
|
572
|
+
throw new DriftGateError(
|
|
573
|
+
canonicalCode,
|
|
574
|
+
parsed.error?.message ?? "session.start failed",
|
|
575
|
+
parsed.error?.status ?? 500,
|
|
576
|
+
parsed.meta.requestId,
|
|
577
|
+
parsed.error?.details
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
const envelope: DriftGateCanonicalResponse<z.infer<typeof V4SessionStartDataSchema>> = {
|
|
581
|
+
ok: parsed.ok,
|
|
582
|
+
data: parsed.data,
|
|
583
|
+
meta: parsed.meta,
|
|
584
|
+
error: parsed.error,
|
|
585
|
+
raw
|
|
586
|
+
};
|
|
587
|
+
return new DriftGateSessionHandle(this, parsed.data.session, envelope);
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
this.approvals = {
|
|
592
|
+
list: async (workspaceId, status) => {
|
|
593
|
+
const query = status ? `?status=${encodeURIComponent(status)}` : "";
|
|
594
|
+
const response = await this.request(`/v1/headless/workspaces/${encodeURIComponent(workspaceId)}/approvals${query}`);
|
|
595
|
+
return ApprovalsListSchema.parse(response).approvals;
|
|
596
|
+
},
|
|
597
|
+
approve: async (approvalId) => {
|
|
598
|
+
const response = await this.request(`/v1/headless/approvals/${encodeURIComponent(approvalId)}/approve`, {
|
|
599
|
+
method: "POST"
|
|
600
|
+
});
|
|
601
|
+
return RunResponseSchema.parse(response);
|
|
602
|
+
},
|
|
603
|
+
deny: async (approvalId) => {
|
|
604
|
+
const response = await this.request(`/v1/headless/approvals/${encodeURIComponent(approvalId)}/deny`, {
|
|
605
|
+
method: "POST"
|
|
606
|
+
});
|
|
607
|
+
return RunResponseSchema.parse(response);
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
this.connectors = {
|
|
612
|
+
list: async (workspaceId) => {
|
|
613
|
+
const response = await this.request(
|
|
614
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/connectors`
|
|
615
|
+
);
|
|
616
|
+
return ConnectorListSchema.parse(response).connectors;
|
|
617
|
+
},
|
|
618
|
+
create: async (workspaceId, input) => {
|
|
619
|
+
const response = await this.request(
|
|
620
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/connectors`,
|
|
621
|
+
{
|
|
622
|
+
method: "POST",
|
|
623
|
+
body: JSON.stringify(input)
|
|
624
|
+
}
|
|
625
|
+
);
|
|
626
|
+
return ConnectorMutationSchema.parse(response).connector;
|
|
627
|
+
},
|
|
628
|
+
update: async (workspaceId, connectorId, input) => {
|
|
629
|
+
const response = await this.request(
|
|
630
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/connectors/${encodeURIComponent(
|
|
631
|
+
connectorId
|
|
632
|
+
)}`,
|
|
633
|
+
{
|
|
634
|
+
method: "PATCH",
|
|
635
|
+
body: JSON.stringify(input)
|
|
636
|
+
}
|
|
637
|
+
);
|
|
638
|
+
return ConnectorMutationSchema.parse(response).connector;
|
|
639
|
+
},
|
|
640
|
+
delete: async (workspaceId, connectorId) => {
|
|
641
|
+
const response = await this.request(
|
|
642
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/connectors/${encodeURIComponent(
|
|
643
|
+
connectorId
|
|
644
|
+
)}`,
|
|
645
|
+
{
|
|
646
|
+
method: "DELETE"
|
|
647
|
+
}
|
|
648
|
+
);
|
|
649
|
+
return ConnectorMutationSchema.parse(response).connector;
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
this.secrets = {
|
|
654
|
+
list: async (workspaceId) => {
|
|
655
|
+
const response = await this.request(
|
|
656
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/secrets`
|
|
657
|
+
);
|
|
658
|
+
return SecretListSchema.parse(response).secrets;
|
|
659
|
+
},
|
|
660
|
+
create: async (workspaceId, input) => {
|
|
661
|
+
const response = await this.request(
|
|
662
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/secrets`,
|
|
663
|
+
{
|
|
664
|
+
method: "POST",
|
|
665
|
+
body: JSON.stringify(input)
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
return SecretMutationSchema.parse(response).secret;
|
|
669
|
+
},
|
|
670
|
+
update: async (workspaceId, secretId, input) => {
|
|
671
|
+
const response = await this.request(
|
|
672
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/secrets/${encodeURIComponent(secretId)}`,
|
|
673
|
+
{
|
|
674
|
+
method: "PATCH",
|
|
675
|
+
body: JSON.stringify(input)
|
|
676
|
+
}
|
|
677
|
+
);
|
|
678
|
+
return SecretMutationSchema.parse(response).secret;
|
|
679
|
+
},
|
|
680
|
+
delete: async (workspaceId, secretId) => {
|
|
681
|
+
const response = await this.request(
|
|
682
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/secrets/${encodeURIComponent(secretId)}`,
|
|
683
|
+
{
|
|
684
|
+
method: "DELETE"
|
|
685
|
+
}
|
|
686
|
+
);
|
|
687
|
+
return SecretMutationSchema.parse(response).secret;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
this.webhooks = {
|
|
692
|
+
list: async (workspaceId) => {
|
|
693
|
+
const response = await this.request(
|
|
694
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks`
|
|
695
|
+
);
|
|
696
|
+
return WebhookListSchema.parse(response).webhooks;
|
|
697
|
+
},
|
|
698
|
+
create: async (workspaceId, input) => {
|
|
699
|
+
const response = await this.request(
|
|
700
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks`,
|
|
701
|
+
{
|
|
702
|
+
method: "POST",
|
|
703
|
+
body: JSON.stringify(input)
|
|
704
|
+
}
|
|
705
|
+
);
|
|
706
|
+
return WebhookMutationSchema.parse(response).webhook;
|
|
707
|
+
},
|
|
708
|
+
update: async (workspaceId, webhookId, input) => {
|
|
709
|
+
const response = await this.request(
|
|
710
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/${encodeURIComponent(
|
|
711
|
+
webhookId
|
|
712
|
+
)}`,
|
|
713
|
+
{
|
|
714
|
+
method: "PATCH",
|
|
715
|
+
body: JSON.stringify(input)
|
|
716
|
+
}
|
|
717
|
+
);
|
|
718
|
+
return WebhookMutationSchema.parse(response).webhook;
|
|
719
|
+
},
|
|
720
|
+
delete: async (workspaceId, webhookId) => {
|
|
721
|
+
const response = await this.request(
|
|
722
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/webhooks/${encodeURIComponent(
|
|
723
|
+
webhookId
|
|
724
|
+
)}`,
|
|
725
|
+
{
|
|
726
|
+
method: "DELETE"
|
|
727
|
+
}
|
|
728
|
+
);
|
|
729
|
+
return WebhookMutationSchema.parse(response).webhook;
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
this.edgeInterceptors = {
|
|
734
|
+
list: async (workspaceId) => {
|
|
735
|
+
const response = await this.request(
|
|
736
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/edge/interceptors`
|
|
737
|
+
);
|
|
738
|
+
return EdgeInterceptorListSchema.parse(response).registrations;
|
|
739
|
+
},
|
|
740
|
+
register: async (workspaceId, input) => {
|
|
741
|
+
const response = await this.request(
|
|
742
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/edge/interceptors`,
|
|
743
|
+
{
|
|
744
|
+
method: "POST",
|
|
745
|
+
body: JSON.stringify({
|
|
746
|
+
...input,
|
|
747
|
+
workspaceId
|
|
748
|
+
})
|
|
749
|
+
}
|
|
750
|
+
);
|
|
751
|
+
return EdgeInterceptorMutationSchema.parse(response).registration;
|
|
752
|
+
},
|
|
753
|
+
setStatus: async (workspaceId, registrationId, status) => {
|
|
754
|
+
const parsedStatus = EdgeInterceptorStatusSchema.parse(status);
|
|
755
|
+
const response = await this.request(
|
|
756
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/edge/interceptors/${encodeURIComponent(
|
|
757
|
+
registrationId
|
|
758
|
+
)}/status`,
|
|
759
|
+
{
|
|
760
|
+
method: "PATCH",
|
|
761
|
+
body: JSON.stringify({ status: parsedStatus })
|
|
762
|
+
}
|
|
763
|
+
);
|
|
764
|
+
return EdgeInterceptorMutationSchema.parse(response).registration;
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
this.firewall = {
|
|
769
|
+
inspect: async (workspaceId, input) => {
|
|
770
|
+
const parsedInput = FirewallInspectBodySchema.parse(input);
|
|
771
|
+
const response = await this.request(
|
|
772
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/firewall/inspect`,
|
|
773
|
+
{
|
|
774
|
+
method: "POST",
|
|
775
|
+
body: JSON.stringify(parsedInput)
|
|
776
|
+
}
|
|
777
|
+
);
|
|
778
|
+
return FirewallInspectResponseSchema.parse(response);
|
|
779
|
+
},
|
|
780
|
+
events: async (workspaceId) => {
|
|
781
|
+
const response = await this.request(
|
|
782
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/firewall/events`
|
|
783
|
+
);
|
|
784
|
+
return FirewallEventsResponseSchema.parse(response).events;
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
async executeSession(
|
|
790
|
+
sessionId: string,
|
|
791
|
+
input: DriftGateSessionExecuteInput
|
|
792
|
+
): Promise<DriftGateCanonicalResponse<z.infer<typeof RunResponseSchema>>> {
|
|
793
|
+
const payload = V4ExecutionRequestSchema.parse(input);
|
|
794
|
+
const raw = await this.request(`/v4/sessions/${encodeURIComponent(sessionId)}/executions.execute`, {
|
|
795
|
+
method: "POST",
|
|
796
|
+
body: JSON.stringify(payload)
|
|
797
|
+
});
|
|
798
|
+
const parsed = V4ExecutionResponseSchema.parse(raw);
|
|
799
|
+
if (!parsed.ok || !parsed.data) {
|
|
800
|
+
const canonicalCode = parsed.error?.code ?? "INTERNAL";
|
|
801
|
+
throw new DriftGateError(
|
|
802
|
+
canonicalCode,
|
|
803
|
+
parsed.error?.message ?? "session.execute failed",
|
|
804
|
+
parsed.error?.status ?? 500,
|
|
805
|
+
parsed.meta.requestId,
|
|
806
|
+
parsed.error?.details
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
return {
|
|
810
|
+
ok: parsed.ok,
|
|
811
|
+
data: parsed.data,
|
|
812
|
+
meta: parsed.meta,
|
|
813
|
+
error: parsed.error,
|
|
814
|
+
raw
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async execute(
|
|
819
|
+
input: DriftGateEphemeralExecuteInput
|
|
820
|
+
): Promise<DriftGateCanonicalResponse<z.infer<typeof V4EphemeralExecuteDataSchema>>> {
|
|
821
|
+
const payload = V4EphemeralExecuteRequestBodySchema.parse(input);
|
|
822
|
+
const raw = await this.request("/v4/execute", {
|
|
823
|
+
method: "POST",
|
|
824
|
+
body: JSON.stringify(payload)
|
|
825
|
+
});
|
|
826
|
+
const parsed = V4EphemeralExecutionResponseSchema.parse(raw);
|
|
827
|
+
if (!parsed.ok || !parsed.data) {
|
|
828
|
+
const canonicalCode = parsed.error?.code ?? "INTERNAL";
|
|
829
|
+
throw new DriftGateError(
|
|
830
|
+
canonicalCode,
|
|
831
|
+
parsed.error?.message ?? "execute failed",
|
|
832
|
+
parsed.error?.status ?? 500,
|
|
833
|
+
parsed.meta.requestId,
|
|
834
|
+
parsed.error?.details
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
ok: parsed.ok,
|
|
839
|
+
data: parsed.data,
|
|
840
|
+
meta: parsed.meta,
|
|
841
|
+
error: parsed.error,
|
|
842
|
+
raw
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
async enableEdgeMode(input: EdgeInterceptorEnableInput): Promise<EdgeInterceptorRegistration> {
|
|
847
|
+
const registration = await this.edgeInterceptors.register(input.workspaceId, input.registration);
|
|
848
|
+
this.edgeInterceptorState = {
|
|
849
|
+
workspaceId: input.workspaceId,
|
|
850
|
+
registration,
|
|
851
|
+
enforcement: input.enforcement ?? "monitor",
|
|
852
|
+
hooks: input.hooks
|
|
853
|
+
};
|
|
854
|
+
return registration;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
disableEdgeMode(): void {
|
|
858
|
+
this.edgeInterceptorState = null;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
async run(input: DriftGateRunInput): Promise<z.infer<typeof RunResponseSchema>> {
|
|
862
|
+
const edgeDecision = this.evaluateEdgeDecision(input);
|
|
863
|
+
const edgeState = this.edgeInterceptorState;
|
|
864
|
+
if (edgeDecision && edgeState?.hooks?.beforeRun) {
|
|
865
|
+
await edgeState.hooks.beforeRun(input, edgeDecision);
|
|
866
|
+
}
|
|
867
|
+
if (edgeDecision && !edgeDecision.allowed) {
|
|
868
|
+
if (edgeState?.hooks?.onBlocked) {
|
|
869
|
+
await edgeState.hooks.onBlocked(input, edgeDecision);
|
|
870
|
+
}
|
|
871
|
+
if (edgeState?.enforcement === "enforce") {
|
|
872
|
+
throw new DriftGateError(
|
|
873
|
+
"edge_interceptor_denied",
|
|
874
|
+
edgeDecision.reasonText,
|
|
875
|
+
403,
|
|
876
|
+
undefined,
|
|
877
|
+
{
|
|
878
|
+
reasonCode: edgeDecision.reasonCode,
|
|
879
|
+
requiredCapabilities: edgeDecision.requiredCapabilities,
|
|
880
|
+
grantedCapabilities: edgeDecision.grantedCapabilities,
|
|
881
|
+
registrationId: edgeState.registration.registrationId
|
|
882
|
+
}
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const response = await this.request("/v1/headless/runs", {
|
|
888
|
+
method: "POST",
|
|
889
|
+
body: JSON.stringify(input)
|
|
890
|
+
});
|
|
891
|
+
const parsed = RunResponseSchema.parse(response);
|
|
892
|
+
if (edgeDecision && edgeState?.hooks?.afterRun) {
|
|
893
|
+
await edgeState.hooks.afterRun(input, edgeDecision, parsed);
|
|
894
|
+
}
|
|
895
|
+
return parsed;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
async status(runId: string): Promise<z.infer<typeof RunResponseSchema>> {
|
|
899
|
+
const response = await this.request(`/v1/headless/runs/${encodeURIComponent(runId)}`);
|
|
900
|
+
return RunResponseSchema.parse(response);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
async events(runId: string): Promise<z.infer<typeof RunEventsResponseSchema>["events"]> {
|
|
904
|
+
const response = await this.request(`/v1/headless/runs/${encodeURIComponent(runId)}/events`);
|
|
905
|
+
return RunEventsResponseSchema.parse(response).events;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
async waitForTerminal(
|
|
909
|
+
runId: string,
|
|
910
|
+
options: WaitForTerminalOptions = {}
|
|
911
|
+
): Promise<z.infer<typeof RunResponseSchema>> {
|
|
912
|
+
const intervalMs = options.intervalMs ?? 1_500;
|
|
913
|
+
const timeoutMs = options.timeoutMs ?? 120_000;
|
|
914
|
+
const startedAt = Date.now();
|
|
915
|
+
|
|
916
|
+
while (true) {
|
|
917
|
+
const current = await this.status(runId);
|
|
918
|
+
if (isTerminalState(current.run.state)) {
|
|
919
|
+
return current;
|
|
920
|
+
}
|
|
921
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
922
|
+
throw new DriftGateError("timeout", `run ${runId} did not reach terminal state before timeout`, 408);
|
|
923
|
+
}
|
|
924
|
+
await sleep(intervalMs);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
async deployWorkflow(input: DeployWorkflowInput): Promise<z.infer<typeof DeployResponseSchema>> {
|
|
929
|
+
const response = await this.request("/v1/headless/workflows/deploy", {
|
|
930
|
+
method: "POST",
|
|
931
|
+
body: JSON.stringify(input)
|
|
932
|
+
});
|
|
933
|
+
return DeployResponseSchema.parse(response);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async publishWorkflow(workflowId: string, workflowYaml?: string): Promise<WorkflowVersion> {
|
|
937
|
+
const response = await this.request(`/v1/headless/workflows/${encodeURIComponent(workflowId)}/publish`, {
|
|
938
|
+
method: "POST",
|
|
939
|
+
body: JSON.stringify(workflowYaml ? { workflowYaml } : {})
|
|
940
|
+
});
|
|
941
|
+
const parsed = PublishResponseSchema.parse(response);
|
|
942
|
+
return parsed.version;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
private evaluateEdgeDecision(input: DriftGateRunInput): EdgeInterceptorDecision | null {
|
|
946
|
+
const state = this.edgeInterceptorState;
|
|
947
|
+
if (!state || state.workspaceId !== input.workspaceId) {
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
if (state.registration.status !== "active") {
|
|
951
|
+
return {
|
|
952
|
+
allowed: true,
|
|
953
|
+
reasonCode: "edge.interceptor.disabled",
|
|
954
|
+
reasonText: "Edge interceptor registration is disabled; enforcement skipped.",
|
|
955
|
+
requiredCapabilities: [],
|
|
956
|
+
grantedCapabilities: state.registration.capabilities
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const requiredCapabilities = ["runs:create"];
|
|
961
|
+
const grantedCapabilities = state.registration.capabilities;
|
|
962
|
+
const grantedSet = new Set(grantedCapabilities);
|
|
963
|
+
const missingCapabilities = requiredCapabilities.filter(
|
|
964
|
+
(capability) => !grantedSet.has(capability)
|
|
965
|
+
);
|
|
966
|
+
if (missingCapabilities.length === 0) {
|
|
967
|
+
return {
|
|
968
|
+
allowed: true,
|
|
969
|
+
reasonCode: "edge.interceptor.allow",
|
|
970
|
+
reasonText: "Edge interceptor capability checks passed.",
|
|
971
|
+
requiredCapabilities,
|
|
972
|
+
grantedCapabilities
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return {
|
|
977
|
+
allowed: false,
|
|
978
|
+
reasonCode: "edge.interceptor.denied.missing_capability",
|
|
979
|
+
reasonText: `Missing required edge capabilities: ${missingCapabilities.join(", ")}`,
|
|
980
|
+
requiredCapabilities,
|
|
981
|
+
grantedCapabilities
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
private async request(path: string, init: RequestInit = {}): Promise<unknown> {
|
|
986
|
+
const headers = new Headers(init.headers ?? {});
|
|
987
|
+
if (!headers.has("content-type") && init.body) {
|
|
988
|
+
headers.set("content-type", "application/json");
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
if (this.apiKey) {
|
|
992
|
+
headers.set("x-driftgate-api-key", this.apiKey);
|
|
993
|
+
} else if (this.sessionToken) {
|
|
994
|
+
headers.set("authorization", `Bearer ${this.sessionToken}`);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
998
|
+
...init,
|
|
999
|
+
headers
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
const rawText = await response.text();
|
|
1003
|
+
const body = rawText.length > 0 ? safelyParseJson(rawText) : null;
|
|
1004
|
+
|
|
1005
|
+
if (!response.ok) {
|
|
1006
|
+
const canonicalEnvelope = CanonicalErrorEnvelopeSchema.safeParse(body);
|
|
1007
|
+
if (canonicalEnvelope.success) {
|
|
1008
|
+
throw new DriftGateError(
|
|
1009
|
+
canonicalEnvelope.data.error.code,
|
|
1010
|
+
canonicalEnvelope.data.error.message,
|
|
1011
|
+
canonicalEnvelope.data.error.status,
|
|
1012
|
+
canonicalEnvelope.data.meta.requestId,
|
|
1013
|
+
canonicalEnvelope.data.error.details
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const envelope = HeadlessErrorEnvelopeSchema.safeParse(body);
|
|
1018
|
+
if (envelope.success) {
|
|
1019
|
+
throw new DriftGateError(
|
|
1020
|
+
envelope.data.code,
|
|
1021
|
+
envelope.data.message,
|
|
1022
|
+
response.status,
|
|
1023
|
+
envelope.data.correlation_id,
|
|
1024
|
+
envelope.data.details
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const legacyEnvelope = LegacyErrorEnvelopeSchema.safeParse(body);
|
|
1029
|
+
if (legacyEnvelope.success) {
|
|
1030
|
+
throw new DriftGateError(
|
|
1031
|
+
legacyEnvelope.data.error,
|
|
1032
|
+
legacyEnvelope.data.message,
|
|
1033
|
+
response.status,
|
|
1034
|
+
undefined,
|
|
1035
|
+
legacyEnvelope.data.issues
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
throw new DriftGateError(
|
|
1040
|
+
"http_error",
|
|
1041
|
+
`request failed (${response.status})${rawText ? `: ${rawText}` : ""}`,
|
|
1042
|
+
response.status
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return body;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function safelyParseJson(input: string): unknown {
|
|
1051
|
+
try {
|
|
1052
|
+
return JSON.parse(input);
|
|
1053
|
+
} catch {
|
|
1054
|
+
return { raw: input };
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
export type DriftGateRunResponse = z.infer<typeof RunResponseSchema>;
|
|
1059
|
+
export type DriftGateRunEvent = z.infer<typeof RunEventsResponseSchema>["events"][number];
|
|
1060
|
+
export type DriftGatePolicyDecision = PolicyDecision;
|
|
1061
|
+
export type DriftGateEdgeInterceptorRegistration = EdgeInterceptorRegistration;
|
|
1062
|
+
export type DriftGateEdgeInterceptorDecision = EdgeInterceptorDecision;
|
|
1063
|
+
export type DriftGateFirewallInspectInput = FirewallInspectInput;
|
|
1064
|
+
export type DriftGateFirewallInspectResponse = FirewallInspectResponse;
|
|
1065
|
+
export type DriftGateFirewallEvent = FirewallEvent;
|