@mcoda/mswarm 0.1.78 → 0.1.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/dist/runtime.d.ts +74 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +449 -39
- package/dist/runtime.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +15 -0
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/dist/runtime.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { chmod, lstat, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
-
import { hostname, homedir, platform, userInfo } from "node:os";
|
|
3
|
+
import { cpus, freemem, hostname, homedir, loadavg, platform, totalmem, userInfo } from "node:os";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { createHash, createHmac, randomUUID } from "node:crypto";
|
|
6
6
|
import { MswarmCodaliExecutor } from "./codali-executor.js";
|
|
@@ -16,6 +16,11 @@ const DEFAULT_REQUEST_TIMEOUT_MS = 10000;
|
|
|
16
16
|
const DEFAULT_JOB_TIMEOUT_MS = 3600000;
|
|
17
17
|
const DEFAULT_SERVICE_COMMAND_TIMEOUT_MS = 60000;
|
|
18
18
|
const DEFAULT_CAPABILITY_PROBE_TIMEOUT_MS = 2000;
|
|
19
|
+
const SELF_HOSTED_RUNTIME_PROTOCOL_VERSION = 1;
|
|
20
|
+
const SELF_HOSTED_LOAD_BALANCER_PROTOCOL_VERSION = 1;
|
|
21
|
+
const SELF_HOSTED_CATALOG_METADATA_VERSION = 1;
|
|
22
|
+
const MAX_TELEMETRY_LATENCY_SAMPLES = 50;
|
|
23
|
+
const MAX_TELEMETRY_FAILURES = 20;
|
|
19
24
|
const DEFAULT_MCODA_BIN = "mcoda";
|
|
20
25
|
const DEFAULT_MCODA_LIST_ARGS = ["agent", "list", "--json", "--refresh-health"];
|
|
21
26
|
const DEFAULT_COMMAND_MAX_BUFFER = 16 * 1024 * 1024;
|
|
@@ -460,6 +465,112 @@ function optionalBoolean(...values) {
|
|
|
460
465
|
}
|
|
461
466
|
return null;
|
|
462
467
|
}
|
|
468
|
+
function roundedTelemetryNumber(value, digits = 3) {
|
|
469
|
+
if (!Number.isFinite(value)) {
|
|
470
|
+
return 0;
|
|
471
|
+
}
|
|
472
|
+
const factor = 10 ** digits;
|
|
473
|
+
return Math.round(value * factor) / factor;
|
|
474
|
+
}
|
|
475
|
+
function nonNegativeTelemetryInteger(value) {
|
|
476
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
477
|
+
}
|
|
478
|
+
function sha256Json(value) {
|
|
479
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
480
|
+
}
|
|
481
|
+
function buildCatalogFingerprint(models) {
|
|
482
|
+
const projection = models
|
|
483
|
+
.map((model) => ({
|
|
484
|
+
name: optionalText(model.name) || "",
|
|
485
|
+
provider: optionalText(model.provider) || null,
|
|
486
|
+
adapter: optionalText(model.adapter) || null,
|
|
487
|
+
source_agent_slug: optionalText(model.source_agent_slug) || null,
|
|
488
|
+
model_id: optionalText(model.model_id) || optionalText(model.model) || null,
|
|
489
|
+
exposed: model.exposed !== false,
|
|
490
|
+
capabilities: normalizeCapabilities(model.capabilities).sort(),
|
|
491
|
+
health_status: normalizeHealthStatus(model.health_status)
|
|
492
|
+
}))
|
|
493
|
+
.sort((left, right) => `${left.provider || ""}:${left.name}`.localeCompare(`${right.provider || ""}:${right.name}`));
|
|
494
|
+
return `sha256:${sha256Json(projection)}`;
|
|
495
|
+
}
|
|
496
|
+
function executionClassCapacity(input) {
|
|
497
|
+
const maxConcurrency = Math.max(1, Math.floor(input.maxConcurrency));
|
|
498
|
+
const activeJobs = nonNegativeTelemetryInteger(input.activeJobs);
|
|
499
|
+
const queuedJobs = nonNegativeTelemetryInteger(input.queuedJobs);
|
|
500
|
+
return {
|
|
501
|
+
max_concurrency: maxConcurrency,
|
|
502
|
+
active_jobs: activeJobs,
|
|
503
|
+
queued_jobs: queuedJobs,
|
|
504
|
+
free_slots: input.drainMode ? 0 : Math.max(0, maxConcurrency - activeJobs - queuedJobs)
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
function totalHostMemoryBucket() {
|
|
508
|
+
const gib = totalmem() / (1024 ** 3);
|
|
509
|
+
if (!Number.isFinite(gib) || gib <= 0)
|
|
510
|
+
return "unknown";
|
|
511
|
+
if (gib <= 8)
|
|
512
|
+
return "<=8GiB";
|
|
513
|
+
if (gib <= 16)
|
|
514
|
+
return "<=16GiB";
|
|
515
|
+
if (gib <= 32)
|
|
516
|
+
return "<=32GiB";
|
|
517
|
+
if (gib <= 64)
|
|
518
|
+
return "<=64GiB";
|
|
519
|
+
if (gib <= 128)
|
|
520
|
+
return "<=128GiB";
|
|
521
|
+
return ">128GiB";
|
|
522
|
+
}
|
|
523
|
+
function coarsePublicVramTier(value, gpuCount) {
|
|
524
|
+
if (value === "none" ||
|
|
525
|
+
value === "lt8" ||
|
|
526
|
+
value === "8-15" ||
|
|
527
|
+
value === "16-31" ||
|
|
528
|
+
value === "32plus") {
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
return gpuCount > 0 ? "unknown" : "none";
|
|
532
|
+
}
|
|
533
|
+
function buildCoarseHardwarePressure(capabilityPayload) {
|
|
534
|
+
const cpuCount = Math.max(1, cpus().length || 1);
|
|
535
|
+
const totalMemory = totalmem();
|
|
536
|
+
const freeMemory = freemem();
|
|
537
|
+
const projection = capabilityPayload?.public_projection;
|
|
538
|
+
const projectionRecord = projection && typeof projection === "object" && !Array.isArray(projection)
|
|
539
|
+
? projection
|
|
540
|
+
: {};
|
|
541
|
+
const accelerators = projectionRecord.accelerators && typeof projectionRecord.accelerators === "object"
|
|
542
|
+
? projectionRecord.accelerators
|
|
543
|
+
: {};
|
|
544
|
+
const gpu = accelerators.gpu && typeof accelerators.gpu === "object" && !Array.isArray(accelerators.gpu)
|
|
545
|
+
? accelerators.gpu
|
|
546
|
+
: null;
|
|
547
|
+
const rawGpuCount = gpu?.["count"];
|
|
548
|
+
const gpuCount = typeof rawGpuCount === "number" && Number.isFinite(rawGpuCount)
|
|
549
|
+
? Math.max(0, Math.floor(rawGpuCount))
|
|
550
|
+
: 0;
|
|
551
|
+
const vramTier = coarsePublicVramTier(gpu?.["vram_tier"], gpuCount);
|
|
552
|
+
return {
|
|
553
|
+
schema_version: 1,
|
|
554
|
+
collected_at: new Date().toISOString(),
|
|
555
|
+
cpu: {
|
|
556
|
+
core_count: cpuCount,
|
|
557
|
+
load_1m_ratio: roundedTelemetryNumber((loadavg()[0] || 0) / cpuCount)
|
|
558
|
+
},
|
|
559
|
+
ram: {
|
|
560
|
+
used_ratio: totalMemory > 0 ? roundedTelemetryNumber((totalMemory - freeMemory) / totalMemory) : null,
|
|
561
|
+
total_bucket: totalHostMemoryBucket()
|
|
562
|
+
},
|
|
563
|
+
gpu: {
|
|
564
|
+
available: Boolean(gpu?.["available"]),
|
|
565
|
+
count: gpuCount,
|
|
566
|
+
cuda: Boolean(gpu?.["cuda"] || gpu?.["has_cuda"]),
|
|
567
|
+
vram: {
|
|
568
|
+
total_tier: vramTier,
|
|
569
|
+
used_ratio: null
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
}
|
|
463
574
|
function normalizeCapabilities(value) {
|
|
464
575
|
if (!Array.isArray(value)) {
|
|
465
576
|
return [];
|
|
@@ -481,12 +592,19 @@ function normalizeHealthStatus(value) {
|
|
|
481
592
|
return "blocked";
|
|
482
593
|
return "unknown";
|
|
483
594
|
}
|
|
484
|
-
function
|
|
595
|
+
function isMswarmManagedAgent(agent) {
|
|
485
596
|
const config = agent.config && typeof agent.config === "object" ? agent.config : {};
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
typeof
|
|
489
|
-
|
|
597
|
+
const managedConfigs = [config.mswarmCloud, config.mswarmSelfHosted, config.mswarmWorker];
|
|
598
|
+
if (managedConfigs.some((managedConfig) => Boolean(managedConfig) &&
|
|
599
|
+
typeof managedConfig === "object" &&
|
|
600
|
+
managedConfig.managed === true)) {
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
const slug = optionalText(agent.slug)?.toLowerCase();
|
|
604
|
+
return Boolean(slug &&
|
|
605
|
+
(slug.startsWith("mswarm-cloud-") ||
|
|
606
|
+
slug.startsWith("mswarm-self-hosted-") ||
|
|
607
|
+
slug.startsWith("mswarm-worker-")));
|
|
490
608
|
}
|
|
491
609
|
function isModelExposed(modelName, family, config) {
|
|
492
610
|
if (config.modelBlocklist.includes(modelName)) {
|
|
@@ -631,9 +749,14 @@ function serviceEnvironment(config, env, homeDir) {
|
|
|
631
749
|
MSWARM_SELF_HOSTED_HEARTBEAT_INTERVAL_SECONDS: String(config.heartbeatIntervalSeconds),
|
|
632
750
|
MSWARM_SELF_HOSTED_REQUEST_TIMEOUT_MS: String(config.requestTimeoutMs),
|
|
633
751
|
MSWARM_SELF_HOSTED_JOB_TIMEOUT_MS: String(config.jobTimeoutMs),
|
|
752
|
+
MSWARM_SELF_HOSTED_MAX_CONCURRENT_JOBS: String(config.maxConcurrentJobs || 1),
|
|
753
|
+
MSWARM_SELF_HOSTED_MAX_CONCURRENT_LLM_JOBS: String(config.maxConcurrentLlmJobs || config.maxConcurrentJobs || 1),
|
|
634
754
|
MSWARM_SELF_HOSTED_GENERIC_JOBS_ENABLED: config.genericJobsEnabled ? "true" : "false",
|
|
635
755
|
MSWARM_SELF_HOSTED_GENERIC_JOB_TIMEOUT_MS: String(config.genericJobTimeoutMs),
|
|
636
756
|
MSWARM_SELF_HOSTED_GENERIC_JOB_MAX_CONCURRENCY: String(config.genericJobMaxConcurrency),
|
|
757
|
+
MSWARM_SELF_HOSTED_DRAIN_MODE: config.drainMode ? "true" : "false",
|
|
758
|
+
MSWARM_SELF_HOSTED_LOAD_REPORTING_ENABLED: config.loadReportingEnabled === false ? "false" : "true",
|
|
759
|
+
MSWARM_SELF_HOSTED_HARDWARE_TELEMETRY_ENABLED: config.hardwareTelemetryEnabled ? "true" : "false",
|
|
637
760
|
MSWARM_SELF_HOSTED_CAPABILITY_PROBE_TIMEOUT_MS: config.capabilityProbeTimeoutMs
|
|
638
761
|
? String(config.capabilityProbeTimeoutMs)
|
|
639
762
|
: null
|
|
@@ -1041,6 +1164,8 @@ export async function readSelfHostedNodeConfig(env = process.env) {
|
|
|
1041
1164
|
optionalText(env.OLLAMA_HOST) ||
|
|
1042
1165
|
DEFAULT_OLLAMA_BASE_URL;
|
|
1043
1166
|
const packageNodeVersion = await readPackageNodeVersion();
|
|
1167
|
+
const maxConcurrentJobs = parsePositiveInteger(env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_JOBS, state.max_concurrent_jobs || 1);
|
|
1168
|
+
const maxConcurrentLlmJobs = parsePositiveInteger(env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_LLM_JOBS, state.max_concurrent_llm_jobs || maxConcurrentJobs);
|
|
1044
1169
|
return {
|
|
1045
1170
|
gatewayBaseUrl: trimTrailingSlash(gatewayBaseUrl),
|
|
1046
1171
|
nodeId,
|
|
@@ -1070,10 +1195,15 @@ export async function readSelfHostedNodeConfig(env = process.env) {
|
|
|
1070
1195
|
heartbeatIntervalSeconds: parsePositiveInteger(env.MSWARM_SELF_HOSTED_HEARTBEAT_INTERVAL_SECONDS, state.heartbeat_interval_seconds || DEFAULT_HEARTBEAT_INTERVAL_SECONDS),
|
|
1071
1196
|
requestTimeoutMs: parsePositiveInteger(env.MSWARM_SELF_HOSTED_REQUEST_TIMEOUT_MS, state.request_timeout_ms || DEFAULT_REQUEST_TIMEOUT_MS),
|
|
1072
1197
|
jobTimeoutMs: parsePositiveInteger(env.MSWARM_SELF_HOSTED_JOB_TIMEOUT_MS, state.job_timeout_ms || DEFAULT_JOB_TIMEOUT_MS),
|
|
1198
|
+
maxConcurrentJobs,
|
|
1199
|
+
maxConcurrentLlmJobs,
|
|
1073
1200
|
genericJobsEnabled: parseBoolean(env.MSWARM_SELF_HOSTED_GENERIC_JOBS_ENABLED ?? env.MSWARM_SELF_HOSTED_GENERIC_JOBS, state.generic_jobs_enabled === true),
|
|
1074
1201
|
genericJobTimeoutMs: parsePositiveInteger(env.MSWARM_SELF_HOSTED_GENERIC_JOB_TIMEOUT_MS, state.generic_job_timeout_ms || state.job_timeout_ms || DEFAULT_JOB_TIMEOUT_MS),
|
|
1075
1202
|
genericJobMaxConcurrency: parsePositiveInteger(env.MSWARM_SELF_HOSTED_GENERIC_JOB_MAX_CONCURRENCY, state.generic_job_max_concurrency || 1),
|
|
1076
1203
|
capabilityProbeTimeoutMs: parsePositiveInteger(env.MSWARM_SELF_HOSTED_CAPABILITY_PROBE_TIMEOUT_MS, state.capability_probe_timeout_ms || DEFAULT_CAPABILITY_PROBE_TIMEOUT_MS),
|
|
1204
|
+
drainMode: parseBoolean(env.MSWARM_SELF_HOSTED_DRAIN_MODE, state.drain_mode === true),
|
|
1205
|
+
loadReportingEnabled: parseBoolean(env.MSWARM_SELF_HOSTED_LOAD_REPORTING_ENABLED ?? env.MSWARM_SELF_HOSTED_LOAD_REPORTING, state.load_reporting_enabled !== false),
|
|
1206
|
+
hardwareTelemetryEnabled: parseBoolean(env.MSWARM_SELF_HOSTED_HARDWARE_TELEMETRY_ENABLED ?? env.MSWARM_SELF_HOSTED_HARDWARE_TELEMETRY, state.hardware_telemetry_enabled === true),
|
|
1077
1207
|
exposeAllModels: resolveDaemonExposeAllModels(env, state),
|
|
1078
1208
|
modelAllowlist: parseList(env.MSWARM_SELF_HOSTED_MODEL_ALLOWLIST || state.model_allowlist),
|
|
1079
1209
|
modelBlocklist: parseList(env.MSWARM_SELF_HOSTED_MODEL_BLOCKLIST || state.model_blocklist)
|
|
@@ -1104,6 +1234,8 @@ export async function readOwnerSetupConfig(argv = process.argv.slice(3), env = p
|
|
|
1104
1234
|
const allowlist = parseList(options.allow || env.MSWARM_SELF_HOSTED_MODEL_ALLOWLIST);
|
|
1105
1235
|
const blocklist = parseList(options.block || env.MSWARM_SELF_HOSTED_MODEL_BLOCKLIST);
|
|
1106
1236
|
const packageNodeVersion = await readPackageNodeVersion();
|
|
1237
|
+
const maxConcurrentJobs = parsePositiveInteger(options["max-concurrent-jobs"] || env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_JOBS, 1);
|
|
1238
|
+
const maxConcurrentLlmJobs = parsePositiveInteger(options["max-concurrent-llm-jobs"] || env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_LLM_JOBS, maxConcurrentJobs);
|
|
1107
1239
|
return {
|
|
1108
1240
|
apiKey,
|
|
1109
1241
|
gatewayBaseUrl: trimTrailingSlash(gatewayBaseUrl),
|
|
@@ -1126,10 +1258,17 @@ export async function readOwnerSetupConfig(argv = process.argv.slice(3), env = p
|
|
|
1126
1258
|
heartbeatIntervalSeconds: parsePositiveInteger(env.MSWARM_SELF_HOSTED_HEARTBEAT_INTERVAL_SECONDS, DEFAULT_HEARTBEAT_INTERVAL_SECONDS),
|
|
1127
1259
|
requestTimeoutMs: parsePositiveInteger(env.MSWARM_SELF_HOSTED_REQUEST_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS),
|
|
1128
1260
|
jobTimeoutMs: parsePositiveInteger(options["job-timeout-ms"] || env.MSWARM_SELF_HOSTED_JOB_TIMEOUT_MS, DEFAULT_JOB_TIMEOUT_MS),
|
|
1261
|
+
maxConcurrentJobs,
|
|
1262
|
+
maxConcurrentLlmJobs,
|
|
1129
1263
|
genericJobsEnabled: parseBoolean(options["enable-generic-jobs"] || env.MSWARM_SELF_HOSTED_GENERIC_JOBS_ENABLED || env.MSWARM_SELF_HOSTED_GENERIC_JOBS, false),
|
|
1130
1264
|
genericJobTimeoutMs: parsePositiveInteger(options["generic-job-timeout-ms"] || env.MSWARM_SELF_HOSTED_GENERIC_JOB_TIMEOUT_MS, DEFAULT_JOB_TIMEOUT_MS),
|
|
1131
1265
|
genericJobMaxConcurrency: parsePositiveInteger(options["generic-job-max-concurrency"] || env.MSWARM_SELF_HOSTED_GENERIC_JOB_MAX_CONCURRENCY, 1),
|
|
1132
1266
|
capabilityProbeTimeoutMs: parsePositiveInteger(env.MSWARM_SELF_HOSTED_CAPABILITY_PROBE_TIMEOUT_MS, DEFAULT_CAPABILITY_PROBE_TIMEOUT_MS),
|
|
1267
|
+
drainMode: parseBoolean(options.drain || env.MSWARM_SELF_HOSTED_DRAIN_MODE, false),
|
|
1268
|
+
loadReportingEnabled: parseBoolean(options["disable-load-reporting"] === true
|
|
1269
|
+
? false
|
|
1270
|
+
: (env.MSWARM_SELF_HOSTED_LOAD_REPORTING_ENABLED ?? env.MSWARM_SELF_HOSTED_LOAD_REPORTING), true),
|
|
1271
|
+
hardwareTelemetryEnabled: parseBoolean(options["enable-hardware-telemetry"] || env.MSWARM_SELF_HOSTED_HARDWARE_TELEMETRY_ENABLED || env.MSWARM_SELF_HOSTED_HARDWARE_TELEMETRY, false),
|
|
1133
1272
|
exposeAllModels: resolveOwnerSetupExposeAllModels(options, env),
|
|
1134
1273
|
modelAllowlist: allowlist,
|
|
1135
1274
|
modelBlocklist: blocklist,
|
|
@@ -1171,7 +1310,7 @@ export function mapOllamaModelToSelfHostedModel(model, config) {
|
|
|
1171
1310
|
};
|
|
1172
1311
|
}
|
|
1173
1312
|
export function mapMcodaAgentToSelfHostedModel(agent, config) {
|
|
1174
|
-
if (
|
|
1313
|
+
if (isMswarmManagedAgent(agent)) {
|
|
1175
1314
|
return null;
|
|
1176
1315
|
}
|
|
1177
1316
|
const slug = optionalText(agent.slug);
|
|
@@ -1527,10 +1666,6 @@ function mapMcodaAgentToCodaliAgent(agent, fallbackSlug) {
|
|
|
1527
1666
|
maxOutputTokens: optionalNumber(agent.maxOutputTokens, agent.max_output_tokens) ?? undefined,
|
|
1528
1667
|
};
|
|
1529
1668
|
}
|
|
1530
|
-
function isExposedLocalAgent(agent, config) {
|
|
1531
|
-
const mapped = mapMcodaAgentToSelfHostedModel(agent, config);
|
|
1532
|
-
return Boolean(mapped?.exposed);
|
|
1533
|
-
}
|
|
1534
1669
|
function buildCodaliWorkspace(job) {
|
|
1535
1670
|
const root = optionalText(job.workspace?.root);
|
|
1536
1671
|
if (!root) {
|
|
@@ -1550,6 +1685,13 @@ const DOCDEX_JOB_ERROR_CODES = new Set([
|
|
|
1550
1685
|
"docdex_repo_access_denied",
|
|
1551
1686
|
"docdex_unavailable",
|
|
1552
1687
|
]);
|
|
1688
|
+
const PRE_START_JOB_ERROR_CODES = new Set([
|
|
1689
|
+
"selected_agent_unavailable",
|
|
1690
|
+
"selected_agent_unhealthy",
|
|
1691
|
+
"validation_failed",
|
|
1692
|
+
"docdex_context_missing",
|
|
1693
|
+
"docdex_api_key_missing",
|
|
1694
|
+
]);
|
|
1553
1695
|
class SelfHostedDocdexJobError extends Error {
|
|
1554
1696
|
constructor(code, message) {
|
|
1555
1697
|
super(message);
|
|
@@ -1557,6 +1699,13 @@ class SelfHostedDocdexJobError extends Error {
|
|
|
1557
1699
|
this.code = code;
|
|
1558
1700
|
}
|
|
1559
1701
|
}
|
|
1702
|
+
class SelfHostedPreStartJobError extends Error {
|
|
1703
|
+
constructor(code, message) {
|
|
1704
|
+
super(message);
|
|
1705
|
+
this.name = code;
|
|
1706
|
+
this.code = code;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1560
1709
|
function normalizeDocdexCapabilityMap(value) {
|
|
1561
1710
|
const record = objectRecord(value);
|
|
1562
1711
|
if (!record)
|
|
@@ -1614,11 +1763,14 @@ function selfHostedErrorCode(error) {
|
|
|
1614
1763
|
if (!error || typeof error !== "object")
|
|
1615
1764
|
return undefined;
|
|
1616
1765
|
const code = error.code;
|
|
1617
|
-
if (typeof code === "string" &&
|
|
1766
|
+
if (typeof code === "string" &&
|
|
1767
|
+
(DOCDEX_JOB_ERROR_CODES.has(code) || PRE_START_JOB_ERROR_CODES.has(code))) {
|
|
1618
1768
|
return code;
|
|
1619
1769
|
}
|
|
1620
1770
|
const name = error.name;
|
|
1621
|
-
return typeof name === "string" && DOCDEX_JOB_ERROR_CODES.has(name)
|
|
1771
|
+
return typeof name === "string" && (DOCDEX_JOB_ERROR_CODES.has(name) || PRE_START_JOB_ERROR_CODES.has(name))
|
|
1772
|
+
? name
|
|
1773
|
+
: undefined;
|
|
1622
1774
|
}
|
|
1623
1775
|
function redactRuntimeSecretValues(value, secrets) {
|
|
1624
1776
|
let output = value;
|
|
@@ -3135,6 +3287,16 @@ export class MswarmSelfHostedNodeClient {
|
|
|
3135
3287
|
body: JSON.stringify(payload)
|
|
3136
3288
|
}, this.timeoutMs);
|
|
3137
3289
|
}
|
|
3290
|
+
async postJobStart(runtimeToken, jobId, payload) {
|
|
3291
|
+
return fetchJson(this.fetchImpl, `${this.gatewayBaseUrl}/v1/swarm/self-hosted/node/jobs/${encodeURIComponent(jobId)}/start`, {
|
|
3292
|
+
method: "POST",
|
|
3293
|
+
headers: {
|
|
3294
|
+
"content-type": "application/json",
|
|
3295
|
+
authorization: `Bearer ${runtimeToken}`
|
|
3296
|
+
},
|
|
3297
|
+
body: JSON.stringify(payload)
|
|
3298
|
+
}, this.timeoutMs);
|
|
3299
|
+
}
|
|
3138
3300
|
async postJobEvents(runtimeToken, jobId, payload) {
|
|
3139
3301
|
return fetchJson(this.fetchImpl, `${this.gatewayBaseUrl}/v1/swarm/self-hosted/node/jobs/${encodeURIComponent(jobId)}/events`, {
|
|
3140
3302
|
method: "POST",
|
|
@@ -3148,6 +3310,12 @@ export class MswarmSelfHostedNodeClient {
|
|
|
3148
3310
|
}
|
|
3149
3311
|
export class SelfHostedNodeRuntime {
|
|
3150
3312
|
constructor(config, deps) {
|
|
3313
|
+
this.activeLlmJobs = 0;
|
|
3314
|
+
this.activeGenericJobs = 0;
|
|
3315
|
+
this.queuedLlmJobs = 0;
|
|
3316
|
+
this.queuedGenericJobs = 0;
|
|
3317
|
+
this.latencySamplesMs = [];
|
|
3318
|
+
this.recentFailures = [];
|
|
3151
3319
|
this.config = config;
|
|
3152
3320
|
this.gateway =
|
|
3153
3321
|
deps?.gateway ||
|
|
@@ -3192,6 +3360,100 @@ export class SelfHostedNodeRuntime {
|
|
|
3192
3360
|
rootDir: config.artifactStorePath || defaultArtifactStorePath()
|
|
3193
3361
|
});
|
|
3194
3362
|
}
|
|
3363
|
+
updateLocalQueueTelemetry(input) {
|
|
3364
|
+
if (input.llmQueuedJobs !== undefined) {
|
|
3365
|
+
this.queuedLlmJobs = nonNegativeTelemetryInteger(input.llmQueuedJobs);
|
|
3366
|
+
}
|
|
3367
|
+
if (input.genericQueuedJobs !== undefined) {
|
|
3368
|
+
this.queuedGenericJobs = nonNegativeTelemetryInteger(input.genericQueuedJobs);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
beginExecutionTelemetry(executionClass) {
|
|
3372
|
+
if (executionClass === "generic_job") {
|
|
3373
|
+
this.activeGenericJobs += 1;
|
|
3374
|
+
return;
|
|
3375
|
+
}
|
|
3376
|
+
this.activeLlmJobs += 1;
|
|
3377
|
+
}
|
|
3378
|
+
finishExecutionTelemetry(input) {
|
|
3379
|
+
if (input.executionClass === "generic_job") {
|
|
3380
|
+
this.activeGenericJobs = Math.max(0, this.activeGenericJobs - 1);
|
|
3381
|
+
}
|
|
3382
|
+
else {
|
|
3383
|
+
this.activeLlmJobs = Math.max(0, this.activeLlmJobs - 1);
|
|
3384
|
+
}
|
|
3385
|
+
this.latencySamplesMs.push(Math.max(0, Date.now() - input.startedAt));
|
|
3386
|
+
while (this.latencySamplesMs.length > MAX_TELEMETRY_LATENCY_SAMPLES) {
|
|
3387
|
+
this.latencySamplesMs.shift();
|
|
3388
|
+
}
|
|
3389
|
+
if (!input.ok) {
|
|
3390
|
+
this.recentFailures.unshift({
|
|
3391
|
+
execution_class: input.executionClass === "generic_job" ? "generic_job" : "agentic",
|
|
3392
|
+
code: optionalText(input.code) || "upstream_error",
|
|
3393
|
+
at: new Date().toISOString()
|
|
3394
|
+
});
|
|
3395
|
+
this.recentFailures.splice(MAX_TELEMETRY_FAILURES);
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
averageLatencyMs(fallback = null) {
|
|
3399
|
+
if (this.latencySamplesMs.length === 0) {
|
|
3400
|
+
return fallback;
|
|
3401
|
+
}
|
|
3402
|
+
const total = this.latencySamplesMs.reduce((sum, value) => sum + value, 0);
|
|
3403
|
+
return Math.round(total / this.latencySamplesMs.length);
|
|
3404
|
+
}
|
|
3405
|
+
buildLoadTelemetry(input) {
|
|
3406
|
+
const drainMode = this.config.drainMode === true;
|
|
3407
|
+
const llmMaxConcurrency = Math.max(1, Math.floor(this.config.maxConcurrentLlmJobs || this.config.maxConcurrentJobs || 1));
|
|
3408
|
+
const genericMaxConcurrency = Math.max(1, Math.floor(this.config.genericJobMaxConcurrency || 1));
|
|
3409
|
+
const maxConcurrency = Math.max(1, Math.floor(this.config.maxConcurrentJobs || 1), llmMaxConcurrency, this.config.genericJobsEnabled ? genericMaxConcurrency : 1);
|
|
3410
|
+
const activeLlmJobs = nonNegativeTelemetryInteger(this.activeLlmJobs);
|
|
3411
|
+
const activeGenericJobs = nonNegativeTelemetryInteger(this.activeGenericJobs);
|
|
3412
|
+
const queuedLlmJobs = nonNegativeTelemetryInteger(this.queuedLlmJobs);
|
|
3413
|
+
const queuedGenericJobs = nonNegativeTelemetryInteger(this.queuedGenericJobs);
|
|
3414
|
+
const llmCapacity = executionClassCapacity({
|
|
3415
|
+
maxConcurrency: llmMaxConcurrency,
|
|
3416
|
+
activeJobs: activeLlmJobs,
|
|
3417
|
+
queuedJobs: queuedLlmJobs,
|
|
3418
|
+
drainMode
|
|
3419
|
+
});
|
|
3420
|
+
const genericCapacity = executionClassCapacity({
|
|
3421
|
+
maxConcurrency: genericMaxConcurrency,
|
|
3422
|
+
activeJobs: activeGenericJobs,
|
|
3423
|
+
queuedJobs: queuedGenericJobs,
|
|
3424
|
+
drainMode: drainMode || !this.config.genericJobsEnabled
|
|
3425
|
+
});
|
|
3426
|
+
const activeJobs = activeLlmJobs + activeGenericJobs;
|
|
3427
|
+
const queuedJobs = queuedLlmJobs + queuedGenericJobs;
|
|
3428
|
+
const freeSlots = drainMode ? 0 : Math.max(0, maxConcurrency - activeJobs - queuedJobs);
|
|
3429
|
+
const failures = this.recentFailures.slice(0, 10);
|
|
3430
|
+
const discoveryFailureCount = nonNegativeTelemetryInteger(input.discoveryFailureCount);
|
|
3431
|
+
const telemetry = {
|
|
3432
|
+
runtime_protocol_version: SELF_HOSTED_RUNTIME_PROTOCOL_VERSION,
|
|
3433
|
+
load_balancer_protocol_version: SELF_HOSTED_LOAD_BALANCER_PROTOCOL_VERSION,
|
|
3434
|
+
catalog_metadata_version: SELF_HOSTED_CATALOG_METADATA_VERSION,
|
|
3435
|
+
catalog_fingerprint: buildCatalogFingerprint(input.models),
|
|
3436
|
+
max_concurrency: maxConcurrency,
|
|
3437
|
+
max_concurrent_llm_jobs: llmMaxConcurrency,
|
|
3438
|
+
max_concurrent_generic_jobs: this.config.genericJobsEnabled ? genericMaxConcurrency : 0,
|
|
3439
|
+
active_jobs: activeJobs,
|
|
3440
|
+
queued_jobs: queuedJobs,
|
|
3441
|
+
free_slots: freeSlots,
|
|
3442
|
+
drain_mode: drainMode,
|
|
3443
|
+
execution_class_capacity: {
|
|
3444
|
+
chat: llmCapacity,
|
|
3445
|
+
agentic: llmCapacity,
|
|
3446
|
+
generic_job: genericCapacity
|
|
3447
|
+
},
|
|
3448
|
+
avg_latency_ms: this.averageLatencyMs(input.discoveryLatencyMs ?? null),
|
|
3449
|
+
recent_failure_count: failures.length + discoveryFailureCount,
|
|
3450
|
+
recent_failures: failures
|
|
3451
|
+
};
|
|
3452
|
+
if (this.config.hardwareTelemetryEnabled === true) {
|
|
3453
|
+
telemetry.hardware_pressure = buildCoarseHardwarePressure(input.capabilityPayload || null);
|
|
3454
|
+
}
|
|
3455
|
+
return telemetry;
|
|
3456
|
+
}
|
|
3195
3457
|
static async setup(setupConfig, deps) {
|
|
3196
3458
|
const gateway = deps?.gateway ||
|
|
3197
3459
|
new MswarmSelfHostedNodeClient({
|
|
@@ -3213,6 +3475,11 @@ export class SelfHostedNodeRuntime {
|
|
|
3213
3475
|
model_allowlist: setupConfig.modelAllowlist,
|
|
3214
3476
|
model_blocklist: setupConfig.modelBlocklist,
|
|
3215
3477
|
heartbeat_interval_seconds: setupConfig.heartbeatIntervalSeconds,
|
|
3478
|
+
max_concurrent_jobs: setupConfig.maxConcurrentJobs,
|
|
3479
|
+
max_concurrent_llm_jobs: setupConfig.maxConcurrentLlmJobs,
|
|
3480
|
+
drain_mode: setupConfig.drainMode,
|
|
3481
|
+
load_reporting_enabled: setupConfig.loadReportingEnabled,
|
|
3482
|
+
hardware_telemetry_enabled: setupConfig.hardwareTelemetryEnabled,
|
|
3216
3483
|
generic_job_max_concurrency: setupConfig.genericJobMaxConcurrency
|
|
3217
3484
|
});
|
|
3218
3485
|
const nodeId = optionalText(bootstrap.node?.node_id);
|
|
@@ -3242,10 +3509,15 @@ export class SelfHostedNodeRuntime {
|
|
|
3242
3509
|
node_version: setupConfig.nodeVersion,
|
|
3243
3510
|
request_timeout_ms: setupConfig.requestTimeoutMs,
|
|
3244
3511
|
job_timeout_ms: setupConfig.jobTimeoutMs,
|
|
3512
|
+
max_concurrent_jobs: setupConfig.maxConcurrentJobs,
|
|
3513
|
+
max_concurrent_llm_jobs: setupConfig.maxConcurrentLlmJobs,
|
|
3245
3514
|
generic_jobs_enabled: setupConfig.genericJobsEnabled,
|
|
3246
3515
|
generic_job_timeout_ms: setupConfig.genericJobTimeoutMs,
|
|
3247
3516
|
generic_job_max_concurrency: setupConfig.genericJobMaxConcurrency,
|
|
3248
3517
|
capability_probe_timeout_ms: setupConfig.capabilityProbeTimeoutMs || DEFAULT_CAPABILITY_PROBE_TIMEOUT_MS,
|
|
3518
|
+
drain_mode: setupConfig.drainMode,
|
|
3519
|
+
load_reporting_enabled: setupConfig.loadReportingEnabled,
|
|
3520
|
+
hardware_telemetry_enabled: setupConfig.hardwareTelemetryEnabled,
|
|
3249
3521
|
expose_all_models: setupConfig.exposeAllModels,
|
|
3250
3522
|
exposure_policy: setupConfig.exposeAllModels ? "all" : "none",
|
|
3251
3523
|
model_allowlist: setupConfig.modelAllowlist,
|
|
@@ -3276,10 +3548,15 @@ export class SelfHostedNodeRuntime {
|
|
|
3276
3548
|
heartbeatIntervalSeconds: heartbeatInterval,
|
|
3277
3549
|
requestTimeoutMs: setupConfig.requestTimeoutMs,
|
|
3278
3550
|
jobTimeoutMs: setupConfig.jobTimeoutMs,
|
|
3551
|
+
maxConcurrentJobs: setupConfig.maxConcurrentJobs,
|
|
3552
|
+
maxConcurrentLlmJobs: setupConfig.maxConcurrentLlmJobs,
|
|
3279
3553
|
genericJobsEnabled: setupConfig.genericJobsEnabled,
|
|
3280
3554
|
genericJobTimeoutMs: setupConfig.genericJobTimeoutMs,
|
|
3281
3555
|
genericJobMaxConcurrency: setupConfig.genericJobMaxConcurrency,
|
|
3282
3556
|
capabilityProbeTimeoutMs: setupConfig.capabilityProbeTimeoutMs || DEFAULT_CAPABILITY_PROBE_TIMEOUT_MS,
|
|
3557
|
+
drainMode: setupConfig.drainMode,
|
|
3558
|
+
loadReportingEnabled: setupConfig.loadReportingEnabled,
|
|
3559
|
+
hardwareTelemetryEnabled: setupConfig.hardwareTelemetryEnabled,
|
|
3283
3560
|
exposeAllModels: setupConfig.exposeAllModels,
|
|
3284
3561
|
modelAllowlist: setupConfig.modelAllowlist,
|
|
3285
3562
|
modelBlocklist: setupConfig.modelBlocklist
|
|
@@ -3386,9 +3663,14 @@ export class SelfHostedNodeRuntime {
|
|
|
3386
3663
|
node_version: this.config.nodeVersion,
|
|
3387
3664
|
request_timeout_ms: this.config.requestTimeoutMs,
|
|
3388
3665
|
job_timeout_ms: this.config.jobTimeoutMs,
|
|
3666
|
+
max_concurrent_jobs: this.config.maxConcurrentJobs,
|
|
3667
|
+
max_concurrent_llm_jobs: this.config.maxConcurrentLlmJobs,
|
|
3389
3668
|
generic_jobs_enabled: this.config.genericJobsEnabled,
|
|
3390
3669
|
generic_job_timeout_ms: this.config.genericJobTimeoutMs,
|
|
3391
3670
|
generic_job_max_concurrency: this.config.genericJobMaxConcurrency,
|
|
3671
|
+
drain_mode: this.config.drainMode === true,
|
|
3672
|
+
load_reporting_enabled: this.config.loadReportingEnabled !== false,
|
|
3673
|
+
hardware_telemetry_enabled: this.config.hardwareTelemetryEnabled === true,
|
|
3392
3674
|
expose_all_models: this.config.exposeAllModels,
|
|
3393
3675
|
exposure_policy: this.config.exposeAllModels ? "all" : "none",
|
|
3394
3676
|
model_allowlist: this.config.modelAllowlist,
|
|
@@ -3399,26 +3681,38 @@ export class SelfHostedNodeRuntime {
|
|
|
3399
3681
|
return { runtimeToken, state: nextState, enrolled: true };
|
|
3400
3682
|
}
|
|
3401
3683
|
async resolveMcodaAgentForJob(job) {
|
|
3402
|
-
const
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3684
|
+
const selectedSourceAgentSlug = optionalText(job.source_agent_slug);
|
|
3685
|
+
const selectedAgentSlug = optionalText(job.agent_slug);
|
|
3686
|
+
const selectedModel = optionalText(job.model) || optionalText(job.openai_request.model);
|
|
3687
|
+
const selected = selectedSourceAgentSlug || selectedAgentSlug || selectedModel;
|
|
3406
3688
|
if (!selected) {
|
|
3407
|
-
throw new
|
|
3689
|
+
throw new SelfHostedPreStartJobError("selected_agent_unavailable", "mcoda source agent slug is required");
|
|
3408
3690
|
}
|
|
3409
3691
|
const rawAgents = await this.mcoda.listRawAgents();
|
|
3692
|
+
const strictSelectedAgent = selectedSourceAgentSlug || selectedAgentSlug;
|
|
3410
3693
|
const agent = rawAgents.find((entry) => {
|
|
3411
3694
|
const slug = optionalText(entry.slug);
|
|
3695
|
+
if (strictSelectedAgent) {
|
|
3696
|
+
return slug === strictSelectedAgent;
|
|
3697
|
+
}
|
|
3412
3698
|
const defaultModel = mcodaAgentDefaultModel(entry);
|
|
3413
3699
|
return slug === selected || defaultModel === selected;
|
|
3414
3700
|
});
|
|
3415
|
-
if (!agent
|
|
3416
|
-
throw new
|
|
3701
|
+
if (!agent) {
|
|
3702
|
+
throw new SelfHostedPreStartJobError("selected_agent_unavailable", `selected local mcoda agent ${selected} is not available on this node`);
|
|
3703
|
+
}
|
|
3704
|
+
const mapped = mapMcodaAgentToSelfHostedModel(agent, this.config);
|
|
3705
|
+
if (!mapped?.exposed) {
|
|
3706
|
+
throw new SelfHostedPreStartJobError("selected_agent_unavailable", `selected local mcoda agent ${selected} is not exposed by this node`);
|
|
3707
|
+
}
|
|
3708
|
+
if (mapped.health_status && mapped.health_status !== "healthy" && mapped.health_status !== "unknown") {
|
|
3709
|
+
throw new SelfHostedPreStartJobError("selected_agent_unhealthy", `selected local mcoda agent ${selected} is ${mapped.health_status}`);
|
|
3417
3710
|
}
|
|
3418
3711
|
return mapMcodaAgentToCodaliAgent(agent, selected);
|
|
3419
3712
|
}
|
|
3420
3713
|
async executeGenericJob(envelope, options = {}) {
|
|
3421
3714
|
const startedAt = Date.now();
|
|
3715
|
+
this.beginExecutionTelemetry("generic_job");
|
|
3422
3716
|
const events = [];
|
|
3423
3717
|
let sequence = 0;
|
|
3424
3718
|
const emitEvent = async (event) => {
|
|
@@ -3449,6 +3743,12 @@ export class SelfHostedNodeRuntime {
|
|
|
3449
3743
|
},
|
|
3450
3744
|
finished_at: new Date().toISOString()
|
|
3451
3745
|
};
|
|
3746
|
+
this.finishExecutionTelemetry({
|
|
3747
|
+
executionClass: "generic_job",
|
|
3748
|
+
startedAt,
|
|
3749
|
+
ok: false,
|
|
3750
|
+
code
|
|
3751
|
+
});
|
|
3452
3752
|
return {
|
|
3453
3753
|
job_id: envelope.job_id,
|
|
3454
3754
|
request_id: envelope.request_id,
|
|
@@ -3554,6 +3854,12 @@ export class SelfHostedNodeRuntime {
|
|
|
3554
3854
|
runner: runner.id
|
|
3555
3855
|
}
|
|
3556
3856
|
});
|
|
3857
|
+
this.finishExecutionTelemetry({
|
|
3858
|
+
executionClass: "generic_job",
|
|
3859
|
+
startedAt,
|
|
3860
|
+
ok: status === "succeeded",
|
|
3861
|
+
code: runnerResult.error?.code || status
|
|
3862
|
+
});
|
|
3557
3863
|
return {
|
|
3558
3864
|
job_id: envelope.job_id,
|
|
3559
3865
|
request_id: envelope.request_id,
|
|
@@ -3579,7 +3885,9 @@ export class SelfHostedNodeRuntime {
|
|
|
3579
3885
|
}
|
|
3580
3886
|
async executeJob(job, options = {}) {
|
|
3581
3887
|
const startedAt = Date.now();
|
|
3888
|
+
this.beginExecutionTelemetry("llm");
|
|
3582
3889
|
let selectedAgent;
|
|
3890
|
+
let jobStarted = false;
|
|
3583
3891
|
const progressEvents = [];
|
|
3584
3892
|
const streamEvents = [];
|
|
3585
3893
|
const recordProgress = async (event) => {
|
|
@@ -3590,13 +3898,35 @@ export class SelfHostedNodeRuntime {
|
|
|
3590
3898
|
streamEvents.push(chunk);
|
|
3591
3899
|
await options.onOpenAIChunk?.(chunk);
|
|
3592
3900
|
};
|
|
3901
|
+
const acknowledgeStarted = async (agent) => {
|
|
3902
|
+
if (jobStarted) {
|
|
3903
|
+
return;
|
|
3904
|
+
}
|
|
3905
|
+
await options.onStarted?.({
|
|
3906
|
+
job_id: job.job_id,
|
|
3907
|
+
request_id: job.request_id,
|
|
3908
|
+
node_id: job.node_id,
|
|
3909
|
+
agent_slug: optionalText(job.agent_slug) || agent?.slug || "",
|
|
3910
|
+
source_agent_slug: optionalText(job.source_agent_slug) || agent?.slug || null,
|
|
3911
|
+
model: optionalText(job.model) || optionalText(job.openai_request.model)
|
|
3912
|
+
});
|
|
3913
|
+
jobStarted = true;
|
|
3914
|
+
};
|
|
3593
3915
|
if (job.node_id !== this.config.nodeId) {
|
|
3594
|
-
|
|
3916
|
+
const result = {
|
|
3595
3917
|
job_id: job.job_id,
|
|
3596
3918
|
request_id: job.request_id,
|
|
3597
3919
|
status: "failed",
|
|
3920
|
+
pre_start_failure: true,
|
|
3598
3921
|
error: { code: "validation_failed", message: "job node_id does not match this node" }
|
|
3599
3922
|
};
|
|
3923
|
+
this.finishExecutionTelemetry({
|
|
3924
|
+
executionClass: "llm",
|
|
3925
|
+
startedAt,
|
|
3926
|
+
ok: false,
|
|
3927
|
+
code: "validation_failed"
|
|
3928
|
+
});
|
|
3929
|
+
return result;
|
|
3600
3930
|
}
|
|
3601
3931
|
try {
|
|
3602
3932
|
if (job.provider === "ollama") {
|
|
@@ -3609,7 +3939,8 @@ export class SelfHostedNodeRuntime {
|
|
|
3609
3939
|
options.num_predict = job.openai_request.max_tokens;
|
|
3610
3940
|
if (job.openai_request.stop !== undefined)
|
|
3611
3941
|
options.stop = job.openai_request.stop;
|
|
3612
|
-
|
|
3942
|
+
await acknowledgeStarted();
|
|
3943
|
+
const ollamaResult = await this.jobOllama.chat({
|
|
3613
3944
|
model: job.model || job.openai_request.model,
|
|
3614
3945
|
messages: job.openai_request.messages,
|
|
3615
3946
|
options,
|
|
@@ -3622,7 +3953,7 @@ export class SelfHostedNodeRuntime {
|
|
|
3622
3953
|
created: Math.floor(Date.now() / 1000),
|
|
3623
3954
|
model: job.openai_request.model,
|
|
3624
3955
|
choices: [
|
|
3625
|
-
{ index: 0, delta: { content:
|
|
3956
|
+
{ index: 0, delta: { content: ollamaResult.content }, finish_reason: null }
|
|
3626
3957
|
]
|
|
3627
3958
|
});
|
|
3628
3959
|
await emitOpenAIChunk({
|
|
@@ -3635,22 +3966,28 @@ export class SelfHostedNodeRuntime {
|
|
|
3635
3966
|
]
|
|
3636
3967
|
});
|
|
3637
3968
|
}
|
|
3638
|
-
|
|
3969
|
+
const invocationResult = {
|
|
3639
3970
|
job_id: job.job_id,
|
|
3640
3971
|
request_id: job.request_id,
|
|
3641
3972
|
status: "success",
|
|
3642
3973
|
openai_response: buildOpenAIChatCompletion({
|
|
3643
3974
|
requestId: job.request_id,
|
|
3644
3975
|
model: job.openai_request.model,
|
|
3645
|
-
content:
|
|
3646
|
-
promptTokens:
|
|
3647
|
-
completionTokens:
|
|
3648
|
-
metadata: { provider: "ollama", raw:
|
|
3976
|
+
content: ollamaResult.content,
|
|
3977
|
+
promptTokens: ollamaResult.promptTokens,
|
|
3978
|
+
completionTokens: ollamaResult.completionTokens,
|
|
3979
|
+
metadata: { provider: "ollama", raw: ollamaResult.raw }
|
|
3649
3980
|
}),
|
|
3650
3981
|
...(streamEvents.length ? { stream_events: streamEvents } : {}),
|
|
3651
3982
|
...(progressEvents.length ? { progress_events: progressEvents } : {}),
|
|
3652
3983
|
timing: { local_latency_ms: Date.now() - startedAt }
|
|
3653
3984
|
};
|
|
3985
|
+
this.finishExecutionTelemetry({
|
|
3986
|
+
executionClass: "llm",
|
|
3987
|
+
startedAt,
|
|
3988
|
+
ok: true
|
|
3989
|
+
});
|
|
3990
|
+
return invocationResult;
|
|
3654
3991
|
}
|
|
3655
3992
|
const taskPreview = messagesToPrompt(job.openai_request.messages);
|
|
3656
3993
|
if (!taskPreview) {
|
|
@@ -3660,6 +3997,7 @@ export class SelfHostedNodeRuntime {
|
|
|
3660
3997
|
selectedAgent = agent;
|
|
3661
3998
|
validateRequiredDocdexContext(job, options.attachedMswarmApiKey);
|
|
3662
3999
|
const attachedMswarmApiKey = attachedMswarmApiKeyForDocdex(job, options.attachedMswarmApiKey);
|
|
4000
|
+
await acknowledgeStarted(agent);
|
|
3663
4001
|
await recordProgress({
|
|
3664
4002
|
type: "agent_selected",
|
|
3665
4003
|
job_id: job.job_id,
|
|
@@ -3698,7 +4036,7 @@ export class SelfHostedNodeRuntime {
|
|
|
3698
4036
|
}
|
|
3699
4037
|
});
|
|
3700
4038
|
const tokens = usageTokens(response.usage);
|
|
3701
|
-
|
|
4039
|
+
const result = {
|
|
3702
4040
|
job_id: job.job_id,
|
|
3703
4041
|
request_id: job.request_id,
|
|
3704
4042
|
status: "success",
|
|
@@ -3724,6 +4062,12 @@ export class SelfHostedNodeRuntime {
|
|
|
3724
4062
|
...(progressEvents.length ? { progress_events: progressEvents } : {}),
|
|
3725
4063
|
timing: { local_latency_ms: Date.now() - startedAt }
|
|
3726
4064
|
};
|
|
4065
|
+
this.finishExecutionTelemetry({
|
|
4066
|
+
executionClass: "llm",
|
|
4067
|
+
startedAt,
|
|
4068
|
+
ok: true
|
|
4069
|
+
});
|
|
4070
|
+
return result;
|
|
3727
4071
|
}
|
|
3728
4072
|
catch (error) {
|
|
3729
4073
|
const message = redactRuntimeSecretValues(error instanceof Error ? error.message : String(error), [selectedAgent?.apiKey, options.attachedMswarmApiKey]);
|
|
@@ -3736,10 +4080,11 @@ export class SelfHostedNodeRuntime {
|
|
|
3736
4080
|
: /permission|policy|denied/i.test(message)
|
|
3737
4081
|
? "policy_denied"
|
|
3738
4082
|
: "upstream_error");
|
|
3739
|
-
|
|
4083
|
+
const result = {
|
|
3740
4084
|
job_id: job.job_id,
|
|
3741
4085
|
request_id: job.request_id,
|
|
3742
4086
|
status: "failed",
|
|
4087
|
+
...(!jobStarted ? { pre_start_failure: true } : {}),
|
|
3743
4088
|
error: {
|
|
3744
4089
|
code,
|
|
3745
4090
|
message
|
|
@@ -3748,6 +4093,13 @@ export class SelfHostedNodeRuntime {
|
|
|
3748
4093
|
...(progressEvents.length ? { progress_events: progressEvents } : {}),
|
|
3749
4094
|
timing: { local_latency_ms: Date.now() - startedAt }
|
|
3750
4095
|
};
|
|
4096
|
+
this.finishExecutionTelemetry({
|
|
4097
|
+
executionClass: "llm",
|
|
4098
|
+
startedAt,
|
|
4099
|
+
ok: false,
|
|
4100
|
+
code
|
|
4101
|
+
});
|
|
4102
|
+
return result;
|
|
3751
4103
|
}
|
|
3752
4104
|
}
|
|
3753
4105
|
async runOnce() {
|
|
@@ -3772,12 +4124,49 @@ export class SelfHostedNodeRuntime {
|
|
|
3772
4124
|
models = [];
|
|
3773
4125
|
version = null;
|
|
3774
4126
|
}
|
|
4127
|
+
const discoveryLatencyMs = Date.now() - startedAt;
|
|
3775
4128
|
const capabilityPayload = await this.buildCapabilityHeartbeatPayload(enrollment.runtimeToken);
|
|
4129
|
+
const loadTelemetry = this.buildLoadTelemetry({
|
|
4130
|
+
models,
|
|
4131
|
+
discoveryLatencyMs,
|
|
4132
|
+
discoveryFailureCount: recentFailureCount,
|
|
4133
|
+
capabilityPayload
|
|
4134
|
+
});
|
|
4135
|
+
const exposedModelCount = models.filter((model) => model.exposed !== false).length;
|
|
4136
|
+
const loadReportingEnabled = this.config.loadReportingEnabled !== false;
|
|
4137
|
+
const capacityPayload = loadReportingEnabled
|
|
4138
|
+
? {
|
|
4139
|
+
protocol_version: loadTelemetry.runtime_protocol_version,
|
|
4140
|
+
runtime_protocol_version: loadTelemetry.runtime_protocol_version,
|
|
4141
|
+
load_balancer_protocol_version: loadTelemetry.load_balancer_protocol_version,
|
|
4142
|
+
catalog_metadata_version: loadTelemetry.catalog_metadata_version,
|
|
4143
|
+
catalog_fingerprint: loadTelemetry.catalog_fingerprint,
|
|
4144
|
+
max_concurrency: loadTelemetry.max_concurrency,
|
|
4145
|
+
max_concurrent_llm_jobs: loadTelemetry.max_concurrent_llm_jobs,
|
|
4146
|
+
max_concurrent_generic_jobs: loadTelemetry.max_concurrent_generic_jobs,
|
|
4147
|
+
active_jobs: loadTelemetry.active_jobs,
|
|
4148
|
+
queued_jobs: loadTelemetry.queued_jobs,
|
|
4149
|
+
free_slots: loadTelemetry.free_slots,
|
|
4150
|
+
drain_mode: loadTelemetry.drain_mode,
|
|
4151
|
+
execution_class_capacity: loadTelemetry.execution_class_capacity
|
|
4152
|
+
}
|
|
4153
|
+
: {
|
|
4154
|
+
active_jobs: loadTelemetry.active_jobs,
|
|
4155
|
+
queued_jobs: loadTelemetry.queued_jobs
|
|
4156
|
+
};
|
|
3776
4157
|
const heartbeatPayload = {
|
|
3777
4158
|
node_id: this.config.nodeId,
|
|
3778
4159
|
node_version: this.config.nodeVersion,
|
|
4160
|
+
runtime_protocol_version: SELF_HOSTED_RUNTIME_PROTOCOL_VERSION,
|
|
3779
4161
|
config_version: enrollment.state.config_version ?? null,
|
|
3780
4162
|
status,
|
|
4163
|
+
runtime: {
|
|
4164
|
+
protocol_version: SELF_HOSTED_RUNTIME_PROTOCOL_VERSION,
|
|
4165
|
+
relay_mode: this.config.relayMode || "outbound",
|
|
4166
|
+
load_reporting_enabled: loadReportingEnabled,
|
|
4167
|
+
hardware_telemetry_enabled: this.config.hardwareTelemetryEnabled === true,
|
|
4168
|
+
drain_mode: this.config.drainMode === true
|
|
4169
|
+
},
|
|
3781
4170
|
discovery: {
|
|
3782
4171
|
source: discoverySource,
|
|
3783
4172
|
mcoda_status: discoverySource === "mcoda" && status === "online" ? "ok" : status === "degraded" ? "error" : null
|
|
@@ -3791,20 +4180,24 @@ export class SelfHostedNodeRuntime {
|
|
|
3791
4180
|
status: null,
|
|
3792
4181
|
version: null
|
|
3793
4182
|
},
|
|
3794
|
-
capacity:
|
|
3795
|
-
active_jobs: 0,
|
|
3796
|
-
queued_jobs: 0
|
|
3797
|
-
},
|
|
4183
|
+
capacity: capacityPayload,
|
|
3798
4184
|
health: {
|
|
3799
|
-
avg_latency_ms:
|
|
3800
|
-
recent_failure_count:
|
|
4185
|
+
avg_latency_ms: loadTelemetry.avg_latency_ms ?? discoveryLatencyMs,
|
|
4186
|
+
recent_failure_count: loadTelemetry.recent_failure_count,
|
|
4187
|
+
recent_failures: loadTelemetry.recent_failures,
|
|
3801
4188
|
last_success_at: status === "online" ? new Date().toISOString() : null
|
|
3802
4189
|
},
|
|
4190
|
+
local_agent_catalog: {
|
|
4191
|
+
revision: loadTelemetry.catalog_fingerprint,
|
|
4192
|
+
metadata_version: loadTelemetry.catalog_metadata_version,
|
|
4193
|
+
model_count: models.length,
|
|
4194
|
+
exposed_model_count: exposedModelCount
|
|
4195
|
+
},
|
|
3803
4196
|
models,
|
|
3804
|
-
capabilities: capabilityPayload
|
|
4197
|
+
capabilities: capabilityPayload,
|
|
4198
|
+
...(loadTelemetry.hardware_pressure ? { hardware_pressure: loadTelemetry.hardware_pressure } : {})
|
|
3805
4199
|
};
|
|
3806
4200
|
const heartbeatResponse = await this.gateway.heartbeat(enrollment.runtimeToken, heartbeatPayload);
|
|
3807
|
-
const exposedModelCount = models.filter((model) => model.exposed !== false).length;
|
|
3808
4201
|
return {
|
|
3809
4202
|
enrolled: enrollment.enrolled,
|
|
3810
4203
|
status,
|
|
@@ -3812,6 +4205,7 @@ export class SelfHostedNodeRuntime {
|
|
|
3812
4205
|
discovery_source: discoverySource,
|
|
3813
4206
|
mcoda_agent_count: discoverySource === "mcoda" ? exposedModelCount : undefined,
|
|
3814
4207
|
ollama_version: version,
|
|
4208
|
+
capacity: loadTelemetry,
|
|
3815
4209
|
heartbeat_response: heartbeatResponse
|
|
3816
4210
|
};
|
|
3817
4211
|
}
|
|
@@ -3846,9 +4240,17 @@ export class SelfHostedNodeRuntime {
|
|
|
3846
4240
|
}
|
|
3847
4241
|
async pollAndExecuteJob(waitMs = DEFAULT_JOB_POLL_WAIT_MS) {
|
|
3848
4242
|
const enrollment = await this.ensureEnrolled();
|
|
4243
|
+
const pollCapacity = this.buildLoadTelemetry({ models: [] });
|
|
3849
4244
|
const response = await this.gateway.pollJob(enrollment.runtimeToken, {
|
|
3850
4245
|
node_id: this.config.nodeId,
|
|
3851
|
-
capacity: {
|
|
4246
|
+
capacity: {
|
|
4247
|
+
active_jobs: pollCapacity.active_jobs,
|
|
4248
|
+
queued_jobs: pollCapacity.queued_jobs,
|
|
4249
|
+
max_jobs: pollCapacity.max_concurrency,
|
|
4250
|
+
max_concurrency: pollCapacity.max_concurrency,
|
|
4251
|
+
free_slots: pollCapacity.free_slots,
|
|
4252
|
+
drain_mode: pollCapacity.drain_mode
|
|
4253
|
+
},
|
|
3852
4254
|
wait_ms: waitMs
|
|
3853
4255
|
});
|
|
3854
4256
|
const job = response.job || null;
|
|
@@ -3876,6 +4278,14 @@ export class SelfHostedNodeRuntime {
|
|
|
3876
4278
|
};
|
|
3877
4279
|
const result = await this.executeJob(job, {
|
|
3878
4280
|
attachedMswarmApiKey: optionalText(response.attached_mswarm_api_key) || undefined,
|
|
4281
|
+
onStarted: async (event) => {
|
|
4282
|
+
await this.gateway.postJobStart(enrollment.runtimeToken, job.job_id, {
|
|
4283
|
+
node_id: this.config.nodeId,
|
|
4284
|
+
agent_slug: event.agent_slug || job.agent_slug,
|
|
4285
|
+
source_agent_slug: event.source_agent_slug || job.source_agent_slug || null,
|
|
4286
|
+
model: event.model || job.model || job.openai_request.model
|
|
4287
|
+
});
|
|
4288
|
+
},
|
|
3879
4289
|
onOpenAIChunk: async (chunk) => {
|
|
3880
4290
|
if (job.openai_request.stream !== true || streamEventForwardingFailed) {
|
|
3881
4291
|
return;
|