@ornncompute/cli 0.2.7 → 0.2.8
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/package.json +1 -1
- package/vendor/capabilities/capabilities/staff-access.d.ts +27 -0
- package/vendor/capabilities/capabilities/staff-access.js +192 -0
- package/vendor/capabilities/capabilities/staff-commerce.d.ts +83 -0
- package/vendor/capabilities/capabilities/staff-commerce.js +290 -0
- package/vendor/capabilities/capabilities/staff-fleet.d.ts +141 -0
- package/vendor/capabilities/capabilities/staff-fleet.js +465 -0
- package/vendor/capabilities/capabilities/staff-shared.d.ts +18 -0
- package/vendor/capabilities/capabilities/staff-shared.js +34 -0
- package/vendor/capabilities/capability.d.ts +14 -0
- package/vendor/capabilities/capability.js +14 -0
- package/vendor/capabilities/catalog.js +6 -0
- package/vendor/capabilities/index.d.ts +4 -1
- package/vendor/capabilities/index.js +4 -1
package/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const sshKeyPushInput: z.ZodObject<{
|
|
3
|
+
instanceId: z.ZodString;
|
|
4
|
+
reservationId: z.ZodString;
|
|
5
|
+
tenantId: z.ZodString;
|
|
6
|
+
authUserId: z.ZodString;
|
|
7
|
+
sshKeyIds: z.ZodArray<z.ZodString>;
|
|
8
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
9
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export declare const sshKeyPushCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
12
|
+
export declare const sshKeyAddInput: z.ZodObject<{
|
|
13
|
+
reservationId: z.ZodString;
|
|
14
|
+
tenantId: z.ZodString;
|
|
15
|
+
authUserId: z.ZodString;
|
|
16
|
+
publicKey: z.ZodString;
|
|
17
|
+
label: z.ZodOptional<z.ZodString>;
|
|
18
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
export declare const sshKeyAddCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
21
|
+
export declare const sshKeyRevokeInput: z.ZodObject<{
|
|
22
|
+
tenantId: z.ZodString;
|
|
23
|
+
keyId: z.ZodString;
|
|
24
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
export declare const sshKeyRevokeCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
27
|
+
export declare const staffAccessCapabilities: import("../capability.ts").Capability<unknown, unknown>[];
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineStaffMcpCapability, preview } from "../capability.js";
|
|
3
|
+
import { mapBody, seg, statusOf } from "./staff-shared.js";
|
|
4
|
+
// Staff SSH-access verbs. MCP-only — see `defineStaffMcpCapability`.
|
|
5
|
+
//
|
|
6
|
+
// Field contracts from the internal SSH-key routes:
|
|
7
|
+
// - push: PATCH /internal/nodes/{instanceId}/keys (or /internal/pods/... for a VM)
|
|
8
|
+
// body { ssh_authorized_keys, request_id? }
|
|
9
|
+
// - add: POST /internal/v1/reservations/{reservationId}/ssh-keys
|
|
10
|
+
// body { public_key, label? }, after authorization owns the public key
|
|
11
|
+
// - revoke: DELETE /v1/organizations/{tenantId}/ssh-keys/{keyId}
|
|
12
|
+
//
|
|
13
|
+
// The routes never forward tenantId/authUserId to the orchestrator as-is — they use them to
|
|
14
|
+
// verify the reservation actually belongs to that organization, and that authUserId is a member
|
|
15
|
+
// of it (commerce GET /internal/v1/reservations/{id} + authorization GET
|
|
16
|
+
// /internal/organizations/{id}/members). Those two checks live in
|
|
17
|
+
// `assertReservationOwnedByTenantUser` below so every surface runs them.
|
|
18
|
+
/** Acts as the caller for a write the tenant's own user is nominally making. */
|
|
19
|
+
const AUTH_USER_HEADER = "X-Auth-User-Id";
|
|
20
|
+
async function assertReservationOwnedByTenantUser(ctx, options) {
|
|
21
|
+
let reservation;
|
|
22
|
+
try {
|
|
23
|
+
reservation = (await ctx.fetch(`/internal/v1/reservations/${seg(options.reservationId)}`, {
|
|
24
|
+
method: "GET",
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (statusOf(error) === 404) {
|
|
29
|
+
throw new Error("Reservation not found.");
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
if (!reservation?.reservationId) {
|
|
34
|
+
throw new Error("Reservation not found.");
|
|
35
|
+
}
|
|
36
|
+
const organizationId = reservation.organizationId ?? reservation.tenantId;
|
|
37
|
+
if (organizationId !== options.tenantId) {
|
|
38
|
+
throw new Error("Reservation does not belong to that tenant.");
|
|
39
|
+
}
|
|
40
|
+
const members = (await ctx.fetch(`/internal/organizations/${seg(options.tenantId)}/members`, {
|
|
41
|
+
method: "GET",
|
|
42
|
+
}));
|
|
43
|
+
if (!members.some((member) => member.user_id === options.authUserId)) {
|
|
44
|
+
throw new Error("Selected SSH key owner is not a member of this tenant.");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function assertMachineBelongsToReservation(ctx, options) {
|
|
48
|
+
const listed = (await ctx.fetch(`/internal/nodes?reservation_id=${seg(options.reservationId)}`, {
|
|
49
|
+
method: "GET",
|
|
50
|
+
}));
|
|
51
|
+
if (!(listed.nodes ?? []).some((node) => node.node_id === options.instanceId || node.id === options.instanceId)) {
|
|
52
|
+
throw new Error("Machine does not belong to that reservation.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export const sshKeyPushInput = z.object({
|
|
56
|
+
instanceId: z.string(),
|
|
57
|
+
reservationId: z.string(),
|
|
58
|
+
tenantId: z.string(),
|
|
59
|
+
authUserId: z.string(),
|
|
60
|
+
sshKeyIds: z.array(z.string()).min(1),
|
|
61
|
+
requestId: z.string().optional(),
|
|
62
|
+
confirm: z.boolean().optional(),
|
|
63
|
+
});
|
|
64
|
+
export const sshKeyPushCapability = defineStaffMcpCapability({
|
|
65
|
+
id: "ssh-key.push",
|
|
66
|
+
domain: "access",
|
|
67
|
+
roles: ["admin"],
|
|
68
|
+
description: "Internal staff only: push selected reservation SSH keys onto a live machine. " +
|
|
69
|
+
"This grants remote access to a live machine. Requires confirm: true.",
|
|
70
|
+
mutation: "preview-confirm",
|
|
71
|
+
http: { method: "PATCH", path: "/internal/nodes/{instanceId}/keys" },
|
|
72
|
+
input: sshKeyPushInput,
|
|
73
|
+
execute: async (ctx, input) => {
|
|
74
|
+
const parsed = sshKeyPushInput.parse(input);
|
|
75
|
+
if (!ctx.confirmed) {
|
|
76
|
+
return preview("ssh-key.push", {
|
|
77
|
+
instanceId: parsed.instanceId,
|
|
78
|
+
reservationId: parsed.reservationId,
|
|
79
|
+
sshKeyIds: parsed.sshKeyIds,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Sequential, not Promise.all: this is a fail-fast authorization gate, not an independent
|
|
83
|
+
// read pair. If the reservation doesn't belong to this tenant/user, there's no reason to
|
|
84
|
+
// spend a second round trip checking machine membership.
|
|
85
|
+
await assertReservationOwnedByTenantUser(ctx, {
|
|
86
|
+
reservationId: parsed.reservationId,
|
|
87
|
+
tenantId: parsed.tenantId,
|
|
88
|
+
authUserId: parsed.authUserId,
|
|
89
|
+
});
|
|
90
|
+
await assertMachineBelongsToReservation(ctx, {
|
|
91
|
+
reservationId: parsed.reservationId,
|
|
92
|
+
instanceId: parsed.instanceId,
|
|
93
|
+
});
|
|
94
|
+
const listed = (await ctx.fetch(`/v1/organizations/${seg(parsed.tenantId)}/ssh-keys`, {
|
|
95
|
+
method: "GET",
|
|
96
|
+
}));
|
|
97
|
+
const wanted = new Set(parsed.sshKeyIds);
|
|
98
|
+
const sshAuthorizedKeys = (listed.ssh_keys ?? [])
|
|
99
|
+
.filter((key) => key.id &&
|
|
100
|
+
wanted.has(key.id) &&
|
|
101
|
+
key.status === "active" &&
|
|
102
|
+
!key.revoked_at &&
|
|
103
|
+
Boolean(key.public_key?.trim()))
|
|
104
|
+
.map((key) => key.public_key.trim());
|
|
105
|
+
const body = {
|
|
106
|
+
ssh_authorized_keys: sshAuthorizedKeys,
|
|
107
|
+
...mapBody(parsed, { requestId: "request_id" }),
|
|
108
|
+
};
|
|
109
|
+
let keysPath = `/internal/nodes/${seg(parsed.instanceId)}/keys`;
|
|
110
|
+
try {
|
|
111
|
+
const pod = (await ctx.fetch(`/internal/pods/${seg(parsed.instanceId)}`, {
|
|
112
|
+
method: "GET",
|
|
113
|
+
}));
|
|
114
|
+
if (pod.kind === "virtual-machine") {
|
|
115
|
+
keysPath = `/internal/pods/${seg(parsed.instanceId)}/keys`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// instanceId is a host node
|
|
120
|
+
}
|
|
121
|
+
return ctx.fetch(keysPath, {
|
|
122
|
+
method: "PATCH",
|
|
123
|
+
body,
|
|
124
|
+
headers: { [AUTH_USER_HEADER]: parsed.authUserId },
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
export const sshKeyAddInput = z.object({
|
|
129
|
+
reservationId: z.string(),
|
|
130
|
+
tenantId: z.string(),
|
|
131
|
+
authUserId: z.string(),
|
|
132
|
+
publicKey: z.string().min(1),
|
|
133
|
+
label: z.string().optional(),
|
|
134
|
+
confirm: z.boolean().optional(),
|
|
135
|
+
});
|
|
136
|
+
export const sshKeyAddCapability = defineStaffMcpCapability({
|
|
137
|
+
id: "ssh-key.add",
|
|
138
|
+
domain: "access",
|
|
139
|
+
roles: ["admin"],
|
|
140
|
+
description: "Internal staff only: register an SSH public key on a reservation for a tenant user. Requires confirm: true.",
|
|
141
|
+
mutation: "preview-confirm",
|
|
142
|
+
http: { method: "POST", path: "/internal/v1/reservations/{reservationId}/ssh-keys" },
|
|
143
|
+
input: sshKeyAddInput,
|
|
144
|
+
execute: async (ctx, input) => {
|
|
145
|
+
const parsed = sshKeyAddInput.parse(input);
|
|
146
|
+
const body = {
|
|
147
|
+
public_key: parsed.publicKey,
|
|
148
|
+
...mapBody(parsed, { label: "label" }),
|
|
149
|
+
};
|
|
150
|
+
if (!ctx.confirmed) {
|
|
151
|
+
return preview("ssh-key.add", { reservationId: parsed.reservationId, ...body });
|
|
152
|
+
}
|
|
153
|
+
await assertReservationOwnedByTenantUser(ctx, {
|
|
154
|
+
reservationId: parsed.reservationId,
|
|
155
|
+
tenantId: parsed.tenantId,
|
|
156
|
+
authUserId: parsed.authUserId,
|
|
157
|
+
});
|
|
158
|
+
return ctx.fetch(`/internal/v1/reservations/${seg(parsed.reservationId)}/ssh-keys`, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
body,
|
|
161
|
+
headers: { [AUTH_USER_HEADER]: parsed.authUserId },
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
export const sshKeyRevokeInput = z.object({
|
|
166
|
+
tenantId: z.string(),
|
|
167
|
+
keyId: z.string(),
|
|
168
|
+
confirm: z.boolean().optional(),
|
|
169
|
+
});
|
|
170
|
+
export const sshKeyRevokeCapability = defineStaffMcpCapability({
|
|
171
|
+
id: "ssh-key.revoke",
|
|
172
|
+
domain: "access",
|
|
173
|
+
roles: ["admin"],
|
|
174
|
+
description: "Internal staff only: revoke (delete) a tenant-scoped SSH key. Requires confirm: true.",
|
|
175
|
+
mutation: "preview-confirm",
|
|
176
|
+
http: { method: "DELETE", path: "/v1/organizations/{tenantId}/ssh-keys/{keyId}" },
|
|
177
|
+
input: sshKeyRevokeInput,
|
|
178
|
+
execute: async (ctx, input) => {
|
|
179
|
+
const parsed = sshKeyRevokeInput.parse(input);
|
|
180
|
+
if (!ctx.confirmed) {
|
|
181
|
+
return preview("ssh-key.revoke", { tenantId: parsed.tenantId, keyId: parsed.keyId });
|
|
182
|
+
}
|
|
183
|
+
return ctx.fetch(`/v1/organizations/${seg(parsed.tenantId)}/ssh-keys/${seg(parsed.keyId)}`, {
|
|
184
|
+
method: "DELETE",
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
export const staffAccessCapabilities = [
|
|
189
|
+
sshKeyPushCapability,
|
|
190
|
+
sshKeyAddCapability,
|
|
191
|
+
sshKeyRevokeCapability,
|
|
192
|
+
];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const reservationCancelInput: z.ZodObject<{
|
|
3
|
+
reservationId: z.ZodString;
|
|
4
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export declare const reservationCancelCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
7
|
+
export declare const reservationTransferInput: z.ZodObject<{
|
|
8
|
+
reservationId: z.ZodString;
|
|
9
|
+
targetTenantId: z.ZodString;
|
|
10
|
+
targetAuthUserId: z.ZodOptional<z.ZodString>;
|
|
11
|
+
nodeId: z.ZodOptional<z.ZodString>;
|
|
12
|
+
reservedStrategy: z.ZodEnum<{
|
|
13
|
+
reject: "reject";
|
|
14
|
+
park: "park";
|
|
15
|
+
}>;
|
|
16
|
+
confirmReservedTransfer: z.ZodOptional<z.ZodBoolean>;
|
|
17
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
18
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
export declare const reservationTransferCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
21
|
+
export declare const reservationMachinesListInput: z.ZodObject<{
|
|
22
|
+
reservationId: z.ZodString;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export declare const reservationMachinesListCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
25
|
+
export declare const reservationSshKeyAddInput: z.ZodObject<{
|
|
26
|
+
reservationId: z.ZodString;
|
|
27
|
+
userId: z.ZodString;
|
|
28
|
+
publicKey: z.ZodString;
|
|
29
|
+
label: z.ZodOptional<z.ZodString>;
|
|
30
|
+
nodeId: z.ZodOptional<z.ZodString>;
|
|
31
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
+
}, z.core.$strip>;
|
|
33
|
+
export declare const reservationSshKeyAddCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
34
|
+
export declare const reservationSshKeyRemoveInput: z.ZodObject<{
|
|
35
|
+
reservationId: z.ZodString;
|
|
36
|
+
keyId: z.ZodString;
|
|
37
|
+
userId: z.ZodString;
|
|
38
|
+
nodeId: z.ZodOptional<z.ZodString>;
|
|
39
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
export declare const reservationSshKeyRemoveCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
42
|
+
export declare const reservationPublishInput: z.ZodObject<{
|
|
43
|
+
reservationId: z.ZodString;
|
|
44
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
export declare const reservationPublishCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
47
|
+
export declare const bidAcceptInput: z.ZodObject<{
|
|
48
|
+
bidId: z.ZodString;
|
|
49
|
+
acceptedGpuCount: z.ZodOptional<z.ZodNumber>;
|
|
50
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
export declare const bidAcceptCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
53
|
+
export declare const bidRejectInput: z.ZodObject<{
|
|
54
|
+
bidId: z.ZodString;
|
|
55
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
export declare const bidRejectCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
58
|
+
export declare const inventoryUpdateInput: z.ZodObject<{
|
|
59
|
+
inventoryId: z.ZodString;
|
|
60
|
+
siteOperator: z.ZodOptional<z.ZodString>;
|
|
61
|
+
siteNickname: z.ZodOptional<z.ZodString>;
|
|
62
|
+
gpuType: z.ZodOptional<z.ZodString>;
|
|
63
|
+
nodeCount: z.ZodOptional<z.ZodNumber>;
|
|
64
|
+
gpusPerNode: z.ZodOptional<z.ZodNumber>;
|
|
65
|
+
availableFrom: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
66
|
+
availableTo: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
67
|
+
availableStartAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
68
|
+
availableEndAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
69
|
+
cpu: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
70
|
+
ram: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
71
|
+
fabricType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
72
|
+
storage: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
73
|
+
internet: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
74
|
+
networkHardware: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
75
|
+
buyNowPricePerGpuHour: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
76
|
+
confirm: z.ZodOptional<z.ZodBoolean>;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
export declare const inventoryUpdateCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
79
|
+
export declare const tenantCreditProfileShowInput: z.ZodObject<{
|
|
80
|
+
tenantId: z.ZodString;
|
|
81
|
+
}, z.core.$strip>;
|
|
82
|
+
export declare const tenantCreditProfileShowCapability: import("../capability.ts").Capability<unknown, unknown>;
|
|
83
|
+
export declare const staffCommerceCapabilities: import("../capability.ts").Capability<unknown, unknown>[];
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineStaffMcpCapability, preview } from "../capability.js";
|
|
3
|
+
import { mapBody, seg } from "./staff-shared.js";
|
|
4
|
+
// Staff commerce verbs: reservations, bids, forward inventory, and the tenant
|
|
5
|
+
// credit profile. MCP-only — see `defineStaffMcpCapability`.
|
|
6
|
+
export const reservationCancelInput = z.object({
|
|
7
|
+
reservationId: z.string(),
|
|
8
|
+
confirm: z.boolean().optional(),
|
|
9
|
+
});
|
|
10
|
+
export const reservationCancelCapability = defineStaffMcpCapability({
|
|
11
|
+
id: "reservation.cancel",
|
|
12
|
+
domain: "reservations",
|
|
13
|
+
roles: ["admin"],
|
|
14
|
+
description: "Internal staff only: cancel a reservation. Mirrors the internal dashboard's Cancel/Reject action.",
|
|
15
|
+
mutation: "preview-confirm",
|
|
16
|
+
http: { method: "POST", path: "/internal/reservations/{reservationId}/cancel" },
|
|
17
|
+
input: reservationCancelInput,
|
|
18
|
+
execute: async (ctx, input) => {
|
|
19
|
+
const parsed = reservationCancelInput.parse(input);
|
|
20
|
+
if (!ctx.confirmed) {
|
|
21
|
+
return preview("reservation.cancel", { reservationId: parsed.reservationId });
|
|
22
|
+
}
|
|
23
|
+
return ctx.fetch(`/internal/reservations/${seg(parsed.reservationId)}/cancel`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
export const reservationTransferInput = z.object({
|
|
29
|
+
reservationId: z.string(),
|
|
30
|
+
targetTenantId: z.string(),
|
|
31
|
+
targetAuthUserId: z.string().optional(),
|
|
32
|
+
nodeId: z.string().optional(),
|
|
33
|
+
reservedStrategy: z.enum(["reject", "park"]),
|
|
34
|
+
confirmReservedTransfer: z.boolean().optional(),
|
|
35
|
+
notes: z.string().max(2000).optional(),
|
|
36
|
+
confirm: z.boolean().optional(),
|
|
37
|
+
});
|
|
38
|
+
export const reservationTransferCapability = defineStaffMcpCapability({
|
|
39
|
+
id: "reservation.transfer",
|
|
40
|
+
domain: "reservations",
|
|
41
|
+
roles: ["admin"],
|
|
42
|
+
description: "Internal staff only: transfer a reservation to a different tenant/node. Mirrors the internal node-overlay transfer action. " +
|
|
43
|
+
"reservedStrategy has no default and must be chosen explicitly: \"reject\" (transfer only if the reservation isn't " +
|
|
44
|
+
"currently provisioned/assigned) or \"park\" (clean up old access and park it for the target tenant, for a live handoff). " +
|
|
45
|
+
"The dashboard always computes this from the reservation's live state before sending it rather than relying on the " +
|
|
46
|
+
"backend's own default of \"reject\", because a silent \"reject\" default on a reservation the backend doesn't detect as " +
|
|
47
|
+
"actively provisioned/assigned would transfer it without ever asking for confirm_reserved_transfer.",
|
|
48
|
+
mutation: "preview-confirm",
|
|
49
|
+
http: { method: "POST", path: "/internal/reservations/{reservationId}/transfer" },
|
|
50
|
+
input: reservationTransferInput,
|
|
51
|
+
execute: async (ctx, input) => {
|
|
52
|
+
const parsed = reservationTransferInput.parse(input);
|
|
53
|
+
const body = {
|
|
54
|
+
target_tenant_id: parsed.targetTenantId,
|
|
55
|
+
reserved_strategy: parsed.reservedStrategy,
|
|
56
|
+
confirm_reserved_transfer: parsed.confirmReservedTransfer ?? false,
|
|
57
|
+
...mapBody(parsed, {
|
|
58
|
+
targetAuthUserId: "target_auth_user_id",
|
|
59
|
+
nodeId: "node_id",
|
|
60
|
+
notes: "notes",
|
|
61
|
+
}),
|
|
62
|
+
};
|
|
63
|
+
if (!ctx.confirmed)
|
|
64
|
+
return preview("reservation.transfer", body);
|
|
65
|
+
return ctx.fetch(`/internal/reservations/${seg(parsed.reservationId)}/transfer`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
body,
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
export const reservationMachinesListInput = z.object({ reservationId: z.string() });
|
|
72
|
+
export const reservationMachinesListCapability = defineStaffMcpCapability({
|
|
73
|
+
id: "reservation.machines.list",
|
|
74
|
+
domain: "reservations",
|
|
75
|
+
roles: ["reviewer", "admin"],
|
|
76
|
+
description: "Internal staff only: list machines for a reservation.",
|
|
77
|
+
mutation: "read",
|
|
78
|
+
http: { method: "GET", path: "/internal/nodes" },
|
|
79
|
+
input: reservationMachinesListInput,
|
|
80
|
+
execute: async (ctx, input) => {
|
|
81
|
+
const parsed = reservationMachinesListInput.parse(input);
|
|
82
|
+
return ctx.fetch(`/internal/nodes?reservation_id=${seg(parsed.reservationId)}`, {
|
|
83
|
+
method: "GET",
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
export const reservationSshKeyAddInput = z.object({
|
|
88
|
+
reservationId: z.string(),
|
|
89
|
+
userId: z.string(),
|
|
90
|
+
publicKey: z.string().min(1),
|
|
91
|
+
label: z.string().optional(),
|
|
92
|
+
nodeId: z.string().optional(),
|
|
93
|
+
confirm: z.boolean().optional(),
|
|
94
|
+
});
|
|
95
|
+
export const reservationSshKeyAddCapability = defineStaffMcpCapability({
|
|
96
|
+
id: "reservation.ssh-key.add",
|
|
97
|
+
domain: "reservations",
|
|
98
|
+
roles: ["admin"],
|
|
99
|
+
description: "Internal staff only: add an SSH key to a reservation and queue sync to its machines. Requires confirm: true.",
|
|
100
|
+
mutation: "preview-confirm",
|
|
101
|
+
http: { method: "POST", path: "/internal/v1/reservations/{reservationId}/ssh-keys" },
|
|
102
|
+
input: reservationSshKeyAddInput,
|
|
103
|
+
execute: async (ctx, input) => {
|
|
104
|
+
const parsed = reservationSshKeyAddInput.parse(input);
|
|
105
|
+
const body = {
|
|
106
|
+
user_id: parsed.userId,
|
|
107
|
+
public_key: parsed.publicKey,
|
|
108
|
+
...mapBody(parsed, { label: "label", nodeId: "node_id" }),
|
|
109
|
+
};
|
|
110
|
+
if (!ctx.confirmed)
|
|
111
|
+
return preview("reservation.ssh-key.add", body);
|
|
112
|
+
return ctx.fetch(`/internal/v1/reservations/${seg(parsed.reservationId)}/ssh-keys`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
body,
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
export const reservationSshKeyRemoveInput = z.object({
|
|
119
|
+
reservationId: z.string(),
|
|
120
|
+
keyId: z.string(),
|
|
121
|
+
userId: z.string(),
|
|
122
|
+
nodeId: z.string().optional(),
|
|
123
|
+
confirm: z.boolean().optional(),
|
|
124
|
+
});
|
|
125
|
+
export const reservationSshKeyRemoveCapability = defineStaffMcpCapability({
|
|
126
|
+
id: "reservation.ssh-key.remove",
|
|
127
|
+
domain: "reservations",
|
|
128
|
+
roles: ["admin"],
|
|
129
|
+
description: "Internal staff only: remove an SSH key from a reservation and queue cleanup on its machines. Requires confirm: true.",
|
|
130
|
+
mutation: "preview-confirm",
|
|
131
|
+
http: { method: "DELETE", path: "/internal/v1/reservations/{reservationId}/ssh-keys/{keyId}" },
|
|
132
|
+
input: reservationSshKeyRemoveInput,
|
|
133
|
+
execute: async (ctx, input) => {
|
|
134
|
+
const parsed = reservationSshKeyRemoveInput.parse(input);
|
|
135
|
+
const body = {
|
|
136
|
+
user_id: parsed.userId,
|
|
137
|
+
...mapBody(parsed, { nodeId: "node_id" }),
|
|
138
|
+
};
|
|
139
|
+
if (!ctx.confirmed)
|
|
140
|
+
return preview("reservation.ssh-key.remove", body);
|
|
141
|
+
return ctx.fetch(`/internal/v1/reservations/${seg(parsed.reservationId)}/ssh-keys/${seg(parsed.keyId)}`, { method: "DELETE", body });
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
export const reservationPublishInput = z.object({
|
|
145
|
+
reservationId: z.string(),
|
|
146
|
+
confirm: z.boolean().optional(),
|
|
147
|
+
});
|
|
148
|
+
export const reservationPublishCapability = defineStaffMcpCapability({
|
|
149
|
+
id: "reservation.publish",
|
|
150
|
+
domain: "reservations",
|
|
151
|
+
roles: ["admin"],
|
|
152
|
+
description: "Internal staff only: publish a commerce draft into a live Commerce reservation. Requires confirm: true.",
|
|
153
|
+
mutation: "preview-confirm",
|
|
154
|
+
http: { method: "POST", path: "/internal/reservations/{reservationId}/publish" },
|
|
155
|
+
input: reservationPublishInput,
|
|
156
|
+
execute: async (ctx, input) => {
|
|
157
|
+
const parsed = reservationPublishInput.parse(input);
|
|
158
|
+
if (!ctx.confirmed) {
|
|
159
|
+
return preview("reservation.publish", { reservationId: parsed.reservationId });
|
|
160
|
+
}
|
|
161
|
+
return ctx.fetch(`/internal/reservations/${seg(parsed.reservationId)}/publish`, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
export const bidAcceptInput = z.object({
|
|
167
|
+
bidId: z.string(),
|
|
168
|
+
acceptedGpuCount: z.number().int().min(1).optional(),
|
|
169
|
+
confirm: z.boolean().optional(),
|
|
170
|
+
});
|
|
171
|
+
export const bidAcceptCapability = defineStaffMcpCapability({
|
|
172
|
+
id: "bid.accept",
|
|
173
|
+
domain: "bids",
|
|
174
|
+
roles: ["admin"],
|
|
175
|
+
description: "Internal staff only: accept an active bid. Optionally set acceptedGpuCount (defaults to the bid's full gpu_count). Mirrors the internal dashboard's Accept action.",
|
|
176
|
+
mutation: "preview-confirm",
|
|
177
|
+
http: { method: "POST", path: "/internal/bids/{bidId}/accept" },
|
|
178
|
+
input: bidAcceptInput,
|
|
179
|
+
execute: async (ctx, input) => {
|
|
180
|
+
const parsed = bidAcceptInput.parse(input);
|
|
181
|
+
const body = mapBody(parsed, { acceptedGpuCount: "accepted_gpu_count" });
|
|
182
|
+
if (!ctx.confirmed)
|
|
183
|
+
return preview("bid.accept", { bidId: parsed.bidId, ...body });
|
|
184
|
+
return ctx.fetch(`/internal/bids/${seg(parsed.bidId)}/accept`, { method: "POST", body });
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
export const bidRejectInput = z.object({
|
|
188
|
+
bidId: z.string(),
|
|
189
|
+
confirm: z.boolean().optional(),
|
|
190
|
+
});
|
|
191
|
+
export const bidRejectCapability = defineStaffMcpCapability({
|
|
192
|
+
id: "bid.reject",
|
|
193
|
+
domain: "bids",
|
|
194
|
+
roles: ["admin"],
|
|
195
|
+
description: "Internal staff only: reject an active bid. Mirrors the internal dashboard's Reject action.",
|
|
196
|
+
mutation: "preview-confirm",
|
|
197
|
+
http: { method: "POST", path: "/internal/bids/{bidId}/reject" },
|
|
198
|
+
input: bidRejectInput,
|
|
199
|
+
execute: async (ctx, input) => {
|
|
200
|
+
const parsed = bidRejectInput.parse(input);
|
|
201
|
+
if (!ctx.confirmed)
|
|
202
|
+
return preview("bid.reject", { bidId: parsed.bidId });
|
|
203
|
+
return ctx.fetch(`/internal/bids/${seg(parsed.bidId)}/reject`, { method: "POST" });
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
export const inventoryUpdateInput = z.object({
|
|
207
|
+
inventoryId: z.string(),
|
|
208
|
+
siteOperator: z.string().min(1).optional(),
|
|
209
|
+
siteNickname: z.string().min(1).optional(),
|
|
210
|
+
gpuType: z.string().min(1).optional(),
|
|
211
|
+
nodeCount: z
|
|
212
|
+
.number()
|
|
213
|
+
.int()
|
|
214
|
+
.positive()
|
|
215
|
+
.optional()
|
|
216
|
+
.describe("Total nodes (listing capacity), not total GPUs."),
|
|
217
|
+
gpusPerNode: z.number().int().min(1).max(32).optional(),
|
|
218
|
+
availableFrom: z.string().nullable().optional(),
|
|
219
|
+
availableTo: z.string().nullable().optional(),
|
|
220
|
+
availableStartAt: z.string().nullable().optional(),
|
|
221
|
+
availableEndAt: z.string().nullable().optional(),
|
|
222
|
+
cpu: z.string().nullable().optional(),
|
|
223
|
+
ram: z.string().nullable().optional(),
|
|
224
|
+
fabricType: z.string().nullable().optional(),
|
|
225
|
+
storage: z.string().nullable().optional(),
|
|
226
|
+
internet: z.string().nullable().optional(),
|
|
227
|
+
networkHardware: z.string().nullable().optional(),
|
|
228
|
+
buyNowPricePerGpuHour: z.number().positive().nullable().optional(),
|
|
229
|
+
confirm: z.boolean().optional(),
|
|
230
|
+
});
|
|
231
|
+
export const inventoryUpdateCapability = defineStaffMcpCapability({
|
|
232
|
+
id: "inventory.update",
|
|
233
|
+
domain: "listings",
|
|
234
|
+
roles: ["admin"],
|
|
235
|
+
description: "Internal staff only: patch editable fields on a forward inventory listing. Requires confirm: true. " +
|
|
236
|
+
"At least one editable field is required. buyNowPricePerGpuHour may be null to clear the price.",
|
|
237
|
+
mutation: "preview-confirm",
|
|
238
|
+
http: { method: "PATCH", path: "/inventory/{inventoryId}" },
|
|
239
|
+
input: inventoryUpdateInput,
|
|
240
|
+
execute: async (ctx, input) => {
|
|
241
|
+
const parsed = inventoryUpdateInput.parse(input);
|
|
242
|
+
const body = mapBody(parsed, {
|
|
243
|
+
siteOperator: "site_operator",
|
|
244
|
+
siteNickname: "site_nickname",
|
|
245
|
+
gpuType: "gpu_type",
|
|
246
|
+
nodeCount: "node_count",
|
|
247
|
+
gpusPerNode: "gpus_per_node",
|
|
248
|
+
availableFrom: "available_from",
|
|
249
|
+
availableTo: "available_to",
|
|
250
|
+
availableStartAt: "available_start_at",
|
|
251
|
+
availableEndAt: "available_end_at",
|
|
252
|
+
cpu: "cpu",
|
|
253
|
+
ram: "ram",
|
|
254
|
+
fabricType: "fabric_type",
|
|
255
|
+
storage: "storage",
|
|
256
|
+
internet: "internet",
|
|
257
|
+
networkHardware: "network_hardware",
|
|
258
|
+
buyNowPricePerGpuHour: "buy_now_price_per_gpu_hour",
|
|
259
|
+
});
|
|
260
|
+
if (!ctx.confirmed)
|
|
261
|
+
return preview("inventory.update", body);
|
|
262
|
+
return ctx.fetch(`/inventory/${seg(parsed.inventoryId)}`, { method: "PATCH", body });
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
export const tenantCreditProfileShowInput = z.object({ tenantId: z.string() });
|
|
266
|
+
export const tenantCreditProfileShowCapability = defineStaffMcpCapability({
|
|
267
|
+
id: "tenant.credit-profile.show",
|
|
268
|
+
domain: "users",
|
|
269
|
+
roles: ["reviewer", "admin"],
|
|
270
|
+
description: "Internal staff only: show a tenant's credit profile, approval status, and onboarding details. Read-only — does not approve or disapprove.",
|
|
271
|
+
mutation: "read",
|
|
272
|
+
http: { method: "GET", path: "/internal/organizations/{tenantId}" },
|
|
273
|
+
input: tenantCreditProfileShowInput,
|
|
274
|
+
execute: async (ctx, input) => {
|
|
275
|
+
const parsed = tenantCreditProfileShowInput.parse(input);
|
|
276
|
+
return ctx.fetch(`/internal/organizations/${seg(parsed.tenantId)}`, { method: "GET" });
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
export const staffCommerceCapabilities = [
|
|
280
|
+
reservationCancelCapability,
|
|
281
|
+
reservationTransferCapability,
|
|
282
|
+
reservationMachinesListCapability,
|
|
283
|
+
reservationSshKeyAddCapability,
|
|
284
|
+
reservationSshKeyRemoveCapability,
|
|
285
|
+
reservationPublishCapability,
|
|
286
|
+
bidAcceptCapability,
|
|
287
|
+
bidRejectCapability,
|
|
288
|
+
inventoryUpdateCapability,
|
|
289
|
+
tenantCreditProfileShowCapability,
|
|
290
|
+
];
|