@camstack/addon-training 0.1.6 → 0.1.7
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/dist/MaskShapeCanvas-Kfg9dl6B.mjs +686 -0
- package/dist/{MotionZonesSettings-DPtcv80q.mjs → MotionZonesSettings-DKtYdHUO.mjs} +101 -119
- package/dist/{PrivacyMaskSettings-Wsn5QL0_.mjs → PrivacyMaskSettings-BvLUVz86.mjs} +45 -70
- package/dist/ReactKonva-DGuYUN0L.mjs +5416 -0
- package/dist/{SceneMonitorEditor-BlMwyiH8.mjs → SceneMonitorEditor-rFYGkGHV.mjs} +82 -82
- package/dist/_stub.js +28146 -12781
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-Cbrdn9ib.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-B3xXUcaU.mjs} +3 -3
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C7dks3FG.mjs +26 -0
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-DpjirGmB.mjs +89 -0
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_client__loadShare__.js-CFCVuRAs.mjs +58 -0
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.js-BkvDudqY.mjs +62 -0
- package/dist/{_virtual_mf___mfe_internal__addon_training_page__loadShare__react__loadShare__.js-BT_wL8s3.mjs → _virtual_mf___mfe_internal__addon_training_page__loadShare__react__loadShare__.js-DeDbF7NI.mjs} +1 -1
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DnrbBvrw.mjs +34 -0
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom__loadShare__.js-DAZkHxxU.mjs +45 -0
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-CHqN9NLt.mjs +34 -0
- package/dist/addon-training.css +1 -1
- package/dist/{dist-BhCRZEE4.js → dist-DVDBSrjw.js} +270 -146
- package/dist/{dist-Cqa2fHNk.mjs → dist-Phot2UwD.mjs} +271 -141
- package/dist/grid-2x2-Bl3xw1w7.mjs +21 -0
- package/dist/{hostInit-BI98RYIm.mjs → hostInit-BAGO4XYA.mjs} +2 -2
- package/dist/index.js +100 -39
- package/dist/index.mjs +99 -38
- package/dist/lib-CVGFTwe0.mjs +7770 -0
- package/dist/orchestrator/training.addon.js +376 -222
- package/dist/orchestrator/training.addon.mjs +376 -222
- package/dist/{player-overlays-BX3obOMu.mjs → player-overlays-BT-6fK9N.mjs} +26 -7
- package/dist/providers/modal/training-modal.addon.js +361 -155
- package/dist/providers/modal/training-modal.addon.mjs +364 -158
- package/dist/remoteEntry.js +1 -1
- package/dist/{responsive-CdDIJCpI.mjs → responsive-Dy5RAD1s.mjs} +42 -38
- package/dist/{scene-monitor-copy-5u5w3K-Y.mjs → scene-monitor-copy-7HImRbA5.mjs} +4 -4
- package/dist/{square-C-P1-89Q.mjs → square-DoPBhbZN.mjs} +29 -4
- package/dist/{trash-2-BikGYiWf.mjs → trash-2-BwunNgLf.mjs} +1 -1
- package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-DGLUs0o6.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-gU49KvnZ.mjs} +399 -390
- package/package.json +1 -1
- package/dist/MaskShapeCanvas-BokARvOj.mjs +0 -13806
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BkKuzhOJ.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-CaB1jnkt.mjs +0 -25
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_client__loadShare__.js-B3rryujf.mjs +0 -25
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.js-nL4MGFRP.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DDBEvLdo.mjs +0 -26
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom__loadShare__.js-i_pnGDxd.mjs +0 -25
- package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-gTIQrhKs.mjs +0 -25
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as trainingProviderCapability, d as number, f as object, l as BaseAddon, m as string, p as record, u as boolean } from "../../dist-Phot2UwD.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path, { dirname, posix } from "node:path";
|
|
4
|
-
import { createHash, createPrivateKey, createPublicKey, createSign, randomBytes } from "node:crypto";
|
|
4
|
+
import { createHash, createPrivateKey, createPublicKey, createSign, randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
import { createWriteStream, readFileSync } from "node:fs";
|
|
6
6
|
import { mkdir, open, rename, rm, unlink } from "node:fs/promises";
|
|
7
7
|
import { createRequire as createRequire$1 } from "module";
|
|
8
|
-
import { randomFillSync, randomUUID } from "crypto";
|
|
8
|
+
import { randomFillSync, randomUUID as randomUUID$1 } from "crypto";
|
|
9
9
|
import { setTimeout as setTimeout$1 } from "timers/promises";
|
|
10
10
|
import { warn } from "node:console";
|
|
11
11
|
import { homedir } from "node:os";
|
|
@@ -39,6 +39,124 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
39
39
|
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
40
40
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
41
41
|
//#endregion
|
|
42
|
+
//#region src/providers/modal/modal-entries.ts
|
|
43
|
+
/**
|
|
44
|
+
* Modal provider entries — one credentials + defaults row per operator
|
|
45
|
+
* account. Pure helpers so the store is unit-tested without BaseAddon.
|
|
46
|
+
*
|
|
47
|
+
* A pre-multi-entry config (flat tokenId/tokenSecret on the addon) migrates
|
|
48
|
+
* to a single entry named {@link DEFAULT_MODAL_ENTRY_NAME}.
|
|
49
|
+
*/
|
|
50
|
+
var MODAL_TYPE_ID = "modal";
|
|
51
|
+
var DEFAULT_MODAL_ENTRY_NAME = "Default";
|
|
52
|
+
var MODAL_ICON_ASSET = "assets/training-modal/icon.svg";
|
|
53
|
+
var DEFAULT_GPU = "L4";
|
|
54
|
+
var DEFAULT_COST = 5;
|
|
55
|
+
var DEFAULT_HOURS = 4;
|
|
56
|
+
var DEFAULT_GRACE = 24;
|
|
57
|
+
function legacyConfigured(config) {
|
|
58
|
+
return (config.tokenId ?? "").trim() !== "" || (config.tokenSecret ?? "").trim() !== "";
|
|
59
|
+
}
|
|
60
|
+
/** Entries the operator sees — synthesises Default from legacy flat fields. */
|
|
61
|
+
function effectiveModalEntries(config) {
|
|
62
|
+
if (config.entries.length > 0) return config.entries;
|
|
63
|
+
if (!legacyConfigured(config)) return [];
|
|
64
|
+
return [{
|
|
65
|
+
id: "legacy-default",
|
|
66
|
+
name: DEFAULT_MODAL_ENTRY_NAME,
|
|
67
|
+
tokenId: config.tokenId ?? "",
|
|
68
|
+
tokenSecret: config.tokenSecret ?? "",
|
|
69
|
+
environment: config.environment ?? "",
|
|
70
|
+
gpuType: config.gpuType ?? DEFAULT_GPU,
|
|
71
|
+
region: config.region ?? "",
|
|
72
|
+
costCapUsd: config.costCapUsd ?? DEFAULT_COST,
|
|
73
|
+
maxRuntimeHours: config.maxRuntimeHours ?? DEFAULT_HOURS,
|
|
74
|
+
failedDatasetGraceHours: config.failedDatasetGraceHours ?? DEFAULT_GRACE
|
|
75
|
+
}];
|
|
76
|
+
}
|
|
77
|
+
function findModalEntry(config, entryId) {
|
|
78
|
+
return effectiveModalEntries(config).find((e) => e.id === entryId) ?? null;
|
|
79
|
+
}
|
|
80
|
+
/** Persist a Default entry from legacy flat fields; clear the flat secrets. */
|
|
81
|
+
function migrateLegacyModalConfig(config, newId) {
|
|
82
|
+
if (config.entries.length > 0) return config;
|
|
83
|
+
if (!legacyConfigured(config)) return config;
|
|
84
|
+
const entry = {
|
|
85
|
+
id: newId(),
|
|
86
|
+
name: DEFAULT_MODAL_ENTRY_NAME,
|
|
87
|
+
tokenId: config.tokenId ?? "",
|
|
88
|
+
tokenSecret: config.tokenSecret ?? "",
|
|
89
|
+
environment: config.environment ?? "",
|
|
90
|
+
gpuType: config.gpuType ?? DEFAULT_GPU,
|
|
91
|
+
region: config.region ?? "",
|
|
92
|
+
costCapUsd: config.costCapUsd ?? DEFAULT_COST,
|
|
93
|
+
maxRuntimeHours: config.maxRuntimeHours ?? DEFAULT_HOURS,
|
|
94
|
+
failedDatasetGraceHours: config.failedDatasetGraceHours ?? DEFAULT_GRACE
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
entries: [entry],
|
|
98
|
+
tokenId: "",
|
|
99
|
+
tokenSecret: "",
|
|
100
|
+
environment: "",
|
|
101
|
+
gpuType: entry.gpuType,
|
|
102
|
+
region: entry.region,
|
|
103
|
+
costCapUsd: entry.costCapUsd,
|
|
104
|
+
maxRuntimeHours: entry.maxRuntimeHours,
|
|
105
|
+
failedDatasetGraceHours: entry.failedDatasetGraceHours
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function createModalEntry(config, input, newId) {
|
|
109
|
+
if (input.typeId !== "modal") return {
|
|
110
|
+
ok: false,
|
|
111
|
+
reason: "unknown-type"
|
|
112
|
+
};
|
|
113
|
+
const base = migrateLegacyModalConfig(config, newId);
|
|
114
|
+
const entry = {
|
|
115
|
+
id: newId(),
|
|
116
|
+
name: input.name.trim() === "" ? DEFAULT_MODAL_ENTRY_NAME : input.name.trim(),
|
|
117
|
+
tokenId: "",
|
|
118
|
+
tokenSecret: "",
|
|
119
|
+
environment: "",
|
|
120
|
+
gpuType: base.gpuType ?? DEFAULT_GPU,
|
|
121
|
+
region: base.region ?? "",
|
|
122
|
+
costCapUsd: base.costCapUsd ?? DEFAULT_COST,
|
|
123
|
+
maxRuntimeHours: base.maxRuntimeHours ?? DEFAULT_HOURS,
|
|
124
|
+
failedDatasetGraceHours: base.failedDatasetGraceHours ?? DEFAULT_GRACE
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
ok: true,
|
|
128
|
+
entryId: entry.id,
|
|
129
|
+
config: {
|
|
130
|
+
...base,
|
|
131
|
+
entries: [...base.entries, entry],
|
|
132
|
+
tokenId: "",
|
|
133
|
+
tokenSecret: ""
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function deleteModalEntry(config, entryId) {
|
|
138
|
+
const current = effectiveModalEntries(config);
|
|
139
|
+
if (!current.some((e) => e.id === entryId)) return {
|
|
140
|
+
ok: false,
|
|
141
|
+
reason: "unknown-entry"
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
ok: true,
|
|
145
|
+
entryId,
|
|
146
|
+
config: {
|
|
147
|
+
entries: current.filter((e) => e.id !== entryId),
|
|
148
|
+
tokenId: "",
|
|
149
|
+
tokenSecret: "",
|
|
150
|
+
environment: config.environment ?? "",
|
|
151
|
+
gpuType: config.gpuType ?? DEFAULT_GPU,
|
|
152
|
+
region: config.region ?? "",
|
|
153
|
+
costCapUsd: config.costCapUsd ?? DEFAULT_COST,
|
|
154
|
+
maxRuntimeHours: config.maxRuntimeHours ?? DEFAULT_HOURS,
|
|
155
|
+
failedDatasetGraceHours: config.failedDatasetGraceHours ?? DEFAULT_GRACE
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
42
160
|
//#region src/providers/modal/dockerfile.ts
|
|
43
161
|
var ALLOWED = new Set([
|
|
44
162
|
"ARG",
|
|
@@ -234,30 +352,32 @@ var ModalTrainingProvider = class {
|
|
|
234
352
|
constructor(deps) {
|
|
235
353
|
this.deps = deps;
|
|
236
354
|
}
|
|
237
|
-
describe = async () =>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
failedDatasetGraceHours: s.failedDatasetGraceHours
|
|
256
|
-
}
|
|
257
|
-
}];
|
|
355
|
+
describe = async () => this.deps.listEntries().map((entry) => this.descriptorOf(entry));
|
|
356
|
+
describeTypes = async () => [{
|
|
357
|
+
typeId: MODAL_TYPE_ID,
|
|
358
|
+
label: "Modal",
|
|
359
|
+
iconAsset: MODAL_ICON_ASSET,
|
|
360
|
+
addonId: this.deps.addonId
|
|
361
|
+
}];
|
|
362
|
+
listEntries = async () => this.deps.listEntries().map((entry) => ({
|
|
363
|
+
entryId: entry.id,
|
|
364
|
+
typeId: MODAL_TYPE_ID,
|
|
365
|
+
name: entry.name,
|
|
366
|
+
addonId: this.deps.addonId,
|
|
367
|
+
configured: this.entryConfigured(entry)
|
|
368
|
+
}));
|
|
369
|
+
createEntry = async (input) => this.deps.createEntry(input);
|
|
370
|
+
deleteEntry = async (input) => {
|
|
371
|
+
await this.deps.deleteEntry(input.entryId);
|
|
372
|
+
return { ok: true };
|
|
258
373
|
};
|
|
259
|
-
validateCredentials = async () => {
|
|
260
|
-
|
|
374
|
+
validateCredentials = async (input) => {
|
|
375
|
+
if (this.deps.entry(input.entryId) === null) return {
|
|
376
|
+
ok: false,
|
|
377
|
+
reason: "missing",
|
|
378
|
+
detail: "unknown entry"
|
|
379
|
+
};
|
|
380
|
+
const gateway = this.deps.gateway(input.entryId);
|
|
261
381
|
if (gateway === null) return {
|
|
262
382
|
ok: false,
|
|
263
383
|
reason: "missing",
|
|
@@ -291,7 +411,7 @@ var ModalTrainingProvider = class {
|
|
|
291
411
|
};
|
|
292
412
|
};
|
|
293
413
|
uploadDataset = async (input) => {
|
|
294
|
-
const gateway = this.requireGateway();
|
|
414
|
+
const gateway = this.requireGateway(input.entryId);
|
|
295
415
|
const opened = await this.deps.peer.open(input.ticket);
|
|
296
416
|
if (opened.kind === "refused") throw new Error(`dataset offer refused: ${opened.code} — ${opened.detail}`);
|
|
297
417
|
const staging = await gateway.createSandbox({
|
|
@@ -342,7 +462,7 @@ var ModalTrainingProvider = class {
|
|
|
342
462
|
submitJob = async (input) => {
|
|
343
463
|
const existing = await this.deps.ledger.get(input.jobKey);
|
|
344
464
|
if (existing?.sandboxId != null) return this.statusOf(existing);
|
|
345
|
-
const gateway = this.requireGateway();
|
|
465
|
+
const gateway = this.requireGateway(input.entryId);
|
|
346
466
|
const parsed = parseTrainerDockerfile(await this.deps.fetchText(trainerDockerfileUrl(input.trainer.repo, input.trainer.revision)), input.trainer.revision);
|
|
347
467
|
if (parsed.kind === "refused") throw new Error(`trainer Dockerfile unusable on Modal: ${parsed.detail}`);
|
|
348
468
|
const sandbox = await gateway.createSandbox({
|
|
@@ -368,12 +488,14 @@ var ModalTrainingProvider = class {
|
|
|
368
488
|
},
|
|
369
489
|
tags: {
|
|
370
490
|
...tagsFor(input.jobKey),
|
|
371
|
-
role: "trainer"
|
|
491
|
+
role: "trainer",
|
|
492
|
+
entryId: input.entryId
|
|
372
493
|
},
|
|
373
494
|
name: `cst-${input.jobKey}`
|
|
374
495
|
});
|
|
375
496
|
const record = {
|
|
376
497
|
jobKey: input.jobKey,
|
|
498
|
+
entryId: input.entryId,
|
|
377
499
|
sandboxId: sandbox.sandboxId,
|
|
378
500
|
gpuType: input.gpuType,
|
|
379
501
|
region: input.region,
|
|
@@ -393,10 +515,10 @@ var ModalTrainingProvider = class {
|
|
|
393
515
|
};
|
|
394
516
|
getJob = async (input) => {
|
|
395
517
|
const record = await this.deps.ledger.get(input.jobKey);
|
|
396
|
-
if (record === null) return this.lost(input.jobKey);
|
|
518
|
+
if (record === null) return this.lost(input.jobKey, input.entryId);
|
|
397
519
|
if (record.sandboxId === null || record.endedAt !== null) return this.statusOf(record);
|
|
398
|
-
const sandbox = await this.requireGateway().sandboxFromId(record.sandboxId);
|
|
399
|
-
if (sandbox === null) return this.lost(input.jobKey);
|
|
520
|
+
const sandbox = await this.requireGateway(record.entryId).sandboxFromId(record.sandboxId);
|
|
521
|
+
if (sandbox === null) return this.lost(input.jobKey, record.entryId);
|
|
400
522
|
const code = await sandbox.poll();
|
|
401
523
|
if (code === null) return this.statusOf(record);
|
|
402
524
|
const ended = {
|
|
@@ -414,7 +536,7 @@ var ModalTrainingProvider = class {
|
|
|
414
536
|
lines: [],
|
|
415
537
|
cursor: `${String(cursor.ts)}:${String(cursor.seen)}`
|
|
416
538
|
};
|
|
417
|
-
const sandbox = await this.requireGateway().sandboxFromId(record.sandboxId);
|
|
539
|
+
const sandbox = await this.requireGateway(record.entryId).sandboxFromId(record.sandboxId);
|
|
418
540
|
if (sandbox === null) return {
|
|
419
541
|
lines: [],
|
|
420
542
|
cursor: `${String(cursor.ts)}:${String(cursor.seen)}`
|
|
@@ -444,7 +566,7 @@ var ModalTrainingProvider = class {
|
|
|
444
566
|
cancelJob = async (input) => {
|
|
445
567
|
const record = await this.deps.ledger.get(input.jobKey);
|
|
446
568
|
if (record?.sandboxId == null || record.endedAt !== null) return { ok: true };
|
|
447
|
-
await (await this.requireGateway().sandboxFromId(record.sandboxId))?.terminate();
|
|
569
|
+
await (await this.requireGateway(record.entryId).sandboxFromId(record.sandboxId))?.terminate();
|
|
448
570
|
await this.deps.ledger.put({
|
|
449
571
|
...record,
|
|
450
572
|
endedAt: this.deps.now(),
|
|
@@ -453,7 +575,7 @@ var ModalTrainingProvider = class {
|
|
|
453
575
|
return { ok: true };
|
|
454
576
|
};
|
|
455
577
|
readResult = async (input) => {
|
|
456
|
-
const collector = await this.collector(input.jobKey);
|
|
578
|
+
const collector = await this.collector(input.entryId, input.jobKey);
|
|
457
579
|
try {
|
|
458
580
|
const { stdout } = await collector.execText([
|
|
459
581
|
"sh",
|
|
@@ -473,7 +595,7 @@ var ModalTrainingProvider = class {
|
|
|
473
595
|
const offered = await this.deps.peer.offer({
|
|
474
596
|
label: "training-bundle",
|
|
475
597
|
handler: async (_req, res) => {
|
|
476
|
-
const collector = await this.collector(input.jobKey);
|
|
598
|
+
const collector = await this.collector(input.entryId, input.jobKey);
|
|
477
599
|
try {
|
|
478
600
|
const proc = await collector.execStream(["cat", `/data/out/${input.path}`]);
|
|
479
601
|
res.writeHead(200, { "content-type": "application/x-tar" });
|
|
@@ -496,37 +618,66 @@ var ModalTrainingProvider = class {
|
|
|
496
618
|
return { ticket: offered.ticket };
|
|
497
619
|
};
|
|
498
620
|
cleanupJob = async (input) => {
|
|
499
|
-
const gateway = this.requireGateway();
|
|
621
|
+
const gateway = this.requireGateway(input.entryId);
|
|
500
622
|
const deleted = [];
|
|
501
623
|
for (const sb of await gateway.listSandboxes(tagsFor(input.jobKey))) await (await gateway.sandboxFromId(sb.sandboxId))?.terminate();
|
|
502
624
|
if (await gateway.deleteVolume(jobVolumeName(input.jobKey))) deleted.push(jobVolumeName(input.jobKey));
|
|
503
625
|
await this.deps.ledger.remove(input.jobKey);
|
|
504
626
|
return { deleted };
|
|
505
627
|
};
|
|
506
|
-
listJobs = async () => {
|
|
507
|
-
const records = await this.deps.ledger.list();
|
|
628
|
+
listJobs = async (input) => {
|
|
629
|
+
const records = (await this.deps.ledger.list()).filter((r) => input.entryId === void 0 || r.entryId === input.entryId);
|
|
508
630
|
const known = new Set(records.map((r) => r.jobKey));
|
|
509
|
-
const statuses = await Promise.all(records.map((r) => this.getJob({
|
|
510
|
-
|
|
631
|
+
const statuses = await Promise.all(records.map((r) => this.getJob({
|
|
632
|
+
entryId: r.entryId,
|
|
633
|
+
jobKey: r.jobKey
|
|
634
|
+
})));
|
|
635
|
+
const probeEntryId = input.entryId ?? this.deps.listEntries().find((e) => this.entryConfigured(e))?.id;
|
|
636
|
+
if (probeEntryId === void 0) return statuses;
|
|
637
|
+
const gateway = this.deps.gateway(probeEntryId);
|
|
511
638
|
if (gateway === null) return statuses;
|
|
512
639
|
const strays = (await gateway.listSandboxes({ camstack: "training" })).map((s) => s.tags["jobKey"]).filter((k) => k !== void 0 && !known.has(k));
|
|
513
640
|
const unique = [...new Set(strays)];
|
|
514
641
|
return [...statuses, ...unique.map((jobKey) => ({
|
|
515
|
-
...this.lost(jobKey),
|
|
642
|
+
...this.lost(jobKey, probeEntryId),
|
|
516
643
|
state: "running"
|
|
517
644
|
}))];
|
|
518
645
|
};
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
return s.tokenId.trim() !== "" && s.tokenSecret.trim() !== "";
|
|
646
|
+
entryConfigured(entry) {
|
|
647
|
+
return entry.tokenId.trim() !== "" && entry.tokenSecret.trim() !== "";
|
|
522
648
|
}
|
|
523
|
-
|
|
524
|
-
|
|
649
|
+
descriptorOf(entry) {
|
|
650
|
+
return {
|
|
651
|
+
addonId: this.deps.addonId,
|
|
652
|
+
entryId: entry.id,
|
|
653
|
+
typeId: MODAL_TYPE_ID,
|
|
654
|
+
name: entry.name,
|
|
655
|
+
iconAsset: MODAL_ICON_ASSET,
|
|
656
|
+
kind: MODAL_TYPE_ID,
|
|
657
|
+
label: "Modal",
|
|
658
|
+
gpuTypes: MODAL_GPUS,
|
|
659
|
+
regions: MODAL_REGIONS,
|
|
660
|
+
maxDatasetBytes: MODAL_MAX_DATASET_BYTES,
|
|
661
|
+
maxRuntimeSec: MODAL_MAX_RUNTIME_SEC,
|
|
662
|
+
freeTierNote: "The Starter plan includes $30 of credit every month; Modal bills per second.",
|
|
663
|
+
dataResidencyNote: "Modal keeps the frames on a Volume of your workspace for the length of the job; the training container sees them only while it runs.",
|
|
664
|
+
configured: this.entryConfigured(entry),
|
|
665
|
+
defaults: {
|
|
666
|
+
gpuType: entry.gpuType,
|
|
667
|
+
region: entry.region,
|
|
668
|
+
costCapUsd: entry.costCapUsd,
|
|
669
|
+
maxRuntimeSec: Math.round(entry.maxRuntimeHours * 3600),
|
|
670
|
+
failedDatasetGraceHours: entry.failedDatasetGraceHours
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
requireGateway(entryId) {
|
|
675
|
+
const gateway = this.deps.gateway(entryId);
|
|
525
676
|
if (gateway === null) throw new Error("Modal is not configured: enter a token id and secret");
|
|
526
677
|
return gateway;
|
|
527
678
|
}
|
|
528
|
-
collector(jobKey) {
|
|
529
|
-
return this.requireGateway().createSandbox({
|
|
679
|
+
collector(entryId, jobKey) {
|
|
680
|
+
return this.requireGateway(entryId).createSandbox({
|
|
530
681
|
image: {
|
|
531
682
|
kind: "registry",
|
|
532
683
|
tag: MODAL_TOOLS_IMAGE
|
|
@@ -538,7 +689,8 @@ var ModalTrainingProvider = class {
|
|
|
538
689
|
volumes: { "/data": jobVolumeName(jobKey) },
|
|
539
690
|
tags: {
|
|
540
691
|
...tagsFor(jobKey),
|
|
541
|
-
role: "collector"
|
|
692
|
+
role: "collector",
|
|
693
|
+
entryId
|
|
542
694
|
}
|
|
543
695
|
});
|
|
544
696
|
}
|
|
@@ -562,11 +714,11 @@ var ModalTrainingProvider = class {
|
|
|
562
714
|
lastFetchedAt: now
|
|
563
715
|
};
|
|
564
716
|
}
|
|
565
|
-
lost(jobKey) {
|
|
717
|
+
lost(jobKey, entryId) {
|
|
566
718
|
return {
|
|
567
719
|
jobKey,
|
|
568
720
|
state: "lost",
|
|
569
|
-
gpuType: this.deps.
|
|
721
|
+
gpuType: this.deps.entry(entryId)?.gpuType ?? "L4",
|
|
570
722
|
billedSeconds: 0,
|
|
571
723
|
costUsd: 0,
|
|
572
724
|
costIsEstimate: true,
|
|
@@ -29750,7 +29902,7 @@ function rng() {
|
|
|
29750
29902
|
}
|
|
29751
29903
|
//#endregion
|
|
29752
29904
|
//#region ../../node_modules/modal/node_modules/uuid/dist/esm/native.js
|
|
29753
|
-
var native_default = { randomUUID };
|
|
29905
|
+
var native_default = { randomUUID: randomUUID$1 };
|
|
29754
29906
|
//#endregion
|
|
29755
29907
|
//#region ../../node_modules/modal/node_modules/uuid/dist/esm/v4.js
|
|
29756
29908
|
function v4(options, buf, offset) {
|
|
@@ -88336,17 +88488,21 @@ var ModalSdkGateway = class {
|
|
|
88336
88488
|
/**
|
|
88337
88489
|
* `training-modal` — the Modal `training-provider` (D807).
|
|
88338
88490
|
*
|
|
88339
|
-
* Inert until the operator enters a Modal token (the
|
|
88340
|
-
* posture): the provider registers anyway so the Training page
|
|
88341
|
-
*
|
|
88342
|
-
* the failed-job grace period are
|
|
88491
|
+
* Inert until the operator enters a Modal token on an **entry** (the
|
|
88492
|
+
* `loki-logging` posture): the provider registers anyway so the Training page
|
|
88493
|
+
* can list types and create entries. Region, GPU, cost cap, maximum runtime and
|
|
88494
|
+
* the failed-job grace period are per-entry settings (operator decision
|
|
88343
88495
|
* 2026-10-08: they depend on the user's account). The token secret is a
|
|
88344
|
-
* `password` field, so BaseAddon keeps it in the keychain
|
|
88345
|
-
* read from `this.config` and handed to the SDK client
|
|
88496
|
+
* `password` field on each entry row, so BaseAddon keeps it in the keychain
|
|
88497
|
+
* (D787) — it is read from `this.config` and handed to the SDK client
|
|
88498
|
+
* constructor only.
|
|
88499
|
+
*
|
|
88500
|
+
* A pre-multi-entry flat config migrates to one entry named "Default" on boot.
|
|
88346
88501
|
*/
|
|
88347
88502
|
var LEDGER_KEY = "modalJobs";
|
|
88348
88503
|
var ModalJobRecordSchema = object({
|
|
88349
88504
|
jobKey: string(),
|
|
88505
|
+
entryId: string().default(""),
|
|
88350
88506
|
sandboxId: string().nullable(),
|
|
88351
88507
|
gpuType: string(),
|
|
88352
88508
|
region: string(),
|
|
@@ -88364,10 +88520,10 @@ async function fetchText(url) {
|
|
|
88364
88520
|
}
|
|
88365
88521
|
var TrainingModalAddon = class extends BaseAddon {
|
|
88366
88522
|
ledgerState = null;
|
|
88367
|
-
|
|
88368
|
-
gatewayKey = "";
|
|
88523
|
+
gateways = /* @__PURE__ */ new Map();
|
|
88369
88524
|
constructor() {
|
|
88370
88525
|
super({
|
|
88526
|
+
entries: [],
|
|
88371
88527
|
tokenId: "",
|
|
88372
88528
|
tokenSecret: "",
|
|
88373
88529
|
environment: "",
|
|
@@ -88379,48 +88535,77 @@ var TrainingModalAddon = class extends BaseAddon {
|
|
|
88379
88535
|
});
|
|
88380
88536
|
}
|
|
88381
88537
|
async onInitialize() {
|
|
88538
|
+
if (this.config.entries.length === 0) {
|
|
88539
|
+
const migrated = migrateLegacyModalConfig(this.config, () => randomUUID());
|
|
88540
|
+
if (migrated.entries.length > 0) await this.updateGlobalSettings(migrated);
|
|
88541
|
+
}
|
|
88382
88542
|
this.ledgerState = this.state(LEDGER_KEY, LedgerSchema, {});
|
|
88383
88543
|
const peer = this.ctx.peerBytes;
|
|
88384
88544
|
if (peer === void 0) throw new Error("training-modal needs the peer-bytes facility");
|
|
88385
88545
|
const provider = new ModalTrainingProvider({
|
|
88386
88546
|
addonId: this.ctx.id,
|
|
88387
|
-
|
|
88388
|
-
|
|
88547
|
+
listEntries: () => effectiveModalEntries(this.config),
|
|
88548
|
+
entry: (entryId) => findModalEntry(this.config, entryId),
|
|
88549
|
+
gateway: (entryId) => this.gatewayFor(entryId),
|
|
88550
|
+
createEntry: async (input) => {
|
|
88551
|
+
const result = createModalEntry(this.config, input, () => randomUUID());
|
|
88552
|
+
if (!result.ok) throw new Error(`cannot create entry: ${result.reason}`);
|
|
88553
|
+
await this.updateGlobalSettings(result.config);
|
|
88554
|
+
return { entryId: result.entryId };
|
|
88555
|
+
},
|
|
88556
|
+
deleteEntry: async (entryId) => {
|
|
88557
|
+
const result = deleteModalEntry(this.config, entryId);
|
|
88558
|
+
if (!result.ok) throw new Error(`cannot delete entry: ${result.reason}`);
|
|
88559
|
+
await this.updateGlobalSettings(result.config);
|
|
88560
|
+
this.closeGateway(entryId);
|
|
88561
|
+
},
|
|
88389
88562
|
ledger: this.ledger(),
|
|
88390
88563
|
peer,
|
|
88391
88564
|
fetchText,
|
|
88392
88565
|
now: () => Date.now(),
|
|
88393
88566
|
logger: this.ctx.logger
|
|
88394
88567
|
});
|
|
88395
|
-
this.ctx.logger.info("Modal training provider ready", { meta: {
|
|
88568
|
+
this.ctx.logger.info("Modal training provider ready", { meta: {
|
|
88569
|
+
entries: effectiveModalEntries(this.config).length,
|
|
88570
|
+
configured: effectiveModalEntries(this.config).some((e) => e.tokenId.trim() !== "" && e.tokenSecret.trim() !== "")
|
|
88571
|
+
} });
|
|
88396
88572
|
return { providers: [{
|
|
88397
88573
|
capability: trainingProviderCapability,
|
|
88398
88574
|
provider
|
|
88399
88575
|
}] };
|
|
88400
88576
|
}
|
|
88401
88577
|
async onShutdown() {
|
|
88402
|
-
this.gateway
|
|
88403
|
-
this.
|
|
88578
|
+
for (const { gateway } of this.gateways.values()) gateway.close();
|
|
88579
|
+
this.gateways.clear();
|
|
88404
88580
|
}
|
|
88405
88581
|
async onConfigChanged() {
|
|
88406
|
-
this.gateway
|
|
88407
|
-
this.
|
|
88408
|
-
|
|
88409
|
-
|
|
88410
|
-
|
|
88411
|
-
|
|
88412
|
-
if (tokenId.trim() === "" || tokenSecret.trim() === "") return null;
|
|
88413
|
-
const key = `${tokenId}\u0000${environment}`;
|
|
88414
|
-
|
|
88415
|
-
|
|
88416
|
-
|
|
88417
|
-
|
|
88418
|
-
|
|
88419
|
-
|
|
88420
|
-
|
|
88421
|
-
|
|
88422
|
-
|
|
88423
|
-
|
|
88582
|
+
for (const { gateway } of this.gateways.values()) gateway.close();
|
|
88583
|
+
this.gateways.clear();
|
|
88584
|
+
}
|
|
88585
|
+
gatewayFor(entryId) {
|
|
88586
|
+
const entry = findModalEntry(this.config, entryId);
|
|
88587
|
+
if (entry === null) return null;
|
|
88588
|
+
if (entry.tokenId.trim() === "" || entry.tokenSecret.trim() === "") return null;
|
|
88589
|
+
const key = `${entry.tokenId}\u0000${entry.environment}`;
|
|
88590
|
+
const cached = this.gateways.get(entryId);
|
|
88591
|
+
if (cached !== void 0 && cached.key === key) return cached.gateway;
|
|
88592
|
+
cached?.gateway.close();
|
|
88593
|
+
const gateway = new ModalSdkGateway({
|
|
88594
|
+
tokenId: entry.tokenId.trim(),
|
|
88595
|
+
tokenSecret: entry.tokenSecret.trim(),
|
|
88596
|
+
environment: entry.environment.trim()
|
|
88597
|
+
});
|
|
88598
|
+
this.gateways.set(entryId, {
|
|
88599
|
+
key,
|
|
88600
|
+
gateway
|
|
88601
|
+
});
|
|
88602
|
+
return gateway;
|
|
88603
|
+
}
|
|
88604
|
+
closeGateway(entryId) {
|
|
88605
|
+
const cached = this.gateways.get(entryId);
|
|
88606
|
+
if (cached === void 0) return;
|
|
88607
|
+
cached.gateway.close();
|
|
88608
|
+
this.gateways.delete(entryId);
|
|
88424
88609
|
}
|
|
88425
88610
|
ledger() {
|
|
88426
88611
|
const state = () => {
|
|
@@ -88446,80 +88631,101 @@ var TrainingModalAddon = class extends BaseAddon {
|
|
|
88446
88631
|
}
|
|
88447
88632
|
globalSettingsSchema() {
|
|
88448
88633
|
return this.schema({ sections: [{
|
|
88449
|
-
id: "
|
|
88450
|
-
title: "Modal
|
|
88451
|
-
description: "
|
|
88452
|
-
fields: [
|
|
88453
|
-
|
|
88454
|
-
|
|
88455
|
-
|
|
88456
|
-
|
|
88457
|
-
|
|
88458
|
-
|
|
88459
|
-
|
|
88460
|
-
|
|
88461
|
-
|
|
88462
|
-
|
|
88463
|
-
|
|
88464
|
-
|
|
88465
|
-
|
|
88466
|
-
|
|
88467
|
-
|
|
88468
|
-
|
|
88469
|
-
|
|
88470
|
-
|
|
88471
|
-
|
|
88472
|
-
|
|
88473
|
-
|
|
88474
|
-
|
|
88475
|
-
|
|
88476
|
-
|
|
88477
|
-
|
|
88478
|
-
|
|
88479
|
-
|
|
88480
|
-
|
|
88481
|
-
|
|
88482
|
-
|
|
88483
|
-
|
|
88484
|
-
|
|
88485
|
-
|
|
88486
|
-
|
|
88487
|
-
|
|
88488
|
-
|
|
88489
|
-
|
|
88490
|
-
|
|
88491
|
-
|
|
88492
|
-
|
|
88493
|
-
|
|
88494
|
-
}
|
|
88495
|
-
|
|
88496
|
-
|
|
88497
|
-
|
|
88498
|
-
|
|
88499
|
-
|
|
88500
|
-
|
|
88501
|
-
|
|
88502
|
-
|
|
88503
|
-
|
|
88504
|
-
|
|
88505
|
-
|
|
88506
|
-
|
|
88507
|
-
|
|
88508
|
-
|
|
88509
|
-
|
|
88510
|
-
|
|
88511
|
-
|
|
88512
|
-
|
|
88513
|
-
|
|
88514
|
-
|
|
88515
|
-
|
|
88516
|
-
|
|
88517
|
-
|
|
88518
|
-
|
|
88519
|
-
|
|
88520
|
-
|
|
88521
|
-
|
|
88522
|
-
|
|
88634
|
+
id: "entries",
|
|
88635
|
+
title: "Modal accounts",
|
|
88636
|
+
description: "Each entry is one Modal workspace token. Create entries from the Training → Providers tab; edit credentials here. Secrets stay in the CamStack keychain.",
|
|
88637
|
+
fields: [{
|
|
88638
|
+
type: "editable-array",
|
|
88639
|
+
key: "entries",
|
|
88640
|
+
label: "Entries",
|
|
88641
|
+
itemIdKey: "id",
|
|
88642
|
+
emptyMessage: "No Modal entries yet — add one from Training → Providers.",
|
|
88643
|
+
addLabel: "Add entry",
|
|
88644
|
+
rowTitleTemplate: "{name}",
|
|
88645
|
+
defaultItem: {
|
|
88646
|
+
id: "",
|
|
88647
|
+
name: "Default",
|
|
88648
|
+
tokenId: "",
|
|
88649
|
+
tokenSecret: "",
|
|
88650
|
+
environment: "",
|
|
88651
|
+
gpuType: "L4",
|
|
88652
|
+
region: "",
|
|
88653
|
+
costCapUsd: 5,
|
|
88654
|
+
maxRuntimeHours: 4,
|
|
88655
|
+
failedDatasetGraceHours: 24
|
|
88656
|
+
},
|
|
88657
|
+
itemFields: [
|
|
88658
|
+
{
|
|
88659
|
+
type: "text",
|
|
88660
|
+
key: "id",
|
|
88661
|
+
label: "Entry id"
|
|
88662
|
+
},
|
|
88663
|
+
{
|
|
88664
|
+
type: "text",
|
|
88665
|
+
key: "name",
|
|
88666
|
+
label: "Name"
|
|
88667
|
+
},
|
|
88668
|
+
{
|
|
88669
|
+
type: "text",
|
|
88670
|
+
key: "tokenId",
|
|
88671
|
+
label: "Token id",
|
|
88672
|
+
placeholder: "ak-…"
|
|
88673
|
+
},
|
|
88674
|
+
{
|
|
88675
|
+
type: "password",
|
|
88676
|
+
key: "tokenSecret",
|
|
88677
|
+
label: "Token secret",
|
|
88678
|
+
showToggle: true
|
|
88679
|
+
},
|
|
88680
|
+
{
|
|
88681
|
+
type: "text",
|
|
88682
|
+
key: "environment",
|
|
88683
|
+
label: "Environment",
|
|
88684
|
+
description: "Leave empty for the workspace default."
|
|
88685
|
+
},
|
|
88686
|
+
{
|
|
88687
|
+
type: "select",
|
|
88688
|
+
key: "gpuType",
|
|
88689
|
+
label: "GPU",
|
|
88690
|
+
options: MODAL_GPUS.map((g) => ({
|
|
88691
|
+
value: g.id,
|
|
88692
|
+
label: `${g.label} — $${g.usdPerHour.toFixed(2)}/h`
|
|
88693
|
+
}))
|
|
88694
|
+
},
|
|
88695
|
+
{
|
|
88696
|
+
type: "select",
|
|
88697
|
+
key: "region",
|
|
88698
|
+
label: "Region",
|
|
88699
|
+
options: MODAL_REGIONS.map((r) => ({
|
|
88700
|
+
value: r.id,
|
|
88701
|
+
label: r.label
|
|
88702
|
+
}))
|
|
88703
|
+
},
|
|
88704
|
+
{
|
|
88705
|
+
type: "number",
|
|
88706
|
+
key: "costCapUsd",
|
|
88707
|
+
label: "Cost cap per job (USD)",
|
|
88708
|
+
min: .5,
|
|
88709
|
+
step: .5
|
|
88710
|
+
},
|
|
88711
|
+
{
|
|
88712
|
+
type: "number",
|
|
88713
|
+
key: "maxRuntimeHours",
|
|
88714
|
+
label: "Maximum runtime (hours)",
|
|
88715
|
+
min: .5,
|
|
88716
|
+
max: 24,
|
|
88717
|
+
step: .5
|
|
88718
|
+
},
|
|
88719
|
+
{
|
|
88720
|
+
type: "number",
|
|
88721
|
+
key: "failedDatasetGraceHours",
|
|
88722
|
+
label: "Keep a failed job’s dataset (hours)",
|
|
88723
|
+
min: 0,
|
|
88724
|
+
max: 168,
|
|
88725
|
+
step: 1
|
|
88726
|
+
}
|
|
88727
|
+
]
|
|
88728
|
+
}]
|
|
88523
88729
|
}] });
|
|
88524
88730
|
}
|
|
88525
88731
|
};
|