@camstack/addon-training 0.1.5 → 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-DKtYdHUO.mjs +292 -0
- package/dist/{PrivacyMaskSettings-BwYjIkQR.mjs → PrivacyMaskSettings-BvLUVz86.mjs} +45 -70
- package/dist/ReactKonva-DGuYUN0L.mjs +5416 -0
- package/dist/{SceneMonitorEditor-BBfow3Lm.mjs → SceneMonitorEditor-rFYGkGHV.mjs} +344 -344
- package/dist/_stub.js +25672 -10252
- package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-CGhkyuU-.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-BiSFqfpy.js → dist-DVDBSrjw.js} +282 -147
- package/dist/{dist-B_79m03G.mjs → dist-Phot2UwD.mjs} +283 -142
- package/dist/grid-2x2-Bl3xw1w7.mjs +21 -0
- package/dist/{hostInit-BEPknhsh.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-wE5IcDiI.mjs → player-overlays-BT-6fK9N.mjs} +29 -10
- 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-D17kMsm1.mjs → responsive-Dy5RAD1s.mjs} +42 -38
- package/dist/{scene-monitor-copy-LjlvhSRM.mjs → scene-monitor-copy-7HImRbA5.mjs} +4 -4
- package/dist/{square-JGDHhLlY.mjs → square-DoPBhbZN.mjs} +29 -4
- package/dist/{trash-2-zeFnLJvf.mjs → trash-2-BwunNgLf.mjs} +1 -1
- package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-C0RB_rkV.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/MotionZonesSettings-aj085Nrn.mjs +0 -310
- 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,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_dist = require("../../dist-
|
|
2
|
+
const require_dist = require("../../dist-DVDBSrjw.js");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
node_path = require_dist.__toESM(node_path, 1);
|
|
5
5
|
let node_crypto = require("node:crypto");
|
|
@@ -11,6 +11,124 @@ let timers_promises = require("timers/promises");
|
|
|
11
11
|
let node_console = require("node:console");
|
|
12
12
|
let node_os = require("node:os");
|
|
13
13
|
let node_stream = require("node:stream");
|
|
14
|
+
//#region src/providers/modal/modal-entries.ts
|
|
15
|
+
/**
|
|
16
|
+
* Modal provider entries — one credentials + defaults row per operator
|
|
17
|
+
* account. Pure helpers so the store is unit-tested without BaseAddon.
|
|
18
|
+
*
|
|
19
|
+
* A pre-multi-entry config (flat tokenId/tokenSecret on the addon) migrates
|
|
20
|
+
* to a single entry named {@link DEFAULT_MODAL_ENTRY_NAME}.
|
|
21
|
+
*/
|
|
22
|
+
var MODAL_TYPE_ID = "modal";
|
|
23
|
+
var DEFAULT_MODAL_ENTRY_NAME = "Default";
|
|
24
|
+
var MODAL_ICON_ASSET = "assets/training-modal/icon.svg";
|
|
25
|
+
var DEFAULT_GPU = "L4";
|
|
26
|
+
var DEFAULT_COST = 5;
|
|
27
|
+
var DEFAULT_HOURS = 4;
|
|
28
|
+
var DEFAULT_GRACE = 24;
|
|
29
|
+
function legacyConfigured(config) {
|
|
30
|
+
return (config.tokenId ?? "").trim() !== "" || (config.tokenSecret ?? "").trim() !== "";
|
|
31
|
+
}
|
|
32
|
+
/** Entries the operator sees — synthesises Default from legacy flat fields. */
|
|
33
|
+
function effectiveModalEntries(config) {
|
|
34
|
+
if (config.entries.length > 0) return config.entries;
|
|
35
|
+
if (!legacyConfigured(config)) return [];
|
|
36
|
+
return [{
|
|
37
|
+
id: "legacy-default",
|
|
38
|
+
name: DEFAULT_MODAL_ENTRY_NAME,
|
|
39
|
+
tokenId: config.tokenId ?? "",
|
|
40
|
+
tokenSecret: config.tokenSecret ?? "",
|
|
41
|
+
environment: config.environment ?? "",
|
|
42
|
+
gpuType: config.gpuType ?? DEFAULT_GPU,
|
|
43
|
+
region: config.region ?? "",
|
|
44
|
+
costCapUsd: config.costCapUsd ?? DEFAULT_COST,
|
|
45
|
+
maxRuntimeHours: config.maxRuntimeHours ?? DEFAULT_HOURS,
|
|
46
|
+
failedDatasetGraceHours: config.failedDatasetGraceHours ?? DEFAULT_GRACE
|
|
47
|
+
}];
|
|
48
|
+
}
|
|
49
|
+
function findModalEntry(config, entryId) {
|
|
50
|
+
return effectiveModalEntries(config).find((e) => e.id === entryId) ?? null;
|
|
51
|
+
}
|
|
52
|
+
/** Persist a Default entry from legacy flat fields; clear the flat secrets. */
|
|
53
|
+
function migrateLegacyModalConfig(config, newId) {
|
|
54
|
+
if (config.entries.length > 0) return config;
|
|
55
|
+
if (!legacyConfigured(config)) return config;
|
|
56
|
+
const entry = {
|
|
57
|
+
id: newId(),
|
|
58
|
+
name: DEFAULT_MODAL_ENTRY_NAME,
|
|
59
|
+
tokenId: config.tokenId ?? "",
|
|
60
|
+
tokenSecret: config.tokenSecret ?? "",
|
|
61
|
+
environment: config.environment ?? "",
|
|
62
|
+
gpuType: config.gpuType ?? DEFAULT_GPU,
|
|
63
|
+
region: config.region ?? "",
|
|
64
|
+
costCapUsd: config.costCapUsd ?? DEFAULT_COST,
|
|
65
|
+
maxRuntimeHours: config.maxRuntimeHours ?? DEFAULT_HOURS,
|
|
66
|
+
failedDatasetGraceHours: config.failedDatasetGraceHours ?? DEFAULT_GRACE
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
entries: [entry],
|
|
70
|
+
tokenId: "",
|
|
71
|
+
tokenSecret: "",
|
|
72
|
+
environment: "",
|
|
73
|
+
gpuType: entry.gpuType,
|
|
74
|
+
region: entry.region,
|
|
75
|
+
costCapUsd: entry.costCapUsd,
|
|
76
|
+
maxRuntimeHours: entry.maxRuntimeHours,
|
|
77
|
+
failedDatasetGraceHours: entry.failedDatasetGraceHours
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function createModalEntry(config, input, newId) {
|
|
81
|
+
if (input.typeId !== "modal") return {
|
|
82
|
+
ok: false,
|
|
83
|
+
reason: "unknown-type"
|
|
84
|
+
};
|
|
85
|
+
const base = migrateLegacyModalConfig(config, newId);
|
|
86
|
+
const entry = {
|
|
87
|
+
id: newId(),
|
|
88
|
+
name: input.name.trim() === "" ? DEFAULT_MODAL_ENTRY_NAME : input.name.trim(),
|
|
89
|
+
tokenId: "",
|
|
90
|
+
tokenSecret: "",
|
|
91
|
+
environment: "",
|
|
92
|
+
gpuType: base.gpuType ?? DEFAULT_GPU,
|
|
93
|
+
region: base.region ?? "",
|
|
94
|
+
costCapUsd: base.costCapUsd ?? DEFAULT_COST,
|
|
95
|
+
maxRuntimeHours: base.maxRuntimeHours ?? DEFAULT_HOURS,
|
|
96
|
+
failedDatasetGraceHours: base.failedDatasetGraceHours ?? DEFAULT_GRACE
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
entryId: entry.id,
|
|
101
|
+
config: {
|
|
102
|
+
...base,
|
|
103
|
+
entries: [...base.entries, entry],
|
|
104
|
+
tokenId: "",
|
|
105
|
+
tokenSecret: ""
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function deleteModalEntry(config, entryId) {
|
|
110
|
+
const current = effectiveModalEntries(config);
|
|
111
|
+
if (!current.some((e) => e.id === entryId)) return {
|
|
112
|
+
ok: false,
|
|
113
|
+
reason: "unknown-entry"
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
ok: true,
|
|
117
|
+
entryId,
|
|
118
|
+
config: {
|
|
119
|
+
entries: current.filter((e) => e.id !== entryId),
|
|
120
|
+
tokenId: "",
|
|
121
|
+
tokenSecret: "",
|
|
122
|
+
environment: config.environment ?? "",
|
|
123
|
+
gpuType: config.gpuType ?? DEFAULT_GPU,
|
|
124
|
+
region: config.region ?? "",
|
|
125
|
+
costCapUsd: config.costCapUsd ?? DEFAULT_COST,
|
|
126
|
+
maxRuntimeHours: config.maxRuntimeHours ?? DEFAULT_HOURS,
|
|
127
|
+
failedDatasetGraceHours: config.failedDatasetGraceHours ?? DEFAULT_GRACE
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
14
132
|
//#region src/providers/modal/dockerfile.ts
|
|
15
133
|
var ALLOWED = new Set([
|
|
16
134
|
"ARG",
|
|
@@ -206,30 +324,32 @@ var ModalTrainingProvider = class {
|
|
|
206
324
|
constructor(deps) {
|
|
207
325
|
this.deps = deps;
|
|
208
326
|
}
|
|
209
|
-
describe = async () =>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
failedDatasetGraceHours: s.failedDatasetGraceHours
|
|
228
|
-
}
|
|
229
|
-
}];
|
|
327
|
+
describe = async () => this.deps.listEntries().map((entry) => this.descriptorOf(entry));
|
|
328
|
+
describeTypes = async () => [{
|
|
329
|
+
typeId: MODAL_TYPE_ID,
|
|
330
|
+
label: "Modal",
|
|
331
|
+
iconAsset: MODAL_ICON_ASSET,
|
|
332
|
+
addonId: this.deps.addonId
|
|
333
|
+
}];
|
|
334
|
+
listEntries = async () => this.deps.listEntries().map((entry) => ({
|
|
335
|
+
entryId: entry.id,
|
|
336
|
+
typeId: MODAL_TYPE_ID,
|
|
337
|
+
name: entry.name,
|
|
338
|
+
addonId: this.deps.addonId,
|
|
339
|
+
configured: this.entryConfigured(entry)
|
|
340
|
+
}));
|
|
341
|
+
createEntry = async (input) => this.deps.createEntry(input);
|
|
342
|
+
deleteEntry = async (input) => {
|
|
343
|
+
await this.deps.deleteEntry(input.entryId);
|
|
344
|
+
return { ok: true };
|
|
230
345
|
};
|
|
231
|
-
validateCredentials = async () => {
|
|
232
|
-
|
|
346
|
+
validateCredentials = async (input) => {
|
|
347
|
+
if (this.deps.entry(input.entryId) === null) return {
|
|
348
|
+
ok: false,
|
|
349
|
+
reason: "missing",
|
|
350
|
+
detail: "unknown entry"
|
|
351
|
+
};
|
|
352
|
+
const gateway = this.deps.gateway(input.entryId);
|
|
233
353
|
if (gateway === null) return {
|
|
234
354
|
ok: false,
|
|
235
355
|
reason: "missing",
|
|
@@ -263,7 +383,7 @@ var ModalTrainingProvider = class {
|
|
|
263
383
|
};
|
|
264
384
|
};
|
|
265
385
|
uploadDataset = async (input) => {
|
|
266
|
-
const gateway = this.requireGateway();
|
|
386
|
+
const gateway = this.requireGateway(input.entryId);
|
|
267
387
|
const opened = await this.deps.peer.open(input.ticket);
|
|
268
388
|
if (opened.kind === "refused") throw new Error(`dataset offer refused: ${opened.code} — ${opened.detail}`);
|
|
269
389
|
const staging = await gateway.createSandbox({
|
|
@@ -314,7 +434,7 @@ var ModalTrainingProvider = class {
|
|
|
314
434
|
submitJob = async (input) => {
|
|
315
435
|
const existing = await this.deps.ledger.get(input.jobKey);
|
|
316
436
|
if (existing?.sandboxId != null) return this.statusOf(existing);
|
|
317
|
-
const gateway = this.requireGateway();
|
|
437
|
+
const gateway = this.requireGateway(input.entryId);
|
|
318
438
|
const parsed = parseTrainerDockerfile(await this.deps.fetchText(trainerDockerfileUrl(input.trainer.repo, input.trainer.revision)), input.trainer.revision);
|
|
319
439
|
if (parsed.kind === "refused") throw new Error(`trainer Dockerfile unusable on Modal: ${parsed.detail}`);
|
|
320
440
|
const sandbox = await gateway.createSandbox({
|
|
@@ -340,12 +460,14 @@ var ModalTrainingProvider = class {
|
|
|
340
460
|
},
|
|
341
461
|
tags: {
|
|
342
462
|
...tagsFor(input.jobKey),
|
|
343
|
-
role: "trainer"
|
|
463
|
+
role: "trainer",
|
|
464
|
+
entryId: input.entryId
|
|
344
465
|
},
|
|
345
466
|
name: `cst-${input.jobKey}`
|
|
346
467
|
});
|
|
347
468
|
const record = {
|
|
348
469
|
jobKey: input.jobKey,
|
|
470
|
+
entryId: input.entryId,
|
|
349
471
|
sandboxId: sandbox.sandboxId,
|
|
350
472
|
gpuType: input.gpuType,
|
|
351
473
|
region: input.region,
|
|
@@ -365,10 +487,10 @@ var ModalTrainingProvider = class {
|
|
|
365
487
|
};
|
|
366
488
|
getJob = async (input) => {
|
|
367
489
|
const record = await this.deps.ledger.get(input.jobKey);
|
|
368
|
-
if (record === null) return this.lost(input.jobKey);
|
|
490
|
+
if (record === null) return this.lost(input.jobKey, input.entryId);
|
|
369
491
|
if (record.sandboxId === null || record.endedAt !== null) return this.statusOf(record);
|
|
370
|
-
const sandbox = await this.requireGateway().sandboxFromId(record.sandboxId);
|
|
371
|
-
if (sandbox === null) return this.lost(input.jobKey);
|
|
492
|
+
const sandbox = await this.requireGateway(record.entryId).sandboxFromId(record.sandboxId);
|
|
493
|
+
if (sandbox === null) return this.lost(input.jobKey, record.entryId);
|
|
372
494
|
const code = await sandbox.poll();
|
|
373
495
|
if (code === null) return this.statusOf(record);
|
|
374
496
|
const ended = {
|
|
@@ -386,7 +508,7 @@ var ModalTrainingProvider = class {
|
|
|
386
508
|
lines: [],
|
|
387
509
|
cursor: `${String(cursor.ts)}:${String(cursor.seen)}`
|
|
388
510
|
};
|
|
389
|
-
const sandbox = await this.requireGateway().sandboxFromId(record.sandboxId);
|
|
511
|
+
const sandbox = await this.requireGateway(record.entryId).sandboxFromId(record.sandboxId);
|
|
390
512
|
if (sandbox === null) return {
|
|
391
513
|
lines: [],
|
|
392
514
|
cursor: `${String(cursor.ts)}:${String(cursor.seen)}`
|
|
@@ -416,7 +538,7 @@ var ModalTrainingProvider = class {
|
|
|
416
538
|
cancelJob = async (input) => {
|
|
417
539
|
const record = await this.deps.ledger.get(input.jobKey);
|
|
418
540
|
if (record?.sandboxId == null || record.endedAt !== null) return { ok: true };
|
|
419
|
-
await (await this.requireGateway().sandboxFromId(record.sandboxId))?.terminate();
|
|
541
|
+
await (await this.requireGateway(record.entryId).sandboxFromId(record.sandboxId))?.terminate();
|
|
420
542
|
await this.deps.ledger.put({
|
|
421
543
|
...record,
|
|
422
544
|
endedAt: this.deps.now(),
|
|
@@ -425,7 +547,7 @@ var ModalTrainingProvider = class {
|
|
|
425
547
|
return { ok: true };
|
|
426
548
|
};
|
|
427
549
|
readResult = async (input) => {
|
|
428
|
-
const collector = await this.collector(input.jobKey);
|
|
550
|
+
const collector = await this.collector(input.entryId, input.jobKey);
|
|
429
551
|
try {
|
|
430
552
|
const { stdout } = await collector.execText([
|
|
431
553
|
"sh",
|
|
@@ -445,7 +567,7 @@ var ModalTrainingProvider = class {
|
|
|
445
567
|
const offered = await this.deps.peer.offer({
|
|
446
568
|
label: "training-bundle",
|
|
447
569
|
handler: async (_req, res) => {
|
|
448
|
-
const collector = await this.collector(input.jobKey);
|
|
570
|
+
const collector = await this.collector(input.entryId, input.jobKey);
|
|
449
571
|
try {
|
|
450
572
|
const proc = await collector.execStream(["cat", `/data/out/${input.path}`]);
|
|
451
573
|
res.writeHead(200, { "content-type": "application/x-tar" });
|
|
@@ -468,37 +590,66 @@ var ModalTrainingProvider = class {
|
|
|
468
590
|
return { ticket: offered.ticket };
|
|
469
591
|
};
|
|
470
592
|
cleanupJob = async (input) => {
|
|
471
|
-
const gateway = this.requireGateway();
|
|
593
|
+
const gateway = this.requireGateway(input.entryId);
|
|
472
594
|
const deleted = [];
|
|
473
595
|
for (const sb of await gateway.listSandboxes(tagsFor(input.jobKey))) await (await gateway.sandboxFromId(sb.sandboxId))?.terminate();
|
|
474
596
|
if (await gateway.deleteVolume(jobVolumeName(input.jobKey))) deleted.push(jobVolumeName(input.jobKey));
|
|
475
597
|
await this.deps.ledger.remove(input.jobKey);
|
|
476
598
|
return { deleted };
|
|
477
599
|
};
|
|
478
|
-
listJobs = async () => {
|
|
479
|
-
const records = await this.deps.ledger.list();
|
|
600
|
+
listJobs = async (input) => {
|
|
601
|
+
const records = (await this.deps.ledger.list()).filter((r) => input.entryId === void 0 || r.entryId === input.entryId);
|
|
480
602
|
const known = new Set(records.map((r) => r.jobKey));
|
|
481
|
-
const statuses = await Promise.all(records.map((r) => this.getJob({
|
|
482
|
-
|
|
603
|
+
const statuses = await Promise.all(records.map((r) => this.getJob({
|
|
604
|
+
entryId: r.entryId,
|
|
605
|
+
jobKey: r.jobKey
|
|
606
|
+
})));
|
|
607
|
+
const probeEntryId = input.entryId ?? this.deps.listEntries().find((e) => this.entryConfigured(e))?.id;
|
|
608
|
+
if (probeEntryId === void 0) return statuses;
|
|
609
|
+
const gateway = this.deps.gateway(probeEntryId);
|
|
483
610
|
if (gateway === null) return statuses;
|
|
484
611
|
const strays = (await gateway.listSandboxes({ camstack: "training" })).map((s) => s.tags["jobKey"]).filter((k) => k !== void 0 && !known.has(k));
|
|
485
612
|
const unique = [...new Set(strays)];
|
|
486
613
|
return [...statuses, ...unique.map((jobKey) => ({
|
|
487
|
-
...this.lost(jobKey),
|
|
614
|
+
...this.lost(jobKey, probeEntryId),
|
|
488
615
|
state: "running"
|
|
489
616
|
}))];
|
|
490
617
|
};
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return s.tokenId.trim() !== "" && s.tokenSecret.trim() !== "";
|
|
618
|
+
entryConfigured(entry) {
|
|
619
|
+
return entry.tokenId.trim() !== "" && entry.tokenSecret.trim() !== "";
|
|
494
620
|
}
|
|
495
|
-
|
|
496
|
-
|
|
621
|
+
descriptorOf(entry) {
|
|
622
|
+
return {
|
|
623
|
+
addonId: this.deps.addonId,
|
|
624
|
+
entryId: entry.id,
|
|
625
|
+
typeId: MODAL_TYPE_ID,
|
|
626
|
+
name: entry.name,
|
|
627
|
+
iconAsset: MODAL_ICON_ASSET,
|
|
628
|
+
kind: MODAL_TYPE_ID,
|
|
629
|
+
label: "Modal",
|
|
630
|
+
gpuTypes: MODAL_GPUS,
|
|
631
|
+
regions: MODAL_REGIONS,
|
|
632
|
+
maxDatasetBytes: MODAL_MAX_DATASET_BYTES,
|
|
633
|
+
maxRuntimeSec: MODAL_MAX_RUNTIME_SEC,
|
|
634
|
+
freeTierNote: "The Starter plan includes $30 of credit every month; Modal bills per second.",
|
|
635
|
+
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.",
|
|
636
|
+
configured: this.entryConfigured(entry),
|
|
637
|
+
defaults: {
|
|
638
|
+
gpuType: entry.gpuType,
|
|
639
|
+
region: entry.region,
|
|
640
|
+
costCapUsd: entry.costCapUsd,
|
|
641
|
+
maxRuntimeSec: Math.round(entry.maxRuntimeHours * 3600),
|
|
642
|
+
failedDatasetGraceHours: entry.failedDatasetGraceHours
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
requireGateway(entryId) {
|
|
647
|
+
const gateway = this.deps.gateway(entryId);
|
|
497
648
|
if (gateway === null) throw new Error("Modal is not configured: enter a token id and secret");
|
|
498
649
|
return gateway;
|
|
499
650
|
}
|
|
500
|
-
collector(jobKey) {
|
|
501
|
-
return this.requireGateway().createSandbox({
|
|
651
|
+
collector(entryId, jobKey) {
|
|
652
|
+
return this.requireGateway(entryId).createSandbox({
|
|
502
653
|
image: {
|
|
503
654
|
kind: "registry",
|
|
504
655
|
tag: MODAL_TOOLS_IMAGE
|
|
@@ -510,7 +661,8 @@ var ModalTrainingProvider = class {
|
|
|
510
661
|
volumes: { "/data": jobVolumeName(jobKey) },
|
|
511
662
|
tags: {
|
|
512
663
|
...tagsFor(jobKey),
|
|
513
|
-
role: "collector"
|
|
664
|
+
role: "collector",
|
|
665
|
+
entryId
|
|
514
666
|
}
|
|
515
667
|
});
|
|
516
668
|
}
|
|
@@ -534,11 +686,11 @@ var ModalTrainingProvider = class {
|
|
|
534
686
|
lastFetchedAt: now
|
|
535
687
|
};
|
|
536
688
|
}
|
|
537
|
-
lost(jobKey) {
|
|
689
|
+
lost(jobKey, entryId) {
|
|
538
690
|
return {
|
|
539
691
|
jobKey,
|
|
540
692
|
state: "lost",
|
|
541
|
-
gpuType: this.deps.
|
|
693
|
+
gpuType: this.deps.entry(entryId)?.gpuType ?? "L4",
|
|
542
694
|
billedSeconds: 0,
|
|
543
695
|
costUsd: 0,
|
|
544
696
|
costIsEstimate: true,
|
|
@@ -88308,17 +88460,21 @@ var ModalSdkGateway = class {
|
|
|
88308
88460
|
/**
|
|
88309
88461
|
* `training-modal` — the Modal `training-provider` (D807).
|
|
88310
88462
|
*
|
|
88311
|
-
* Inert until the operator enters a Modal token (the
|
|
88312
|
-
* posture): the provider registers anyway so the Training page
|
|
88313
|
-
*
|
|
88314
|
-
* the failed-job grace period are
|
|
88463
|
+
* Inert until the operator enters a Modal token on an **entry** (the
|
|
88464
|
+
* `loki-logging` posture): the provider registers anyway so the Training page
|
|
88465
|
+
* can list types and create entries. Region, GPU, cost cap, maximum runtime and
|
|
88466
|
+
* the failed-job grace period are per-entry settings (operator decision
|
|
88315
88467
|
* 2026-10-08: they depend on the user's account). The token secret is a
|
|
88316
|
-
* `password` field, so BaseAddon keeps it in the keychain
|
|
88317
|
-
* read from `this.config` and handed to the SDK client
|
|
88468
|
+
* `password` field on each entry row, so BaseAddon keeps it in the keychain
|
|
88469
|
+
* (D787) — it is read from `this.config` and handed to the SDK client
|
|
88470
|
+
* constructor only.
|
|
88471
|
+
*
|
|
88472
|
+
* A pre-multi-entry flat config migrates to one entry named "Default" on boot.
|
|
88318
88473
|
*/
|
|
88319
88474
|
var LEDGER_KEY = "modalJobs";
|
|
88320
88475
|
var ModalJobRecordSchema = require_dist.object({
|
|
88321
88476
|
jobKey: require_dist.string(),
|
|
88477
|
+
entryId: require_dist.string().default(""),
|
|
88322
88478
|
sandboxId: require_dist.string().nullable(),
|
|
88323
88479
|
gpuType: require_dist.string(),
|
|
88324
88480
|
region: require_dist.string(),
|
|
@@ -88336,10 +88492,10 @@ async function fetchText(url) {
|
|
|
88336
88492
|
}
|
|
88337
88493
|
var TrainingModalAddon = class extends require_dist.BaseAddon {
|
|
88338
88494
|
ledgerState = null;
|
|
88339
|
-
|
|
88340
|
-
gatewayKey = "";
|
|
88495
|
+
gateways = /* @__PURE__ */ new Map();
|
|
88341
88496
|
constructor() {
|
|
88342
88497
|
super({
|
|
88498
|
+
entries: [],
|
|
88343
88499
|
tokenId: "",
|
|
88344
88500
|
tokenSecret: "",
|
|
88345
88501
|
environment: "",
|
|
@@ -88351,48 +88507,77 @@ var TrainingModalAddon = class extends require_dist.BaseAddon {
|
|
|
88351
88507
|
});
|
|
88352
88508
|
}
|
|
88353
88509
|
async onInitialize() {
|
|
88510
|
+
if (this.config.entries.length === 0) {
|
|
88511
|
+
const migrated = migrateLegacyModalConfig(this.config, () => (0, node_crypto.randomUUID)());
|
|
88512
|
+
if (migrated.entries.length > 0) await this.updateGlobalSettings(migrated);
|
|
88513
|
+
}
|
|
88354
88514
|
this.ledgerState = this.state(LEDGER_KEY, LedgerSchema, {});
|
|
88355
88515
|
const peer = this.ctx.peerBytes;
|
|
88356
88516
|
if (peer === void 0) throw new Error("training-modal needs the peer-bytes facility");
|
|
88357
88517
|
const provider = new ModalTrainingProvider({
|
|
88358
88518
|
addonId: this.ctx.id,
|
|
88359
|
-
|
|
88360
|
-
|
|
88519
|
+
listEntries: () => effectiveModalEntries(this.config),
|
|
88520
|
+
entry: (entryId) => findModalEntry(this.config, entryId),
|
|
88521
|
+
gateway: (entryId) => this.gatewayFor(entryId),
|
|
88522
|
+
createEntry: async (input) => {
|
|
88523
|
+
const result = createModalEntry(this.config, input, () => (0, node_crypto.randomUUID)());
|
|
88524
|
+
if (!result.ok) throw new Error(`cannot create entry: ${result.reason}`);
|
|
88525
|
+
await this.updateGlobalSettings(result.config);
|
|
88526
|
+
return { entryId: result.entryId };
|
|
88527
|
+
},
|
|
88528
|
+
deleteEntry: async (entryId) => {
|
|
88529
|
+
const result = deleteModalEntry(this.config, entryId);
|
|
88530
|
+
if (!result.ok) throw new Error(`cannot delete entry: ${result.reason}`);
|
|
88531
|
+
await this.updateGlobalSettings(result.config);
|
|
88532
|
+
this.closeGateway(entryId);
|
|
88533
|
+
},
|
|
88361
88534
|
ledger: this.ledger(),
|
|
88362
88535
|
peer,
|
|
88363
88536
|
fetchText,
|
|
88364
88537
|
now: () => Date.now(),
|
|
88365
88538
|
logger: this.ctx.logger
|
|
88366
88539
|
});
|
|
88367
|
-
this.ctx.logger.info("Modal training provider ready", { meta: {
|
|
88540
|
+
this.ctx.logger.info("Modal training provider ready", { meta: {
|
|
88541
|
+
entries: effectiveModalEntries(this.config).length,
|
|
88542
|
+
configured: effectiveModalEntries(this.config).some((e) => e.tokenId.trim() !== "" && e.tokenSecret.trim() !== "")
|
|
88543
|
+
} });
|
|
88368
88544
|
return { providers: [{
|
|
88369
88545
|
capability: require_dist.trainingProviderCapability,
|
|
88370
88546
|
provider
|
|
88371
88547
|
}] };
|
|
88372
88548
|
}
|
|
88373
88549
|
async onShutdown() {
|
|
88374
|
-
this.gateway
|
|
88375
|
-
this.
|
|
88550
|
+
for (const { gateway } of this.gateways.values()) gateway.close();
|
|
88551
|
+
this.gateways.clear();
|
|
88376
88552
|
}
|
|
88377
88553
|
async onConfigChanged() {
|
|
88378
|
-
this.gateway
|
|
88379
|
-
this.
|
|
88380
|
-
|
|
88381
|
-
|
|
88382
|
-
|
|
88383
|
-
|
|
88384
|
-
if (tokenId.trim() === "" || tokenSecret.trim() === "") return null;
|
|
88385
|
-
const key = `${tokenId}\u0000${environment}`;
|
|
88386
|
-
|
|
88387
|
-
|
|
88388
|
-
|
|
88389
|
-
|
|
88390
|
-
|
|
88391
|
-
|
|
88392
|
-
|
|
88393
|
-
|
|
88394
|
-
|
|
88395
|
-
|
|
88554
|
+
for (const { gateway } of this.gateways.values()) gateway.close();
|
|
88555
|
+
this.gateways.clear();
|
|
88556
|
+
}
|
|
88557
|
+
gatewayFor(entryId) {
|
|
88558
|
+
const entry = findModalEntry(this.config, entryId);
|
|
88559
|
+
if (entry === null) return null;
|
|
88560
|
+
if (entry.tokenId.trim() === "" || entry.tokenSecret.trim() === "") return null;
|
|
88561
|
+
const key = `${entry.tokenId}\u0000${entry.environment}`;
|
|
88562
|
+
const cached = this.gateways.get(entryId);
|
|
88563
|
+
if (cached !== void 0 && cached.key === key) return cached.gateway;
|
|
88564
|
+
cached?.gateway.close();
|
|
88565
|
+
const gateway = new ModalSdkGateway({
|
|
88566
|
+
tokenId: entry.tokenId.trim(),
|
|
88567
|
+
tokenSecret: entry.tokenSecret.trim(),
|
|
88568
|
+
environment: entry.environment.trim()
|
|
88569
|
+
});
|
|
88570
|
+
this.gateways.set(entryId, {
|
|
88571
|
+
key,
|
|
88572
|
+
gateway
|
|
88573
|
+
});
|
|
88574
|
+
return gateway;
|
|
88575
|
+
}
|
|
88576
|
+
closeGateway(entryId) {
|
|
88577
|
+
const cached = this.gateways.get(entryId);
|
|
88578
|
+
if (cached === void 0) return;
|
|
88579
|
+
cached.gateway.close();
|
|
88580
|
+
this.gateways.delete(entryId);
|
|
88396
88581
|
}
|
|
88397
88582
|
ledger() {
|
|
88398
88583
|
const state = () => {
|
|
@@ -88418,80 +88603,101 @@ var TrainingModalAddon = class extends require_dist.BaseAddon {
|
|
|
88418
88603
|
}
|
|
88419
88604
|
globalSettingsSchema() {
|
|
88420
88605
|
return this.schema({ sections: [{
|
|
88421
|
-
id: "
|
|
88422
|
-
title: "Modal
|
|
88423
|
-
description: "
|
|
88424
|
-
fields: [
|
|
88425
|
-
|
|
88426
|
-
|
|
88427
|
-
|
|
88428
|
-
|
|
88429
|
-
|
|
88430
|
-
|
|
88431
|
-
|
|
88432
|
-
|
|
88433
|
-
|
|
88434
|
-
|
|
88435
|
-
|
|
88436
|
-
|
|
88437
|
-
|
|
88438
|
-
|
|
88439
|
-
|
|
88440
|
-
|
|
88441
|
-
|
|
88442
|
-
|
|
88443
|
-
|
|
88444
|
-
|
|
88445
|
-
|
|
88446
|
-
|
|
88447
|
-
|
|
88448
|
-
|
|
88449
|
-
|
|
88450
|
-
|
|
88451
|
-
|
|
88452
|
-
|
|
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
|
-
|
|
88606
|
+
id: "entries",
|
|
88607
|
+
title: "Modal accounts",
|
|
88608
|
+
description: "Each entry is one Modal workspace token. Create entries from the Training → Providers tab; edit credentials here. Secrets stay in the CamStack keychain.",
|
|
88609
|
+
fields: [{
|
|
88610
|
+
type: "editable-array",
|
|
88611
|
+
key: "entries",
|
|
88612
|
+
label: "Entries",
|
|
88613
|
+
itemIdKey: "id",
|
|
88614
|
+
emptyMessage: "No Modal entries yet — add one from Training → Providers.",
|
|
88615
|
+
addLabel: "Add entry",
|
|
88616
|
+
rowTitleTemplate: "{name}",
|
|
88617
|
+
defaultItem: {
|
|
88618
|
+
id: "",
|
|
88619
|
+
name: "Default",
|
|
88620
|
+
tokenId: "",
|
|
88621
|
+
tokenSecret: "",
|
|
88622
|
+
environment: "",
|
|
88623
|
+
gpuType: "L4",
|
|
88624
|
+
region: "",
|
|
88625
|
+
costCapUsd: 5,
|
|
88626
|
+
maxRuntimeHours: 4,
|
|
88627
|
+
failedDatasetGraceHours: 24
|
|
88628
|
+
},
|
|
88629
|
+
itemFields: [
|
|
88630
|
+
{
|
|
88631
|
+
type: "text",
|
|
88632
|
+
key: "id",
|
|
88633
|
+
label: "Entry id"
|
|
88634
|
+
},
|
|
88635
|
+
{
|
|
88636
|
+
type: "text",
|
|
88637
|
+
key: "name",
|
|
88638
|
+
label: "Name"
|
|
88639
|
+
},
|
|
88640
|
+
{
|
|
88641
|
+
type: "text",
|
|
88642
|
+
key: "tokenId",
|
|
88643
|
+
label: "Token id",
|
|
88644
|
+
placeholder: "ak-…"
|
|
88645
|
+
},
|
|
88646
|
+
{
|
|
88647
|
+
type: "password",
|
|
88648
|
+
key: "tokenSecret",
|
|
88649
|
+
label: "Token secret",
|
|
88650
|
+
showToggle: true
|
|
88651
|
+
},
|
|
88652
|
+
{
|
|
88653
|
+
type: "text",
|
|
88654
|
+
key: "environment",
|
|
88655
|
+
label: "Environment",
|
|
88656
|
+
description: "Leave empty for the workspace default."
|
|
88657
|
+
},
|
|
88658
|
+
{
|
|
88659
|
+
type: "select",
|
|
88660
|
+
key: "gpuType",
|
|
88661
|
+
label: "GPU",
|
|
88662
|
+
options: MODAL_GPUS.map((g) => ({
|
|
88663
|
+
value: g.id,
|
|
88664
|
+
label: `${g.label} — $${g.usdPerHour.toFixed(2)}/h`
|
|
88665
|
+
}))
|
|
88666
|
+
},
|
|
88667
|
+
{
|
|
88668
|
+
type: "select",
|
|
88669
|
+
key: "region",
|
|
88670
|
+
label: "Region",
|
|
88671
|
+
options: MODAL_REGIONS.map((r) => ({
|
|
88672
|
+
value: r.id,
|
|
88673
|
+
label: r.label
|
|
88674
|
+
}))
|
|
88675
|
+
},
|
|
88676
|
+
{
|
|
88677
|
+
type: "number",
|
|
88678
|
+
key: "costCapUsd",
|
|
88679
|
+
label: "Cost cap per job (USD)",
|
|
88680
|
+
min: .5,
|
|
88681
|
+
step: .5
|
|
88682
|
+
},
|
|
88683
|
+
{
|
|
88684
|
+
type: "number",
|
|
88685
|
+
key: "maxRuntimeHours",
|
|
88686
|
+
label: "Maximum runtime (hours)",
|
|
88687
|
+
min: .5,
|
|
88688
|
+
max: 24,
|
|
88689
|
+
step: .5
|
|
88690
|
+
},
|
|
88691
|
+
{
|
|
88692
|
+
type: "number",
|
|
88693
|
+
key: "failedDatasetGraceHours",
|
|
88694
|
+
label: "Keep a failed job’s dataset (hours)",
|
|
88695
|
+
min: 0,
|
|
88696
|
+
max: 168,
|
|
88697
|
+
step: 1
|
|
88698
|
+
}
|
|
88699
|
+
]
|
|
88700
|
+
}]
|
|
88495
88701
|
}] });
|
|
88496
88702
|
}
|
|
88497
88703
|
};
|