@mcoda/mswarm 0.1.80 → 0.1.85
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/codali-executor.d.ts.map +1 -1
- package/dist/codali-executor.js +17 -16
- package/dist/codali-executor.js.map +1 -1
- package/dist/runtime.d.ts +49 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +369 -43
- package/dist/runtime.js.map +1 -1
- package/dist/vendor/codali/agents/PhaseAgentSelector.d.ts.map +1 -1
- package/dist/vendor/codali/agents/PhaseAgentSelector.js +15 -0
- package/package.json +3 -3
package/dist/runtime.js
CHANGED
|
@@ -3,8 +3,9 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
|
3
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
|
+
import { GlobalRepository } from "@mcoda/db";
|
|
6
7
|
import { MswarmCodaliExecutor } from "./codali-executor.js";
|
|
7
|
-
import { MSWARM_CAPABILITY_SCHEMA_VERSION, assertMswarmSafeRelativePath, validateMswarmArchiveEntry, buildMswarmCapabilityNames, buildMswarmPrivateCapabilityCatalogEntry, buildMswarmLocalArtifactUri, buildMswarmSandboxProfile, defaultMswarmArtifactAccessPolicy, defaultMswarmArtifactRetentionPolicy, projectMswarmPublicCapabilities, validateMswarmGenericJobRequest } from "@mcoda/shared";
|
|
8
|
+
import { MSWARM_CAPABILITY_SCHEMA_VERSION, CryptoHelper, assertMswarmSafeRelativePath, validateMswarmArchiveEntry, buildMswarmCapabilityNames, buildMswarmPrivateCapabilityCatalogEntry, buildMswarmLocalArtifactUri, buildMswarmSandboxProfile, defaultMswarmArtifactAccessPolicy, defaultMswarmArtifactRetentionPolicy, projectMswarmPublicCapabilities, validateMswarmGenericJobRequest } from "@mcoda/shared";
|
|
8
9
|
const DEFAULT_GATEWAY_BASE_URL = "http://127.0.0.1:8080";
|
|
9
10
|
const DEFAULT_SETUP_GATEWAY_BASE_URL = "https://api.mswarm.org";
|
|
10
11
|
const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434";
|
|
@@ -26,7 +27,12 @@ const DEFAULT_MCODA_LIST_ARGS = ["agent", "list", "--json", "--refresh-health"];
|
|
|
26
27
|
const DEFAULT_COMMAND_MAX_BUFFER = 16 * 1024 * 1024;
|
|
27
28
|
const DEFAULT_LOCAL_ARTIFACT_MAX_BYTES = 512 * 1024 * 1024;
|
|
28
29
|
const DEFAULT_JOB_POLL_WAIT_MS = 25000;
|
|
30
|
+
const DEFAULT_SELF_HOSTED_JOBS_POLL_PATH = "/v1/swarm/self-hosted/node/jobs/poll";
|
|
31
|
+
const DEFAULT_SELF_HOSTED_JOBS_START_PATH_TEMPLATE = "/v1/swarm/self-hosted/node/jobs/:jobId/start";
|
|
32
|
+
const DEFAULT_SELF_HOSTED_JOBS_EVENTS_PATH_TEMPLATE = "/v1/swarm/self-hosted/node/jobs/:jobId/events";
|
|
33
|
+
const DEFAULT_SELF_HOSTED_JOBS_RESULT_PATH_TEMPLATE = "/v1/swarm/self-hosted/node/jobs/:jobId/result";
|
|
29
34
|
const DEFAULT_STREAM_EVENT_BATCH_SIZE = 8;
|
|
35
|
+
const SELF_HOSTED_PROTOCOL_MISMATCH_CODE = "self_hosted_protocol_mismatch";
|
|
30
36
|
const OWNER_LOCAL_TEST_ECHO_JOB_TYPE = "tenant.test-echo";
|
|
31
37
|
const TEST_ECHO_RUNNER_ID = "test.echo";
|
|
32
38
|
const RENDER_BLENDER_JOB_TYPE = "render.blender";
|
|
@@ -402,6 +408,37 @@ function parseCliOptions(argv) {
|
|
|
402
408
|
function trimTrailingSlash(value) {
|
|
403
409
|
return value.replace(/\/+$/g, "");
|
|
404
410
|
}
|
|
411
|
+
function lifecyclePath(value, fallback) {
|
|
412
|
+
return optionalText(value) || fallback;
|
|
413
|
+
}
|
|
414
|
+
function resolveLifecycleUrl(gatewayBaseUrl, pathOrUrl) {
|
|
415
|
+
try {
|
|
416
|
+
return new URL(pathOrUrl).toString();
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
return new URL(pathOrUrl.startsWith("/") ? pathOrUrl : `/${pathOrUrl}`, `${trimTrailingSlash(gatewayBaseUrl)}/`).toString();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function resolveLifecycleTemplate(template, jobId) {
|
|
423
|
+
const encoded = encodeURIComponent(jobId);
|
|
424
|
+
return template
|
|
425
|
+
.replace(/:jobId\b/g, encoded)
|
|
426
|
+
.replace(/:job_id\b/g, encoded)
|
|
427
|
+
.replace(/\{jobId\}/g, encoded)
|
|
428
|
+
.replace(/\{job_id\}/g, encoded);
|
|
429
|
+
}
|
|
430
|
+
function usageFromTokenCounts(promptTokens, completionTokens) {
|
|
431
|
+
const prompt = positiveInteger(promptTokens) ?? 0;
|
|
432
|
+
const completion = positiveInteger(completionTokens) ?? 0;
|
|
433
|
+
return { prompt_tokens: prompt, completion_tokens: completion, total_tokens: prompt + completion };
|
|
434
|
+
}
|
|
435
|
+
function openAiUsage(response) {
|
|
436
|
+
const usage = isRecord(response?.usage) ? response.usage : undefined;
|
|
437
|
+
const prompt = positiveInteger(usage?.prompt_tokens) ?? 0;
|
|
438
|
+
const completion = positiveInteger(usage?.completion_tokens) ?? 0;
|
|
439
|
+
const total = positiveInteger(usage?.total_tokens) ?? prompt + completion;
|
|
440
|
+
return { prompt_tokens: prompt, completion_tokens: completion, total_tokens: total };
|
|
441
|
+
}
|
|
405
442
|
function defaultStatePath() {
|
|
406
443
|
return join(homedir(), ".mswarm", "self-hosted-node", "config.json");
|
|
407
444
|
}
|
|
@@ -631,6 +668,18 @@ function isAgentExposed(agentSlug, defaultModel, bestUsage, config) {
|
|
|
631
668
|
}
|
|
632
669
|
return config.exposeAllModels;
|
|
633
670
|
}
|
|
671
|
+
class HttpStatusError extends Error {
|
|
672
|
+
constructor(url, status, responseText) {
|
|
673
|
+
super(`request_failed:${status}:${responseText.slice(0, 200)}`);
|
|
674
|
+
this.name = "HttpStatusError";
|
|
675
|
+
this.url = url;
|
|
676
|
+
this.status = status;
|
|
677
|
+
this.responseText = responseText;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function isHttpStatusError(error, status) {
|
|
681
|
+
return error instanceof HttpStatusError && (status === undefined || error.status === status);
|
|
682
|
+
}
|
|
634
683
|
async function fetchJson(fetchImpl, url, init, timeoutMs) {
|
|
635
684
|
const controller = new AbortController();
|
|
636
685
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -638,7 +687,7 @@ async function fetchJson(fetchImpl, url, init, timeoutMs) {
|
|
|
638
687
|
const response = await fetchImpl(url, { ...init, signal: controller.signal });
|
|
639
688
|
if (!response.ok) {
|
|
640
689
|
const text = await response.text().catch(() => "");
|
|
641
|
-
throw new
|
|
690
|
+
throw new HttpStatusError(url, response.status, text);
|
|
642
691
|
}
|
|
643
692
|
return (await response.json());
|
|
644
693
|
}
|
|
@@ -1168,6 +1217,18 @@ export async function readSelfHostedNodeConfig(env = process.env) {
|
|
|
1168
1217
|
const maxConcurrentLlmJobs = parsePositiveInteger(env.MSWARM_SELF_HOSTED_MAX_CONCURRENT_LLM_JOBS, state.max_concurrent_llm_jobs || maxConcurrentJobs);
|
|
1169
1218
|
return {
|
|
1170
1219
|
gatewayBaseUrl: trimTrailingSlash(gatewayBaseUrl),
|
|
1220
|
+
jobsPollPath: optionalText(env.MSWARM_SELF_HOSTED_JOBS_POLL_PATH) ||
|
|
1221
|
+
state.jobs_poll_path ||
|
|
1222
|
+
DEFAULT_SELF_HOSTED_JOBS_POLL_PATH,
|
|
1223
|
+
jobsStartPathTemplate: optionalText(env.MSWARM_SELF_HOSTED_JOBS_START_PATH_TEMPLATE) ||
|
|
1224
|
+
state.jobs_start_path_template ||
|
|
1225
|
+
DEFAULT_SELF_HOSTED_JOBS_START_PATH_TEMPLATE,
|
|
1226
|
+
jobsEventsPathTemplate: optionalText(env.MSWARM_SELF_HOSTED_JOBS_EVENTS_PATH_TEMPLATE) ||
|
|
1227
|
+
state.jobs_events_path_template ||
|
|
1228
|
+
DEFAULT_SELF_HOSTED_JOBS_EVENTS_PATH_TEMPLATE,
|
|
1229
|
+
jobsResultPathTemplate: optionalText(env.MSWARM_SELF_HOSTED_JOBS_RESULT_PATH_TEMPLATE) ||
|
|
1230
|
+
state.jobs_result_path_template ||
|
|
1231
|
+
DEFAULT_SELF_HOSTED_JOBS_RESULT_PATH_TEMPLATE,
|
|
1171
1232
|
nodeId,
|
|
1172
1233
|
serverName: optionalText(env.MSWARM_SELF_HOSTED_SERVER_NAME) || state.server_name || null,
|
|
1173
1234
|
relayMode: parseRelayMode(env.MSWARM_SELF_HOSTED_RELAY_MODE || state.relay_mode),
|
|
@@ -1608,6 +1669,20 @@ function configText(config, ...keys) {
|
|
|
1608
1669
|
}
|
|
1609
1670
|
return undefined;
|
|
1610
1671
|
}
|
|
1672
|
+
async function defaultMcodaAgentAuthResolver(agent) {
|
|
1673
|
+
const agentId = optionalText(agent.id);
|
|
1674
|
+
if (!agentId) {
|
|
1675
|
+
return undefined;
|
|
1676
|
+
}
|
|
1677
|
+
const repo = await GlobalRepository.create();
|
|
1678
|
+
try {
|
|
1679
|
+
const secret = await repo.getAgentAuthSecret(agentId);
|
|
1680
|
+
return secret?.encryptedSecret ? CryptoHelper.decryptSecret(secret.encryptedSecret) : undefined;
|
|
1681
|
+
}
|
|
1682
|
+
finally {
|
|
1683
|
+
await repo.close();
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1611
1686
|
function mcodaAgentDefaultModel(agent) {
|
|
1612
1687
|
return (optionalText(agent.defaultModel) ||
|
|
1613
1688
|
optionalText(agent.default_model) ||
|
|
@@ -1623,18 +1698,35 @@ function resolveCodaliProviderForAgent(agent) {
|
|
|
1623
1698
|
if (["ollama-remote", "ollama-cli", "ollama", "local-model"].includes(adapter || "")) {
|
|
1624
1699
|
return "ollama-remote";
|
|
1625
1700
|
}
|
|
1626
|
-
if (adapter === "openai" ||
|
|
1701
|
+
if (adapter === "openai" ||
|
|
1702
|
+
adapter === "openai-api" ||
|
|
1703
|
+
adapter === "openai-compatible" ||
|
|
1704
|
+
adapter === "openai-cli") {
|
|
1627
1705
|
return "openai-compatible";
|
|
1628
1706
|
}
|
|
1629
1707
|
if (adapter === "codex-cli")
|
|
1630
1708
|
return "codex-cli";
|
|
1631
1709
|
return adapter || undefined;
|
|
1632
1710
|
}
|
|
1711
|
+
function mcodaAgentRequiresApiKey(agent, mapped) {
|
|
1712
|
+
if (mapped.provider !== "openai-compatible") {
|
|
1713
|
+
return false;
|
|
1714
|
+
}
|
|
1715
|
+
if (mapped.localRunner) {
|
|
1716
|
+
return mapped.authMode === "bearer";
|
|
1717
|
+
}
|
|
1718
|
+
const adapter = optionalText(agent.adapter) || "";
|
|
1719
|
+
if (["openai", "openai-api", "openai-compatible"].includes(adapter)) {
|
|
1720
|
+
return mapped.authMode !== "none";
|
|
1721
|
+
}
|
|
1722
|
+
return mapped.authMode === "bearer";
|
|
1723
|
+
}
|
|
1633
1724
|
function mapMcodaAgentToCodaliAgent(agent, fallbackSlug) {
|
|
1634
1725
|
const adapter = optionalText(agent.adapter) || "unknown";
|
|
1635
1726
|
const model = mcodaAgentDefaultModel(agent) || fallbackSlug;
|
|
1636
1727
|
const config = agent.config ?? null;
|
|
1637
1728
|
const localRunner = normalizeMcodaLocalRunnerConfig(adapter, config);
|
|
1729
|
+
const configAuthMode = normalizeLocalRunnerAuthMode(config?.authMode);
|
|
1638
1730
|
const supportsTools = optionalBoolean(agent.supportsTools, agent.supports_tools, localRunner?.supportsTools) === true;
|
|
1639
1731
|
return {
|
|
1640
1732
|
slug: optionalText(agent.slug) || fallbackSlug,
|
|
@@ -1649,7 +1741,7 @@ function mapMcodaAgentToCodaliAgent(agent, fallbackSlug) {
|
|
|
1649
1741
|
: configText(config, "apiKey", "api_key"),
|
|
1650
1742
|
localRunner,
|
|
1651
1743
|
runnerKind: localRunner?.runnerKind,
|
|
1652
|
-
authMode: localRunner?.authMode,
|
|
1744
|
+
authMode: localRunner?.authMode ?? configAuthMode,
|
|
1653
1745
|
dummyBearerToken: localRunner?.dummyBearerToken,
|
|
1654
1746
|
headers: localRunner?.headers,
|
|
1655
1747
|
extraBody: localRunner?.extraBody,
|
|
@@ -1687,10 +1779,12 @@ const DOCDEX_JOB_ERROR_CODES = new Set([
|
|
|
1687
1779
|
]);
|
|
1688
1780
|
const PRE_START_JOB_ERROR_CODES = new Set([
|
|
1689
1781
|
"selected_agent_unavailable",
|
|
1782
|
+
"selected_agent_auth_unavailable",
|
|
1690
1783
|
"selected_agent_unhealthy",
|
|
1691
1784
|
"validation_failed",
|
|
1692
1785
|
"docdex_context_missing",
|
|
1693
1786
|
"docdex_api_key_missing",
|
|
1787
|
+
SELF_HOSTED_PROTOCOL_MISMATCH_CODE,
|
|
1694
1788
|
]);
|
|
1695
1789
|
class SelfHostedDocdexJobError extends Error {
|
|
1696
1790
|
constructor(code, message) {
|
|
@@ -1706,6 +1800,18 @@ class SelfHostedPreStartJobError extends Error {
|
|
|
1706
1800
|
this.code = code;
|
|
1707
1801
|
}
|
|
1708
1802
|
}
|
|
1803
|
+
class SelfHostedProtocolMismatchError extends Error {
|
|
1804
|
+
constructor(input) {
|
|
1805
|
+
super(`Gateway is missing lifecycle endpoint ${input.endpoint} at ${input.gatewayBaseUrl} ` +
|
|
1806
|
+
`(runtime package version ${input.runtimePackageVersion}). ` +
|
|
1807
|
+
"Run a direct local agent/model instead, for example qwen3.6-llama.cpp.");
|
|
1808
|
+
this.code = SELF_HOSTED_PROTOCOL_MISMATCH_CODE;
|
|
1809
|
+
this.name = SELF_HOSTED_PROTOCOL_MISMATCH_CODE;
|
|
1810
|
+
this.endpoint = input.endpoint;
|
|
1811
|
+
this.gatewayBaseUrl = input.gatewayBaseUrl;
|
|
1812
|
+
this.runtimePackageVersion = input.runtimePackageVersion;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1709
1815
|
function normalizeDocdexCapabilityMap(value) {
|
|
1710
1816
|
const record = objectRecord(value);
|
|
1711
1817
|
if (!record)
|
|
@@ -3214,9 +3320,36 @@ export class McodaLocalAgentExecutor {
|
|
|
3214
3320
|
export class MswarmSelfHostedNodeClient {
|
|
3215
3321
|
constructor(input) {
|
|
3216
3322
|
this.gatewayBaseUrl = trimTrailingSlash(input.gatewayBaseUrl);
|
|
3323
|
+
this.jobsPollPath = lifecyclePath(input.jobsPollPath, DEFAULT_SELF_HOSTED_JOBS_POLL_PATH);
|
|
3324
|
+
this.jobsStartPathTemplate = lifecyclePath(input.jobsStartPathTemplate, DEFAULT_SELF_HOSTED_JOBS_START_PATH_TEMPLATE);
|
|
3325
|
+
this.jobsEventsPathTemplate = lifecyclePath(input.jobsEventsPathTemplate, DEFAULT_SELF_HOSTED_JOBS_EVENTS_PATH_TEMPLATE);
|
|
3326
|
+
this.jobsResultPathTemplate = lifecyclePath(input.jobsResultPathTemplate, DEFAULT_SELF_HOSTED_JOBS_RESULT_PATH_TEMPLATE);
|
|
3217
3327
|
this.fetchImpl = input.fetchImpl || fetch;
|
|
3218
3328
|
this.timeoutMs = input.timeoutMs || DEFAULT_REQUEST_TIMEOUT_MS;
|
|
3219
3329
|
}
|
|
3330
|
+
lifecycleEndpoint(kind) {
|
|
3331
|
+
const path = kind === "poll"
|
|
3332
|
+
? this.jobsPollPath
|
|
3333
|
+
: kind === "start"
|
|
3334
|
+
? this.jobsStartPathTemplate
|
|
3335
|
+
: kind === "events"
|
|
3336
|
+
? this.jobsEventsPathTemplate
|
|
3337
|
+
: this.jobsResultPathTemplate;
|
|
3338
|
+
return `POST ${path}`;
|
|
3339
|
+
}
|
|
3340
|
+
lifecycleGatewayBaseUrl() {
|
|
3341
|
+
return this.gatewayBaseUrl;
|
|
3342
|
+
}
|
|
3343
|
+
lifecycleUrl(kind, jobId) {
|
|
3344
|
+
const path = kind === "poll"
|
|
3345
|
+
? this.jobsPollPath
|
|
3346
|
+
: kind === "start"
|
|
3347
|
+
? this.jobsStartPathTemplate
|
|
3348
|
+
: kind === "events"
|
|
3349
|
+
? this.jobsEventsPathTemplate
|
|
3350
|
+
: this.jobsResultPathTemplate;
|
|
3351
|
+
return resolveLifecycleUrl(this.gatewayBaseUrl, jobId ? resolveLifecycleTemplate(path, jobId) : path);
|
|
3352
|
+
}
|
|
3220
3353
|
async enroll(nodeId, enrollmentToken) {
|
|
3221
3354
|
return fetchJson(this.fetchImpl, `${this.gatewayBaseUrl}/v1/swarm/self-hosted/node/enroll`, {
|
|
3222
3355
|
method: "POST",
|
|
@@ -3268,7 +3401,7 @@ export class MswarmSelfHostedNodeClient {
|
|
|
3268
3401
|
}, this.timeoutMs);
|
|
3269
3402
|
}
|
|
3270
3403
|
async pollJob(runtimeToken, payload) {
|
|
3271
|
-
return fetchJson(this.fetchImpl,
|
|
3404
|
+
return fetchJson(this.fetchImpl, this.lifecycleUrl("poll"), {
|
|
3272
3405
|
method: "POST",
|
|
3273
3406
|
headers: {
|
|
3274
3407
|
"content-type": "application/json",
|
|
@@ -3278,7 +3411,7 @@ export class MswarmSelfHostedNodeClient {
|
|
|
3278
3411
|
}, Math.max(this.timeoutMs, (payload.wait_ms || 0) + 5000));
|
|
3279
3412
|
}
|
|
3280
3413
|
async postJobResult(runtimeToken, jobId, payload) {
|
|
3281
|
-
return fetchJson(this.fetchImpl,
|
|
3414
|
+
return fetchJson(this.fetchImpl, this.lifecycleUrl("result", jobId), {
|
|
3282
3415
|
method: "POST",
|
|
3283
3416
|
headers: {
|
|
3284
3417
|
"content-type": "application/json",
|
|
@@ -3288,7 +3421,7 @@ export class MswarmSelfHostedNodeClient {
|
|
|
3288
3421
|
}, this.timeoutMs);
|
|
3289
3422
|
}
|
|
3290
3423
|
async postJobStart(runtimeToken, jobId, payload) {
|
|
3291
|
-
return fetchJson(this.fetchImpl,
|
|
3424
|
+
return fetchJson(this.fetchImpl, this.lifecycleUrl("start", jobId), {
|
|
3292
3425
|
method: "POST",
|
|
3293
3426
|
headers: {
|
|
3294
3427
|
"content-type": "application/json",
|
|
@@ -3298,7 +3431,7 @@ export class MswarmSelfHostedNodeClient {
|
|
|
3298
3431
|
}, this.timeoutMs);
|
|
3299
3432
|
}
|
|
3300
3433
|
async postJobEvents(runtimeToken, jobId, payload) {
|
|
3301
|
-
return fetchJson(this.fetchImpl,
|
|
3434
|
+
return fetchJson(this.fetchImpl, this.lifecycleUrl("events", jobId), {
|
|
3302
3435
|
method: "POST",
|
|
3303
3436
|
headers: {
|
|
3304
3437
|
"content-type": "application/json",
|
|
@@ -3316,11 +3449,16 @@ export class SelfHostedNodeRuntime {
|
|
|
3316
3449
|
this.queuedGenericJobs = 0;
|
|
3317
3450
|
this.latencySamplesMs = [];
|
|
3318
3451
|
this.recentFailures = [];
|
|
3452
|
+
this.lifecyclePollingDisabled = false;
|
|
3319
3453
|
this.config = config;
|
|
3320
3454
|
this.gateway =
|
|
3321
3455
|
deps?.gateway ||
|
|
3322
3456
|
new MswarmSelfHostedNodeClient({
|
|
3323
3457
|
gatewayBaseUrl: config.gatewayBaseUrl,
|
|
3458
|
+
jobsPollPath: config.jobsPollPath,
|
|
3459
|
+
jobsStartPathTemplate: config.jobsStartPathTemplate,
|
|
3460
|
+
jobsEventsPathTemplate: config.jobsEventsPathTemplate,
|
|
3461
|
+
jobsResultPathTemplate: config.jobsResultPathTemplate,
|
|
3324
3462
|
fetchImpl: deps?.fetchImpl,
|
|
3325
3463
|
timeoutMs: config.requestTimeoutMs
|
|
3326
3464
|
});
|
|
@@ -3331,6 +3469,7 @@ export class SelfHostedNodeRuntime {
|
|
|
3331
3469
|
args: config.mcodaListArgs,
|
|
3332
3470
|
timeoutMs: config.requestTimeoutMs
|
|
3333
3471
|
});
|
|
3472
|
+
this.mcodaAgentAuthResolver = deps?.mcodaAgentAuthResolver || defaultMcodaAgentAuthResolver;
|
|
3334
3473
|
this.mcodaExecutor =
|
|
3335
3474
|
deps?.mcodaExecutor ||
|
|
3336
3475
|
new McodaLocalAgentExecutor({
|
|
@@ -3395,6 +3534,53 @@ export class SelfHostedNodeRuntime {
|
|
|
3395
3534
|
this.recentFailures.splice(MAX_TELEMETRY_FAILURES);
|
|
3396
3535
|
}
|
|
3397
3536
|
}
|
|
3537
|
+
lifecycleProtocolMismatch(kind, error) {
|
|
3538
|
+
if (!isHttpStatusError(error, 404)) {
|
|
3539
|
+
return undefined;
|
|
3540
|
+
}
|
|
3541
|
+
return new SelfHostedProtocolMismatchError({
|
|
3542
|
+
endpoint: this.gateway.lifecycleEndpoint(kind),
|
|
3543
|
+
gatewayBaseUrl: this.gateway.lifecycleGatewayBaseUrl(),
|
|
3544
|
+
runtimePackageVersion: this.config.nodeVersion
|
|
3545
|
+
});
|
|
3546
|
+
}
|
|
3547
|
+
isLifecycleProtocolDegradedState(state) {
|
|
3548
|
+
return state?.lifecycle_health_reason === SELF_HOSTED_PROTOCOL_MISMATCH_CODE;
|
|
3549
|
+
}
|
|
3550
|
+
async readLifecycleState() {
|
|
3551
|
+
try {
|
|
3552
|
+
return await readSelfHostedNodeState(this.config.statePath);
|
|
3553
|
+
}
|
|
3554
|
+
catch {
|
|
3555
|
+
return {};
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
async markLifecycleProtocolDegraded(error) {
|
|
3559
|
+
this.lifecyclePollingDisabled = true;
|
|
3560
|
+
this.recentFailures.unshift({
|
|
3561
|
+
execution_class: "agentic",
|
|
3562
|
+
code: SELF_HOSTED_PROTOCOL_MISMATCH_CODE,
|
|
3563
|
+
at: new Date().toISOString()
|
|
3564
|
+
});
|
|
3565
|
+
this.recentFailures.splice(MAX_TELEMETRY_FAILURES);
|
|
3566
|
+
const state = await this.readLifecycleState();
|
|
3567
|
+
await writeSelfHostedNodeState(this.config.statePath, {
|
|
3568
|
+
...state,
|
|
3569
|
+
lifecycle_health_status: "degraded",
|
|
3570
|
+
lifecycle_health_reason: SELF_HOSTED_PROTOCOL_MISMATCH_CODE,
|
|
3571
|
+
lifecycle_health_message: error.message,
|
|
3572
|
+
lifecycle_health_updated_at: new Date().toISOString()
|
|
3573
|
+
});
|
|
3574
|
+
}
|
|
3575
|
+
jobResultPayload(result) {
|
|
3576
|
+
return {
|
|
3577
|
+
...result,
|
|
3578
|
+
node_id: this.config.nodeId,
|
|
3579
|
+
...(result.status === "success"
|
|
3580
|
+
? { usage: result.usage || openAiUsage(result.openai_response) }
|
|
3581
|
+
: {})
|
|
3582
|
+
};
|
|
3583
|
+
}
|
|
3398
3584
|
averageLatencyMs(fallback = null) {
|
|
3399
3585
|
if (this.latencySamplesMs.length === 0) {
|
|
3400
3586
|
return fallback;
|
|
@@ -3488,6 +3674,12 @@ export class SelfHostedNodeRuntime {
|
|
|
3488
3674
|
throw new Error("Bootstrap response did not include node_id and runtime_token");
|
|
3489
3675
|
}
|
|
3490
3676
|
const heartbeatInterval = bootstrap.heartbeat_interval_seconds || setupConfig.heartbeatIntervalSeconds;
|
|
3677
|
+
const relay = bootstrap.relay || {};
|
|
3678
|
+
const relayGatewayBaseUrl = optionalText(relay.gateway_base_url) || setupConfig.gatewayBaseUrl;
|
|
3679
|
+
const jobsPollPath = lifecyclePath(relay.jobs_poll_path, DEFAULT_SELF_HOSTED_JOBS_POLL_PATH);
|
|
3680
|
+
const jobsStartPathTemplate = lifecyclePath(relay.jobs_start_path_template, DEFAULT_SELF_HOSTED_JOBS_START_PATH_TEMPLATE);
|
|
3681
|
+
const jobsEventsPathTemplate = lifecyclePath(relay.jobs_events_path_template, DEFAULT_SELF_HOSTED_JOBS_EVENTS_PATH_TEMPLATE);
|
|
3682
|
+
const jobsResultPathTemplate = lifecyclePath(relay.jobs_result_path_template, DEFAULT_SELF_HOSTED_JOBS_RESULT_PATH_TEMPLATE);
|
|
3491
3683
|
const state = {
|
|
3492
3684
|
node_id: nodeId,
|
|
3493
3685
|
server_name: optionalText(bootstrap.node?.server_name) || setupConfig.serverName,
|
|
@@ -3501,7 +3693,15 @@ export class SelfHostedNodeRuntime {
|
|
|
3501
3693
|
heartbeat_timeout_seconds: bootstrap.heartbeat_timeout_seconds,
|
|
3502
3694
|
enrolled_at: new Date().toISOString(),
|
|
3503
3695
|
updated_at: new Date().toISOString(),
|
|
3504
|
-
gateway_base_url:
|
|
3696
|
+
gateway_base_url: trimTrailingSlash(relayGatewayBaseUrl),
|
|
3697
|
+
jobs_poll_path: jobsPollPath,
|
|
3698
|
+
jobs_start_path_template: jobsStartPathTemplate,
|
|
3699
|
+
jobs_events_path_template: jobsEventsPathTemplate,
|
|
3700
|
+
jobs_result_path_template: jobsResultPathTemplate,
|
|
3701
|
+
lifecycle_health_status: "healthy",
|
|
3702
|
+
lifecycle_health_reason: undefined,
|
|
3703
|
+
lifecycle_health_message: undefined,
|
|
3704
|
+
lifecycle_health_updated_at: new Date().toISOString(),
|
|
3505
3705
|
ollama_base_url: setupConfig.ollamaBaseUrl,
|
|
3506
3706
|
discovery_mode: setupConfig.discoveryMode,
|
|
3507
3707
|
mcoda_bin: setupConfig.mcodaBin,
|
|
@@ -3525,8 +3725,23 @@ export class SelfHostedNodeRuntime {
|
|
|
3525
3725
|
};
|
|
3526
3726
|
await writeSelfHostedNodeState(setupConfig.statePath, state);
|
|
3527
3727
|
await writeSelfHostedRuntimeToken(setupConfig.runtimeTokenPath, runtimeToken);
|
|
3728
|
+
const runtimeGateway = deps?.gateway && trimTrailingSlash(relayGatewayBaseUrl) === setupConfig.gatewayBaseUrl
|
|
3729
|
+
? deps.gateway
|
|
3730
|
+
: new MswarmSelfHostedNodeClient({
|
|
3731
|
+
gatewayBaseUrl: trimTrailingSlash(relayGatewayBaseUrl),
|
|
3732
|
+
jobsPollPath,
|
|
3733
|
+
jobsStartPathTemplate,
|
|
3734
|
+
jobsEventsPathTemplate,
|
|
3735
|
+
jobsResultPathTemplate,
|
|
3736
|
+
fetchImpl: deps?.fetchImpl,
|
|
3737
|
+
timeoutMs: setupConfig.requestTimeoutMs
|
|
3738
|
+
});
|
|
3528
3739
|
const runtime = new SelfHostedNodeRuntime({
|
|
3529
|
-
gatewayBaseUrl:
|
|
3740
|
+
gatewayBaseUrl: trimTrailingSlash(relayGatewayBaseUrl),
|
|
3741
|
+
jobsPollPath,
|
|
3742
|
+
jobsStartPathTemplate,
|
|
3743
|
+
jobsEventsPathTemplate,
|
|
3744
|
+
jobsResultPathTemplate,
|
|
3530
3745
|
nodeId,
|
|
3531
3746
|
serverName: state.server_name,
|
|
3532
3747
|
relayMode: state.relay_mode || setupConfig.relayMode,
|
|
@@ -3560,7 +3775,7 @@ export class SelfHostedNodeRuntime {
|
|
|
3560
3775
|
exposeAllModels: setupConfig.exposeAllModels,
|
|
3561
3776
|
modelAllowlist: setupConfig.modelAllowlist,
|
|
3562
3777
|
modelBlocklist: setupConfig.modelBlocklist
|
|
3563
|
-
}, { ...deps, gateway });
|
|
3778
|
+
}, { ...deps, gateway: runtimeGateway });
|
|
3564
3779
|
const once = await runtime.runOnce();
|
|
3565
3780
|
return {
|
|
3566
3781
|
created: bootstrap.created === true,
|
|
@@ -3646,6 +3861,12 @@ export class SelfHostedNodeRuntime {
|
|
|
3646
3861
|
if (!runtimeToken) {
|
|
3647
3862
|
throw new Error("Enrollment response did not include runtime_token");
|
|
3648
3863
|
}
|
|
3864
|
+
const relay = response.relay || {};
|
|
3865
|
+
const gatewayBaseUrl = trimTrailingSlash(optionalText(relay.gateway_base_url) || this.config.gatewayBaseUrl);
|
|
3866
|
+
const jobsPollPath = lifecyclePath(relay.jobs_poll_path || this.config.jobsPollPath, DEFAULT_SELF_HOSTED_JOBS_POLL_PATH);
|
|
3867
|
+
const jobsStartPathTemplate = lifecyclePath(relay.jobs_start_path_template || this.config.jobsStartPathTemplate, DEFAULT_SELF_HOSTED_JOBS_START_PATH_TEMPLATE);
|
|
3868
|
+
const jobsEventsPathTemplate = lifecyclePath(relay.jobs_events_path_template || this.config.jobsEventsPathTemplate, DEFAULT_SELF_HOSTED_JOBS_EVENTS_PATH_TEMPLATE);
|
|
3869
|
+
const jobsResultPathTemplate = lifecyclePath(relay.jobs_result_path_template || this.config.jobsResultPathTemplate, DEFAULT_SELF_HOSTED_JOBS_RESULT_PATH_TEMPLATE);
|
|
3649
3870
|
const nextState = {
|
|
3650
3871
|
...currentState,
|
|
3651
3872
|
node_id: this.config.nodeId,
|
|
@@ -3655,7 +3876,15 @@ export class SelfHostedNodeRuntime {
|
|
|
3655
3876
|
heartbeat_timeout_seconds: response.heartbeat_timeout_seconds,
|
|
3656
3877
|
enrolled_at: currentState.enrolled_at || new Date().toISOString(),
|
|
3657
3878
|
updated_at: new Date().toISOString(),
|
|
3658
|
-
gateway_base_url:
|
|
3879
|
+
gateway_base_url: gatewayBaseUrl,
|
|
3880
|
+
jobs_poll_path: jobsPollPath,
|
|
3881
|
+
jobs_start_path_template: jobsStartPathTemplate,
|
|
3882
|
+
jobs_events_path_template: jobsEventsPathTemplate,
|
|
3883
|
+
jobs_result_path_template: jobsResultPathTemplate,
|
|
3884
|
+
lifecycle_health_status: "healthy",
|
|
3885
|
+
lifecycle_health_reason: undefined,
|
|
3886
|
+
lifecycle_health_message: undefined,
|
|
3887
|
+
lifecycle_health_updated_at: new Date().toISOString(),
|
|
3659
3888
|
ollama_base_url: this.config.ollamaBaseUrl,
|
|
3660
3889
|
discovery_mode: this.config.discoveryMode,
|
|
3661
3890
|
mcoda_bin: this.config.mcodaBin,
|
|
@@ -3708,7 +3937,21 @@ export class SelfHostedNodeRuntime {
|
|
|
3708
3937
|
if (mapped.health_status && mapped.health_status !== "healthy" && mapped.health_status !== "unknown") {
|
|
3709
3938
|
throw new SelfHostedPreStartJobError("selected_agent_unhealthy", `selected local mcoda agent ${selected} is ${mapped.health_status}`);
|
|
3710
3939
|
}
|
|
3711
|
-
|
|
3940
|
+
const codaliAgent = mapMcodaAgentToCodaliAgent(agent, selected);
|
|
3941
|
+
if (!codaliAgent.apiKey && mcodaAgentRequiresApiKey(agent, codaliAgent)) {
|
|
3942
|
+
let apiKey;
|
|
3943
|
+
try {
|
|
3944
|
+
apiKey = optionalText(await this.mcodaAgentAuthResolver(agent)) || undefined;
|
|
3945
|
+
}
|
|
3946
|
+
catch {
|
|
3947
|
+
throw new SelfHostedPreStartJobError("selected_agent_auth_unavailable", `selected local mcoda agent ${selected} auth could not be loaded`);
|
|
3948
|
+
}
|
|
3949
|
+
if (!apiKey) {
|
|
3950
|
+
throw new SelfHostedPreStartJobError("selected_agent_auth_unavailable", `selected local mcoda agent ${selected} requires an API key; run "mcoda agent auth set ${selected}" on this node`);
|
|
3951
|
+
}
|
|
3952
|
+
codaliAgent.apiKey = apiKey;
|
|
3953
|
+
}
|
|
3954
|
+
return codaliAgent;
|
|
3712
3955
|
}
|
|
3713
3956
|
async executeGenericJob(envelope, options = {}) {
|
|
3714
3957
|
const startedAt = Date.now();
|
|
@@ -3966,6 +4209,8 @@ export class SelfHostedNodeRuntime {
|
|
|
3966
4209
|
]
|
|
3967
4210
|
});
|
|
3968
4211
|
}
|
|
4212
|
+
const promptTokens = positiveInteger(ollamaResult.promptTokens);
|
|
4213
|
+
const completionTokens = positiveInteger(ollamaResult.completionTokens);
|
|
3969
4214
|
const invocationResult = {
|
|
3970
4215
|
job_id: job.job_id,
|
|
3971
4216
|
request_id: job.request_id,
|
|
@@ -3974,10 +4219,11 @@ export class SelfHostedNodeRuntime {
|
|
|
3974
4219
|
requestId: job.request_id,
|
|
3975
4220
|
model: job.openai_request.model,
|
|
3976
4221
|
content: ollamaResult.content,
|
|
3977
|
-
promptTokens
|
|
3978
|
-
completionTokens
|
|
4222
|
+
promptTokens,
|
|
4223
|
+
completionTokens,
|
|
3979
4224
|
metadata: { provider: "ollama", raw: ollamaResult.raw }
|
|
3980
4225
|
}),
|
|
4226
|
+
usage: usageFromTokenCounts(promptTokens, completionTokens),
|
|
3981
4227
|
...(streamEvents.length ? { stream_events: streamEvents } : {}),
|
|
3982
4228
|
...(progressEvents.length ? { progress_events: progressEvents } : {}),
|
|
3983
4229
|
timing: { local_latency_ms: Date.now() - startedAt }
|
|
@@ -4058,6 +4304,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4058
4304
|
mode: response.metadata.mode
|
|
4059
4305
|
}
|
|
4060
4306
|
}),
|
|
4307
|
+
usage: usageFromTokenCounts(tokens.promptTokens, tokens.completionTokens),
|
|
4061
4308
|
...(streamEvents.length ? { stream_events: streamEvents } : {}),
|
|
4062
4309
|
...(progressEvents.length ? { progress_events: progressEvents } : {}),
|
|
4063
4310
|
timing: { local_latency_ms: Date.now() - startedAt }
|
|
@@ -4124,6 +4371,13 @@ export class SelfHostedNodeRuntime {
|
|
|
4124
4371
|
models = [];
|
|
4125
4372
|
version = null;
|
|
4126
4373
|
}
|
|
4374
|
+
const lifecycleState = await this.readLifecycleState();
|
|
4375
|
+
const lifecycleProtocolDegraded = this.isLifecycleProtocolDegradedState(lifecycleState);
|
|
4376
|
+
if (lifecycleProtocolDegraded) {
|
|
4377
|
+
status = "degraded";
|
|
4378
|
+
recentFailureCount = Math.max(recentFailureCount, 1);
|
|
4379
|
+
this.lifecyclePollingDisabled = true;
|
|
4380
|
+
}
|
|
4127
4381
|
const discoveryLatencyMs = Date.now() - startedAt;
|
|
4128
4382
|
const capabilityPayload = await this.buildCapabilityHeartbeatPayload(enrollment.runtimeToken);
|
|
4129
4383
|
const loadTelemetry = this.buildLoadTelemetry({
|
|
@@ -4165,7 +4419,17 @@ export class SelfHostedNodeRuntime {
|
|
|
4165
4419
|
relay_mode: this.config.relayMode || "outbound",
|
|
4166
4420
|
load_reporting_enabled: loadReportingEnabled,
|
|
4167
4421
|
hardware_telemetry_enabled: this.config.hardwareTelemetryEnabled === true,
|
|
4168
|
-
drain_mode: this.config.drainMode === true
|
|
4422
|
+
drain_mode: this.config.drainMode === true,
|
|
4423
|
+
...(lifecycleProtocolDegraded
|
|
4424
|
+
? {
|
|
4425
|
+
lifecycle: {
|
|
4426
|
+
status: "degraded",
|
|
4427
|
+
reason: lifecycleState.lifecycle_health_reason,
|
|
4428
|
+
message: lifecycleState.lifecycle_health_message,
|
|
4429
|
+
updated_at: lifecycleState.lifecycle_health_updated_at
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
: {})
|
|
4169
4433
|
},
|
|
4170
4434
|
discovery: {
|
|
4171
4435
|
source: discoverySource,
|
|
@@ -4185,7 +4449,14 @@ export class SelfHostedNodeRuntime {
|
|
|
4185
4449
|
avg_latency_ms: loadTelemetry.avg_latency_ms ?? discoveryLatencyMs,
|
|
4186
4450
|
recent_failure_count: loadTelemetry.recent_failure_count,
|
|
4187
4451
|
recent_failures: loadTelemetry.recent_failures,
|
|
4188
|
-
last_success_at: status === "online" ? new Date().toISOString() : null
|
|
4452
|
+
last_success_at: status === "online" ? new Date().toISOString() : null,
|
|
4453
|
+
...(lifecycleProtocolDegraded
|
|
4454
|
+
? {
|
|
4455
|
+
lifecycle_status: "degraded",
|
|
4456
|
+
lifecycle_reason: lifecycleState.lifecycle_health_reason,
|
|
4457
|
+
lifecycle_message: lifecycleState.lifecycle_health_message
|
|
4458
|
+
}
|
|
4459
|
+
: {})
|
|
4189
4460
|
},
|
|
4190
4461
|
local_agent_catalog: {
|
|
4191
4462
|
revision: loadTelemetry.catalog_fingerprint,
|
|
@@ -4239,20 +4510,36 @@ export class SelfHostedNodeRuntime {
|
|
|
4239
4510
|
return { count: models.filter((model) => model.exposed !== false).length, response };
|
|
4240
4511
|
}
|
|
4241
4512
|
async pollAndExecuteJob(waitMs = DEFAULT_JOB_POLL_WAIT_MS) {
|
|
4513
|
+
const lifecycleState = await this.readLifecycleState();
|
|
4514
|
+
if (this.lifecyclePollingDisabled || this.isLifecycleProtocolDegradedState(lifecycleState)) {
|
|
4515
|
+
this.lifecyclePollingDisabled = true;
|
|
4516
|
+
return { executed: false, status: "failed" };
|
|
4517
|
+
}
|
|
4242
4518
|
const enrollment = await this.ensureEnrolled();
|
|
4243
4519
|
const pollCapacity = this.buildLoadTelemetry({ models: [] });
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4520
|
+
let response;
|
|
4521
|
+
try {
|
|
4522
|
+
response = await this.gateway.pollJob(enrollment.runtimeToken, {
|
|
4523
|
+
node_id: this.config.nodeId,
|
|
4524
|
+
capacity: {
|
|
4525
|
+
active_jobs: pollCapacity.active_jobs,
|
|
4526
|
+
queued_jobs: pollCapacity.queued_jobs,
|
|
4527
|
+
max_jobs: pollCapacity.max_concurrency,
|
|
4528
|
+
max_concurrency: pollCapacity.max_concurrency,
|
|
4529
|
+
free_slots: pollCapacity.free_slots,
|
|
4530
|
+
drain_mode: pollCapacity.drain_mode
|
|
4531
|
+
},
|
|
4532
|
+
wait_ms: waitMs
|
|
4533
|
+
});
|
|
4534
|
+
}
|
|
4535
|
+
catch (error) {
|
|
4536
|
+
const mismatch = this.lifecycleProtocolMismatch("poll", error);
|
|
4537
|
+
if (mismatch) {
|
|
4538
|
+
await this.markLifecycleProtocolDegraded(mismatch);
|
|
4539
|
+
return { executed: false, status: "failed" };
|
|
4540
|
+
}
|
|
4541
|
+
throw error;
|
|
4542
|
+
}
|
|
4256
4543
|
const job = response.job || null;
|
|
4257
4544
|
if (!job) {
|
|
4258
4545
|
return { executed: false };
|
|
@@ -4272,19 +4559,35 @@ export class SelfHostedNodeRuntime {
|
|
|
4272
4559
|
});
|
|
4273
4560
|
forwardedStreamEventCount += stream_events.length;
|
|
4274
4561
|
}
|
|
4275
|
-
catch {
|
|
4562
|
+
catch (error) {
|
|
4563
|
+
const mismatch = this.lifecycleProtocolMismatch("events", error);
|
|
4564
|
+
if (mismatch) {
|
|
4565
|
+
streamEventForwardingFailed = true;
|
|
4566
|
+
await this.markLifecycleProtocolDegraded(mismatch);
|
|
4567
|
+
throw mismatch;
|
|
4568
|
+
}
|
|
4276
4569
|
streamEventForwardingFailed = true;
|
|
4277
4570
|
}
|
|
4278
4571
|
};
|
|
4279
|
-
|
|
4572
|
+
let result = await this.executeJob(job, {
|
|
4280
4573
|
attachedMswarmApiKey: optionalText(response.attached_mswarm_api_key) || undefined,
|
|
4281
4574
|
onStarted: async (event) => {
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4575
|
+
try {
|
|
4576
|
+
await this.gateway.postJobStart(enrollment.runtimeToken, job.job_id, {
|
|
4577
|
+
node_id: this.config.nodeId,
|
|
4578
|
+
agent_slug: event.agent_slug || job.agent_slug,
|
|
4579
|
+
source_agent_slug: event.source_agent_slug || job.source_agent_slug || null,
|
|
4580
|
+
model: event.model || job.model || job.openai_request.model
|
|
4581
|
+
});
|
|
4582
|
+
}
|
|
4583
|
+
catch (error) {
|
|
4584
|
+
const mismatch = this.lifecycleProtocolMismatch("start", error);
|
|
4585
|
+
if (mismatch) {
|
|
4586
|
+
await this.markLifecycleProtocolDegraded(mismatch);
|
|
4587
|
+
throw mismatch;
|
|
4588
|
+
}
|
|
4589
|
+
throw error;
|
|
4590
|
+
}
|
|
4288
4591
|
},
|
|
4289
4592
|
onOpenAIChunk: async (chunk) => {
|
|
4290
4593
|
if (job.openai_request.stream !== true || streamEventForwardingFailed) {
|
|
@@ -4296,7 +4599,23 @@ export class SelfHostedNodeRuntime {
|
|
|
4296
4599
|
}
|
|
4297
4600
|
},
|
|
4298
4601
|
});
|
|
4299
|
-
|
|
4602
|
+
try {
|
|
4603
|
+
await flushStreamEvents();
|
|
4604
|
+
}
|
|
4605
|
+
catch (error) {
|
|
4606
|
+
if (!(error instanceof SelfHostedProtocolMismatchError)) {
|
|
4607
|
+
throw error;
|
|
4608
|
+
}
|
|
4609
|
+
result = {
|
|
4610
|
+
job_id: job.job_id,
|
|
4611
|
+
request_id: job.request_id,
|
|
4612
|
+
status: "failed",
|
|
4613
|
+
error: { code: SELF_HOSTED_PROTOCOL_MISMATCH_CODE, message: error.message },
|
|
4614
|
+
...(result.stream_events?.length ? { stream_events: result.stream_events } : {}),
|
|
4615
|
+
...(result.progress_events?.length ? { progress_events: result.progress_events } : {}),
|
|
4616
|
+
timing: result.timing
|
|
4617
|
+
};
|
|
4618
|
+
}
|
|
4300
4619
|
const postedResult = job.openai_request.stream === true
|
|
4301
4620
|
? streamEventForwardingFailed
|
|
4302
4621
|
? {
|
|
@@ -4305,10 +4624,17 @@ export class SelfHostedNodeRuntime {
|
|
|
4305
4624
|
}
|
|
4306
4625
|
: (({ stream_events: _streamEvents, ...rest }) => rest)(result)
|
|
4307
4626
|
: result;
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4627
|
+
try {
|
|
4628
|
+
await this.gateway.postJobResult(enrollment.runtimeToken, job.job_id, this.jobResultPayload(postedResult));
|
|
4629
|
+
}
|
|
4630
|
+
catch (error) {
|
|
4631
|
+
const mismatch = this.lifecycleProtocolMismatch("result", error);
|
|
4632
|
+
if (mismatch) {
|
|
4633
|
+
await this.markLifecycleProtocolDegraded(mismatch);
|
|
4634
|
+
return { executed: true, job_id: job.job_id, status: "failed" };
|
|
4635
|
+
}
|
|
4636
|
+
throw error;
|
|
4637
|
+
}
|
|
4312
4638
|
return { executed: true, job_id: job.job_id, status: result.status };
|
|
4313
4639
|
}
|
|
4314
4640
|
async doctor() {
|
|
@@ -4350,7 +4676,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4350
4676
|
let timer = null;
|
|
4351
4677
|
let polling = false;
|
|
4352
4678
|
const poll = () => {
|
|
4353
|
-
if (stopped || polling || this.config.relayMode === "direct")
|
|
4679
|
+
if (stopped || polling || this.lifecyclePollingDisabled || this.config.relayMode === "direct")
|
|
4354
4680
|
return;
|
|
4355
4681
|
polling = true;
|
|
4356
4682
|
void this.pollAndExecuteJob()
|