@mcoda/mswarm 0.1.101 → 0.1.103
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 +1 -1
- package/dist/codali-executor.d.ts.map +1 -1
- package/dist/codali-executor.js.map +1 -1
- package/dist/invocation-token.d.ts +2 -0
- package/dist/invocation-token.d.ts.map +1 -1
- package/dist/invocation-token.js +10 -0
- package/dist/invocation-token.js.map +1 -1
- package/dist/runtime.d.ts +59 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +754 -28
- package/dist/runtime.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -2
- package/dist/server.js.map +1 -1
- package/package.json +6 -3
- package/scripts/stable-audio-3-openai-server.py +738 -0
- package/scripts/wan22-openai-server.py +901 -0
package/dist/runtime.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
|
6
6
|
import { isIP } from "node:net";
|
|
7
7
|
import { GlobalRepository } from "@mcoda/db";
|
|
8
8
|
import { MswarmCodaliExecutor } from "./codali-executor.js";
|
|
9
|
-
import { MSWARM_CAPABILITY_SCHEMA_VERSION, CryptoHelper, assertMswarmSafeRelativePath, validateMswarmArchiveEntry, buildMswarmCapabilityNames, buildMswarmPrivateCapabilityCatalogEntry, buildMswarmLocalArtifactUri, buildMswarmSandboxProfile, defaultMswarmArtifactAccessPolicy, defaultMswarmArtifactRetentionPolicy, projectMswarmPublicCapabilities, validateMswarmGenericJobRequest } from "@mcoda/shared";
|
|
9
|
+
import { GENERATIVE_OPERATION_DEFAULT_PATHS, GENERATIVE_REQUEST_PARAMETER_ALLOWLISTS, MSWARM_CAPABILITY_SCHEMA_VERSION, CryptoHelper, assertMswarmSafeRelativePath, validateMswarmArchiveEntry, buildMswarmCapabilityNames, buildMswarmPrivateCapabilityCatalogEntry, buildMswarmLocalArtifactUri, buildMswarmSandboxProfile, defaultMswarmArtifactAccessPolicy, defaultMswarmArtifactRetentionPolicy, normalizeGenerativeModalities, normalizeSelfHostedGenerativeOperation, projectMswarmPublicCapabilities, validateMswarmGenericJobRequest } from "@mcoda/shared";
|
|
10
10
|
const REGISTRATION_POLL_INTERVAL_MS = 5000;
|
|
11
11
|
const REGISTRATION_POLL_TIMEOUT_MS = 30 * 60000;
|
|
12
12
|
/**
|
|
@@ -197,6 +197,17 @@ const SECRET_LOCAL_RUNNER_HEADER_KEYS = new Set(["authorization", "proxy-authori
|
|
|
197
197
|
const RESERVED_LOCAL_RUNNER_EXTRA_BODY_KEYS = new Set([
|
|
198
198
|
"model",
|
|
199
199
|
"messages",
|
|
200
|
+
"prompt",
|
|
201
|
+
"n",
|
|
202
|
+
"size",
|
|
203
|
+
"seed",
|
|
204
|
+
"steps",
|
|
205
|
+
"negative_prompt",
|
|
206
|
+
"duration_seconds",
|
|
207
|
+
"sample_rate",
|
|
208
|
+
"fps",
|
|
209
|
+
"video_frames",
|
|
210
|
+
"high_noise_steps",
|
|
200
211
|
"stream",
|
|
201
212
|
"tools",
|
|
202
213
|
"tool_choice",
|
|
@@ -204,6 +215,20 @@ const RESERVED_LOCAL_RUNNER_EXTRA_BODY_KEYS = new Set([
|
|
|
204
215
|
"max_tokens",
|
|
205
216
|
"temperature"
|
|
206
217
|
]);
|
|
218
|
+
const REQUIRED_GENERATIVE_OPERATION_REQUEST_PARAMETERS = {
|
|
219
|
+
"chat.completions": ["model", "messages"],
|
|
220
|
+
"images.generations": ["model", "prompt", "response_format"],
|
|
221
|
+
"audio.generations": ["model", "prompt", "response_format"],
|
|
222
|
+
"videos.generations": ["model", "prompt", "response_format"]
|
|
223
|
+
};
|
|
224
|
+
const AUDIO_GENERATION_RESPONSE_FORMATS = new Set(["wav", "flac", "mp3", "ogg"]);
|
|
225
|
+
const VIDEO_GENERATION_RESPONSE_FORMATS = new Set(["webm", "webp", "avi"]);
|
|
226
|
+
const STABLE_DIFFUSION_CPP_EXTRA_ARGS_TAG_PATTERN = /<sd_cpp_extra_args\b[^>]*>[\s\S]*?<\/sd_cpp_extra_args\s*>/gi;
|
|
227
|
+
const STABLE_DIFFUSION_CPP_EXTRA_ARGS_TAG_FRAGMENT_PATTERN = /<\/?sd_cpp_extra_args\b[^>]*>/gi;
|
|
228
|
+
const DEFAULT_IMAGE_GENERATIVE_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
229
|
+
const DEFAULT_AUDIO_GENERATIVE_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
230
|
+
const DEFAULT_VIDEO_GENERATIVE_OUTPUT_BYTES = 256 * 1024 * 1024;
|
|
231
|
+
const GENERATIVE_RESPONSE_JSON_OVERHEAD_BYTES = 64 * 1024;
|
|
207
232
|
const LOCAL_RUNNER_KIND_ALIASES = {
|
|
208
233
|
vllm: "vllm",
|
|
209
234
|
"llama-cpp": "llama-cpp",
|
|
@@ -226,6 +251,13 @@ const LOCAL_RUNNER_KIND_ALIASES = {
|
|
|
226
251
|
text_generation_inference: "tgi",
|
|
227
252
|
custom: "custom"
|
|
228
253
|
};
|
|
254
|
+
const GENERATIVE_RUNNER_KIND_ALIASES = {
|
|
255
|
+
"stable-diffusion-cpp": "stable-diffusion-cpp",
|
|
256
|
+
"stable-diffusion.cpp": "stable-diffusion-cpp",
|
|
257
|
+
stable_diffusion_cpp: "stable-diffusion-cpp",
|
|
258
|
+
"sd-cpp": "stable-diffusion-cpp",
|
|
259
|
+
"sd.cpp": "stable-diffusion-cpp"
|
|
260
|
+
};
|
|
229
261
|
const LOCAL_RUNNER_AUTH_MODE_ALIASES = {
|
|
230
262
|
none: "none",
|
|
231
263
|
bearer: "bearer",
|
|
@@ -254,6 +286,189 @@ function optionalText(value) {
|
|
|
254
286
|
function isRecord(value) {
|
|
255
287
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
256
288
|
}
|
|
289
|
+
export function normalizeSelfHostedGenerativeOperationName(value) {
|
|
290
|
+
const normalized = optionalText(value);
|
|
291
|
+
return normalized === "chat.completions" ||
|
|
292
|
+
normalized === "images.generations" ||
|
|
293
|
+
normalized === "audio.generations" ||
|
|
294
|
+
normalized === "videos.generations"
|
|
295
|
+
? normalized
|
|
296
|
+
: null;
|
|
297
|
+
}
|
|
298
|
+
export function resolveSelfHostedInvocationOperation(value) {
|
|
299
|
+
const raw = optionalText(value);
|
|
300
|
+
if (!raw) {
|
|
301
|
+
return "chat.completions";
|
|
302
|
+
}
|
|
303
|
+
const operation = normalizeSelfHostedGenerativeOperationName(raw);
|
|
304
|
+
if (!operation) {
|
|
305
|
+
throw new Error(`unsupported self-hosted operation ${raw}`);
|
|
306
|
+
}
|
|
307
|
+
return operation;
|
|
308
|
+
}
|
|
309
|
+
function normalizeGenerativeOperationDescriptor(value) {
|
|
310
|
+
const record = isRecord(value)
|
|
311
|
+
? {
|
|
312
|
+
...value,
|
|
313
|
+
operation: value.operation ?? value.type,
|
|
314
|
+
requestParameterAllowlist: value.requestParameterAllowlist ??
|
|
315
|
+
value.request_parameter_allowlist ??
|
|
316
|
+
value.supported_parameters,
|
|
317
|
+
responseFormats: value.responseFormats ?? value.response_formats,
|
|
318
|
+
outputMimeTypes: value.outputMimeTypes ?? value.output_mime_types
|
|
319
|
+
}
|
|
320
|
+
: value;
|
|
321
|
+
const normalized = normalizeSelfHostedGenerativeOperation(record);
|
|
322
|
+
if (!normalized)
|
|
323
|
+
return null;
|
|
324
|
+
const operation = normalized.operation;
|
|
325
|
+
const requestParameterAllowlist = Array.from(new Set([
|
|
326
|
+
...REQUIRED_GENERATIVE_OPERATION_REQUEST_PARAMETERS[operation],
|
|
327
|
+
...(normalized.requestParameterAllowlist ??
|
|
328
|
+
GENERATIVE_REQUEST_PARAMETER_ALLOWLISTS[operation])
|
|
329
|
+
]));
|
|
330
|
+
const responseFormats = normalized.responseFormats?.length
|
|
331
|
+
? normalized.responseFormats
|
|
332
|
+
: operation === "chat.completions"
|
|
333
|
+
? undefined
|
|
334
|
+
: operation === "images.generations"
|
|
335
|
+
? ["b64_json"]
|
|
336
|
+
: operation === "audio.generations"
|
|
337
|
+
? ["wav", "flac", "mp3", "ogg"]
|
|
338
|
+
: ["webm", "webp", "avi"];
|
|
339
|
+
return {
|
|
340
|
+
operation,
|
|
341
|
+
path: normalized.path,
|
|
342
|
+
method: "POST",
|
|
343
|
+
requestParameterAllowlist,
|
|
344
|
+
...(responseFormats?.length ? { responseFormats } : {}),
|
|
345
|
+
...(normalized.outputMimeTypes?.length ? { outputMimeTypes: normalized.outputMimeTypes } : {}),
|
|
346
|
+
...(normalized.limits ? { limits: normalized.limits } : {})
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function defaultGenerativeOperationDescriptor(operation, path = GENERATIVE_OPERATION_DEFAULT_PATHS[operation]) {
|
|
350
|
+
return {
|
|
351
|
+
operation,
|
|
352
|
+
path,
|
|
353
|
+
method: "POST",
|
|
354
|
+
requestParameterAllowlist: [...GENERATIVE_REQUEST_PARAMETER_ALLOWLISTS[operation]],
|
|
355
|
+
...(operation === "images.generations"
|
|
356
|
+
? { responseFormats: ["b64_json"] }
|
|
357
|
+
: operation === "audio.generations"
|
|
358
|
+
? { responseFormats: ["wav", "flac", "mp3", "ogg"] }
|
|
359
|
+
: operation === "videos.generations"
|
|
360
|
+
? { responseFormats: ["webm", "webp", "avi"] }
|
|
361
|
+
: {})
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function normalizeGenerativeOperations(value) {
|
|
365
|
+
if (value === undefined)
|
|
366
|
+
return undefined;
|
|
367
|
+
if (!Array.isArray(value))
|
|
368
|
+
return [];
|
|
369
|
+
const operations = value
|
|
370
|
+
.map((entry) => normalizeGenerativeOperationDescriptor(entry))
|
|
371
|
+
.filter((entry) => Boolean(entry));
|
|
372
|
+
const deduplicated = new Map();
|
|
373
|
+
for (const operation of operations) {
|
|
374
|
+
if (!deduplicated.has(operation.operation)) {
|
|
375
|
+
deduplicated.set(operation.operation, operation);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return Array.from(deduplicated.values());
|
|
379
|
+
}
|
|
380
|
+
function inferGenerativeModalities(operations, direction) {
|
|
381
|
+
const modalities = new Set();
|
|
382
|
+
for (const descriptor of operations) {
|
|
383
|
+
if (direction === "input" || descriptor.operation === "chat.completions") {
|
|
384
|
+
modalities.add("text");
|
|
385
|
+
}
|
|
386
|
+
if (direction === "output" && descriptor.operation === "images.generations") {
|
|
387
|
+
modalities.add("image");
|
|
388
|
+
}
|
|
389
|
+
if (direction === "output" && descriptor.operation === "audio.generations") {
|
|
390
|
+
modalities.add("audio");
|
|
391
|
+
}
|
|
392
|
+
if (direction === "output" && descriptor.operation === "videos.generations") {
|
|
393
|
+
modalities.add("video");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return Array.from(modalities);
|
|
397
|
+
}
|
|
398
|
+
function generativeOperationCatalogInput(descriptor) {
|
|
399
|
+
const limits = descriptor.limits
|
|
400
|
+
? {
|
|
401
|
+
...(descriptor.limits.maxRequestBytes !== undefined
|
|
402
|
+
? { max_request_bytes: descriptor.limits.maxRequestBytes }
|
|
403
|
+
: {}),
|
|
404
|
+
...(descriptor.limits.maxOutputBytes !== undefined
|
|
405
|
+
? { max_output_bytes: descriptor.limits.maxOutputBytes }
|
|
406
|
+
: {}),
|
|
407
|
+
...(descriptor.limits.maxPromptChars !== undefined
|
|
408
|
+
? { max_prompt_chars: descriptor.limits.maxPromptChars }
|
|
409
|
+
: {}),
|
|
410
|
+
...(descriptor.limits.maxNegativePromptChars !== undefined
|
|
411
|
+
? { max_negative_prompt_chars: descriptor.limits.maxNegativePromptChars }
|
|
412
|
+
: {}),
|
|
413
|
+
...(descriptor.limits.maxN !== undefined ? { max_n: descriptor.limits.maxN } : {}),
|
|
414
|
+
...(descriptor.limits.minWidth !== undefined
|
|
415
|
+
? { min_width: descriptor.limits.minWidth }
|
|
416
|
+
: {}),
|
|
417
|
+
...(descriptor.limits.maxWidth !== undefined
|
|
418
|
+
? { max_width: descriptor.limits.maxWidth }
|
|
419
|
+
: {}),
|
|
420
|
+
...(descriptor.limits.minHeight !== undefined
|
|
421
|
+
? { min_height: descriptor.limits.minHeight }
|
|
422
|
+
: {}),
|
|
423
|
+
...(descriptor.limits.maxHeight !== undefined
|
|
424
|
+
? { max_height: descriptor.limits.maxHeight }
|
|
425
|
+
: {}),
|
|
426
|
+
...(descriptor.limits.maxPixels !== undefined
|
|
427
|
+
? { max_pixels: descriptor.limits.maxPixels }
|
|
428
|
+
: {}),
|
|
429
|
+
...(descriptor.limits.minDurationSeconds !== undefined
|
|
430
|
+
? { min_duration_seconds: descriptor.limits.minDurationSeconds }
|
|
431
|
+
: {}),
|
|
432
|
+
...(descriptor.limits.maxDurationSeconds !== undefined
|
|
433
|
+
? { max_duration_seconds: descriptor.limits.maxDurationSeconds }
|
|
434
|
+
: {}),
|
|
435
|
+
...(descriptor.limits.maxSampleRate !== undefined
|
|
436
|
+
? { max_sample_rate: descriptor.limits.maxSampleRate }
|
|
437
|
+
: {}),
|
|
438
|
+
...(descriptor.limits.minFps !== undefined
|
|
439
|
+
? { min_fps: descriptor.limits.minFps }
|
|
440
|
+
: {}),
|
|
441
|
+
...(descriptor.limits.maxFps !== undefined
|
|
442
|
+
? { max_fps: descriptor.limits.maxFps }
|
|
443
|
+
: {}),
|
|
444
|
+
...(descriptor.limits.minVideoFrames !== undefined
|
|
445
|
+
? { min_video_frames: descriptor.limits.minVideoFrames }
|
|
446
|
+
: {}),
|
|
447
|
+
...(descriptor.limits.maxVideoFrames !== undefined
|
|
448
|
+
? { max_video_frames: descriptor.limits.maxVideoFrames }
|
|
449
|
+
: {}),
|
|
450
|
+
...(descriptor.limits.maxSteps !== undefined
|
|
451
|
+
? { max_steps: descriptor.limits.maxSteps }
|
|
452
|
+
: {}),
|
|
453
|
+
...(descriptor.limits.maxHighNoiseSteps !== undefined
|
|
454
|
+
? { max_high_noise_steps: descriptor.limits.maxHighNoiseSteps }
|
|
455
|
+
: {})
|
|
456
|
+
}
|
|
457
|
+
: undefined;
|
|
458
|
+
return {
|
|
459
|
+
type: descriptor.operation,
|
|
460
|
+
path: descriptor.path,
|
|
461
|
+
method: descriptor.method,
|
|
462
|
+
supported_parameters: [...descriptor.requestParameterAllowlist],
|
|
463
|
+
...(descriptor.responseFormats?.length
|
|
464
|
+
? { response_formats: [...descriptor.responseFormats] }
|
|
465
|
+
: {}),
|
|
466
|
+
...(descriptor.outputMimeTypes?.length
|
|
467
|
+
? { output_mime_types: [...descriptor.outputMimeTypes] }
|
|
468
|
+
: {}),
|
|
469
|
+
...(limits ? { limits } : {})
|
|
470
|
+
};
|
|
471
|
+
}
|
|
257
472
|
function normalizeLookupKey(value) {
|
|
258
473
|
return value.trim().toLowerCase();
|
|
259
474
|
}
|
|
@@ -316,6 +531,10 @@ function normalizeLocalRunnerKind(value) {
|
|
|
316
531
|
const normalized = optionalText(value);
|
|
317
532
|
return normalized ? LOCAL_RUNNER_KIND_ALIASES[normalizeLookupKey(normalized)] : undefined;
|
|
318
533
|
}
|
|
534
|
+
function normalizeGenerativeRunnerKind(value) {
|
|
535
|
+
const normalized = optionalText(value);
|
|
536
|
+
return normalized ? GENERATIVE_RUNNER_KIND_ALIASES[normalizeLookupKey(normalized)] : undefined;
|
|
537
|
+
}
|
|
319
538
|
function normalizeLocalRunnerAuthMode(value) {
|
|
320
539
|
const normalized = optionalText(value);
|
|
321
540
|
return normalized ? LOCAL_RUNNER_AUTH_MODE_ALIASES[normalizeLookupKey(normalized)] : undefined;
|
|
@@ -332,11 +551,14 @@ function normalizeMcodaLocalRunnerConfig(adapter, config) {
|
|
|
332
551
|
const merged = { ...localRunnerRecord, ...configRecord };
|
|
333
552
|
const authMode = normalizeLocalRunnerAuthMode(merged.authMode) ?? "none";
|
|
334
553
|
const dummyBearerToken = readConfigString(merged, ["dummyBearerToken", "dummyApiKey"]);
|
|
554
|
+
const generativeRunnerKind = normalizeGenerativeRunnerKind(merged.runnerKind ?? merged.runner_kind);
|
|
335
555
|
return {
|
|
336
556
|
baseUrl: readConfigString(merged, ["baseUrl", "base_url", "endpoint", "apiBaseUrl", "api_base_url"]),
|
|
337
557
|
endpoint: readConfigString(merged, ["endpoint"]),
|
|
338
558
|
apiBaseUrl: readConfigString(merged, ["apiBaseUrl", "api_base_url"]),
|
|
339
|
-
runnerKind: normalizeLocalRunnerKind(merged.runnerKind
|
|
559
|
+
runnerKind: normalizeLocalRunnerKind(merged.runnerKind ?? merged.runner_kind) ??
|
|
560
|
+
(generativeRunnerKind ? "custom" : defaultLocalRunnerKindForAdapter(adapter)),
|
|
561
|
+
generativeRunnerKind,
|
|
340
562
|
authMode,
|
|
341
563
|
dummyBearerToken: authMode === "dummy-bearer" ? dummyBearerToken ?? "local" : dummyBearerToken,
|
|
342
564
|
headers: normalizeStringMap(merged.headers),
|
|
@@ -348,7 +570,11 @@ function normalizeMcodaLocalRunnerConfig(adapter, config) {
|
|
|
348
570
|
supportsStreaming: readConfigBoolean(merged, ["supportsStreaming", "supports_streaming"]),
|
|
349
571
|
supportsTools: readConfigBoolean(merged, ["supportsTools", "supports_tools"]),
|
|
350
572
|
supportsJsonSchema: readConfigBoolean(merged, ["supportsJsonSchema", "supports_json_schema"]),
|
|
351
|
-
supportsGbnf: readConfigBoolean(merged, ["supportsGbnf", "supports_gbnf"])
|
|
573
|
+
supportsGbnf: readConfigBoolean(merged, ["supportsGbnf", "supports_gbnf"]),
|
|
574
|
+
inputModalities: normalizeGenerativeModalities(merged.inputModalities ?? merged.input_modalities),
|
|
575
|
+
outputModalities: normalizeGenerativeModalities(merged.outputModalities ?? merged.output_modalities),
|
|
576
|
+
operations: normalizeGenerativeOperations(merged.operations),
|
|
577
|
+
publicModelId: readConfigString(merged, ["publicModelId", "public_model_id"])
|
|
352
578
|
};
|
|
353
579
|
}
|
|
354
580
|
function parsePositiveInteger(value, fallback) {
|
|
@@ -681,8 +907,23 @@ function buildCatalogFingerprint(models) {
|
|
|
681
907
|
adapter: optionalText(model.adapter) || null,
|
|
682
908
|
source_agent_slug: optionalText(model.source_agent_slug) || null,
|
|
683
909
|
model_id: optionalText(model.model_id) || optionalText(model.model) || null,
|
|
910
|
+
public_model_id: optionalText(model.public_model_id) || null,
|
|
911
|
+
upstream_model: optionalText(model.upstream_model) || optionalText(model.model) || null,
|
|
684
912
|
exposed: model.exposed !== false,
|
|
685
913
|
capabilities: normalizeCapabilities(model.capabilities).sort(),
|
|
914
|
+
input_modalities: [...(model.input_modalities ?? [])].sort(),
|
|
915
|
+
output_modalities: [...(model.output_modalities ?? [])].sort(),
|
|
916
|
+
operations: (model.operations ?? [])
|
|
917
|
+
.map((operation) => ({
|
|
918
|
+
type: operation.type,
|
|
919
|
+
path: operation.path,
|
|
920
|
+
method: operation.method,
|
|
921
|
+
supported_parameters: [...operation.supported_parameters].sort(),
|
|
922
|
+
response_formats: [...(operation.response_formats ?? [])].sort(),
|
|
923
|
+
output_mime_types: [...(operation.output_mime_types ?? [])].sort(),
|
|
924
|
+
limits: operation.limits ?? null
|
|
925
|
+
}))
|
|
926
|
+
.sort((left, right) => left.type.localeCompare(right.type)),
|
|
686
927
|
health_status: normalizeHealthStatus(model.health_status)
|
|
687
928
|
}))
|
|
688
929
|
.sort((left, right) => `${left.provider || ""}:${left.name}`.localeCompare(`${right.provider || ""}:${right.name}`));
|
|
@@ -860,6 +1101,69 @@ async function fetchJson(fetchImpl, url, init, timeoutMs) {
|
|
|
860
1101
|
clearTimeout(timeout);
|
|
861
1102
|
}
|
|
862
1103
|
}
|
|
1104
|
+
function generativeOutputByteLimit(operation, configuredLimit) {
|
|
1105
|
+
if (typeof configuredLimit === "number" &&
|
|
1106
|
+
Number.isSafeInteger(configuredLimit) &&
|
|
1107
|
+
configuredLimit > 0) {
|
|
1108
|
+
return configuredLimit;
|
|
1109
|
+
}
|
|
1110
|
+
return operation === "images.generations"
|
|
1111
|
+
? DEFAULT_IMAGE_GENERATIVE_OUTPUT_BYTES
|
|
1112
|
+
: operation === "audio.generations"
|
|
1113
|
+
? DEFAULT_AUDIO_GENERATIVE_OUTPUT_BYTES
|
|
1114
|
+
: DEFAULT_VIDEO_GENERATIVE_OUTPUT_BYTES;
|
|
1115
|
+
}
|
|
1116
|
+
function generativeJsonResponseByteLimit(outputByteLimit) {
|
|
1117
|
+
const encodedLimit = Math.ceil(outputByteLimit / 3) * 4;
|
|
1118
|
+
return Math.min(Number.MAX_SAFE_INTEGER, encodedLimit + GENERATIVE_RESPONSE_JSON_OVERHEAD_BYTES);
|
|
1119
|
+
}
|
|
1120
|
+
async function readBoundedResponseText(response, maxBytes, overflowMessage) {
|
|
1121
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
1122
|
+
if (Number.isFinite(declaredLength) &&
|
|
1123
|
+
declaredLength >= 0 &&
|
|
1124
|
+
declaredLength > maxBytes) {
|
|
1125
|
+
await response.body?.cancel().catch(() => undefined);
|
|
1126
|
+
throw new Error(overflowMessage);
|
|
1127
|
+
}
|
|
1128
|
+
if (!response.body) {
|
|
1129
|
+
return "";
|
|
1130
|
+
}
|
|
1131
|
+
const reader = response.body.getReader();
|
|
1132
|
+
const chunks = [];
|
|
1133
|
+
let totalBytes = 0;
|
|
1134
|
+
try {
|
|
1135
|
+
while (true) {
|
|
1136
|
+
const { done, value } = await reader.read();
|
|
1137
|
+
if (done)
|
|
1138
|
+
break;
|
|
1139
|
+
totalBytes += value.byteLength;
|
|
1140
|
+
if (totalBytes > maxBytes) {
|
|
1141
|
+
await reader.cancel().catch(() => undefined);
|
|
1142
|
+
throw new Error(overflowMessage);
|
|
1143
|
+
}
|
|
1144
|
+
chunks.push(Buffer.from(value));
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
finally {
|
|
1148
|
+
reader.releaseLock();
|
|
1149
|
+
}
|
|
1150
|
+
return Buffer.concat(chunks, totalBytes).toString("utf8");
|
|
1151
|
+
}
|
|
1152
|
+
function base64DecodedByteLength(value) {
|
|
1153
|
+
if (value.length === 0 ||
|
|
1154
|
+
value.length % 4 !== 0 ||
|
|
1155
|
+
!/^[A-Za-z0-9+/]*={0,2}$/.test(value)) {
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
|
|
1159
|
+
return (value.length / 4) * 3 - padding;
|
|
1160
|
+
}
|
|
1161
|
+
function sanitizeStableDiffusionCppExtraArgsTags(value) {
|
|
1162
|
+
return value
|
|
1163
|
+
.replace(STABLE_DIFFUSION_CPP_EXTRA_ARGS_TAG_PATTERN, "")
|
|
1164
|
+
.replace(STABLE_DIFFUSION_CPP_EXTRA_ARGS_TAG_FRAGMENT_PATTERN, "")
|
|
1165
|
+
.trim();
|
|
1166
|
+
}
|
|
863
1167
|
export async function readSelfHostedNodeState(statePath) {
|
|
864
1168
|
try {
|
|
865
1169
|
const content = await readFile(statePath, "utf8");
|
|
@@ -1522,17 +1826,20 @@ export function mapOllamaModelToSelfHostedModel(model, config) {
|
|
|
1522
1826
|
const parameterSize = optionalText(model.details?.parameter_size);
|
|
1523
1827
|
const quantizationLevel = optionalText(model.details?.quantization_level);
|
|
1524
1828
|
const embeddingOnly = isEmbeddingModel(name, family);
|
|
1829
|
+
const operations = [defaultGenerativeOperationDescriptor("chat.completions", "/api/chat")];
|
|
1830
|
+
const supportsVision = isVisionModel(name, family);
|
|
1525
1831
|
return {
|
|
1526
1832
|
name,
|
|
1527
1833
|
provider: "ollama",
|
|
1528
1834
|
adapter: "ollama",
|
|
1529
1835
|
model_id: name,
|
|
1836
|
+
upstream_model: name,
|
|
1530
1837
|
digest: optionalText(model.digest),
|
|
1531
1838
|
family,
|
|
1532
1839
|
parameter_size: parameterSize,
|
|
1533
1840
|
quantization_level: quantizationLevel,
|
|
1534
1841
|
supports_tools: false,
|
|
1535
|
-
supports_vision:
|
|
1842
|
+
supports_vision: supportsVision,
|
|
1536
1843
|
openai_compatible: false,
|
|
1537
1844
|
exposed: isModelExposed(name, family, config),
|
|
1538
1845
|
best_usage: embeddingOnly
|
|
@@ -1544,7 +1851,10 @@ export function mapOllamaModelToSelfHostedModel(model, config) {
|
|
|
1544
1851
|
reasoning_rating: 4,
|
|
1545
1852
|
max_complexity: 3,
|
|
1546
1853
|
health_status: "healthy",
|
|
1547
|
-
metadata_quality: model.details ? "discovered" : "unknown"
|
|
1854
|
+
metadata_quality: model.details ? "discovered" : "unknown",
|
|
1855
|
+
input_modalities: supportsVision ? ["text", "image"] : ["text"],
|
|
1856
|
+
output_modalities: ["text"],
|
|
1857
|
+
operations: operations.map(generativeOperationCatalogInput)
|
|
1548
1858
|
};
|
|
1549
1859
|
}
|
|
1550
1860
|
export function mapMcodaAgentToSelfHostedModel(agent, config) {
|
|
@@ -1568,6 +1878,17 @@ export function mapMcodaAgentToSelfHostedModel(agent, config) {
|
|
|
1568
1878
|
const supportsTools = optionalBoolean(agent.supportsTools, agent.supports_tools, localRunner?.supportsTools) === true;
|
|
1569
1879
|
const openaiCompatible = localRunner !== undefined || optionalBoolean(agent.openaiCompatible, agent.openai_compatible) === true;
|
|
1570
1880
|
const model = defaultModel || slug;
|
|
1881
|
+
const operations = localRunner?.operations ??
|
|
1882
|
+
[defaultGenerativeOperationDescriptor("chat.completions", localRunner ? "/chat/completions" : undefined)];
|
|
1883
|
+
const inferredInputModalities = inferGenerativeModalities(operations, "input");
|
|
1884
|
+
const supportsVision = capabilities.some((capability) => capability.toLowerCase().includes("vision")) ||
|
|
1885
|
+
capabilities.some((capability) => capability.toLowerCase().includes("visual"));
|
|
1886
|
+
const inputModalities = localRunner?.inputModalities ??
|
|
1887
|
+
(supportsVision && operations.some((entry) => entry.operation === "chat.completions")
|
|
1888
|
+
? Array.from(new Set([...inferredInputModalities, "image"]))
|
|
1889
|
+
: inferredInputModalities);
|
|
1890
|
+
const outputModalities = localRunner?.outputModalities ?? inferGenerativeModalities(operations, "output");
|
|
1891
|
+
const publicModelId = localRunner?.publicModelId;
|
|
1571
1892
|
return {
|
|
1572
1893
|
name: slug,
|
|
1573
1894
|
provider: "mcoda",
|
|
@@ -1575,9 +1896,11 @@ export function mapMcodaAgentToSelfHostedModel(agent, config) {
|
|
|
1575
1896
|
source_agent_id: optionalText(agent.id),
|
|
1576
1897
|
source_agent_slug: slug,
|
|
1577
1898
|
model,
|
|
1578
|
-
model_id: model,
|
|
1899
|
+
model_id: publicModelId || model,
|
|
1900
|
+
public_model_id: publicModelId,
|
|
1901
|
+
upstream_model: model,
|
|
1579
1902
|
base_url: localRunner?.baseUrl,
|
|
1580
|
-
runner_kind: localRunner?.runnerKind,
|
|
1903
|
+
runner_kind: localRunner?.generativeRunnerKind ?? localRunner?.runnerKind,
|
|
1581
1904
|
auth_mode: localRunner?.authMode,
|
|
1582
1905
|
response_format_strategy: localRunner?.responseFormatStrategy,
|
|
1583
1906
|
health_path: localRunner?.healthPath,
|
|
@@ -1587,8 +1910,7 @@ export function mapMcodaAgentToSelfHostedModel(agent, config) {
|
|
|
1587
1910
|
max_output_tokens: optionalNumber(agent.maxOutputTokens, agent.max_output_tokens),
|
|
1588
1911
|
supports_tools: supportsTools,
|
|
1589
1912
|
supports_streaming: localRunner?.supportsStreaming,
|
|
1590
|
-
supports_vision:
|
|
1591
|
-
capabilities.some((capability) => capability.toLowerCase().includes("visual")),
|
|
1913
|
+
supports_vision: supportsVision,
|
|
1592
1914
|
supports_json_schema: localRunner?.supportsJsonSchema,
|
|
1593
1915
|
supports_gbnf: localRunner?.supportsGbnf,
|
|
1594
1916
|
openai_compatible: openaiCompatible,
|
|
@@ -1601,7 +1923,10 @@ export function mapMcodaAgentToSelfHostedModel(agent, config) {
|
|
|
1601
1923
|
reasoning_rating: optionalNumber(agent.reasoningRating, agent.reasoning_rating),
|
|
1602
1924
|
max_complexity: optionalNumber(agent.maxComplexity, agent.max_complexity),
|
|
1603
1925
|
health_status: healthStatus,
|
|
1604
|
-
metadata_quality: "discovered"
|
|
1926
|
+
metadata_quality: "discovered",
|
|
1927
|
+
input_modalities: inputModalities,
|
|
1928
|
+
output_modalities: outputModalities,
|
|
1929
|
+
operations: operations.map(generativeOperationCatalogInput)
|
|
1605
1930
|
};
|
|
1606
1931
|
}
|
|
1607
1932
|
function parseMcodaAgentListOutput(stdout) {
|
|
@@ -1905,6 +2230,8 @@ function mapMcodaAgentToCodaliAgent(agent, fallbackSlug) {
|
|
|
1905
2230
|
const localRunner = normalizeMcodaLocalRunnerConfig(adapter, config);
|
|
1906
2231
|
const configAuthMode = normalizeLocalRunnerAuthMode(config?.authMode);
|
|
1907
2232
|
const supportsTools = optionalBoolean(agent.supportsTools, agent.supports_tools, localRunner?.supportsTools) === true;
|
|
2233
|
+
const generativeOperations = localRunner?.operations ??
|
|
2234
|
+
[defaultGenerativeOperationDescriptor("chat.completions", localRunner ? "/chat/completions" : undefined)];
|
|
1908
2235
|
return {
|
|
1909
2236
|
slug: optionalText(agent.slug) || fallbackSlug,
|
|
1910
2237
|
adapter,
|
|
@@ -1918,6 +2245,7 @@ function mapMcodaAgentToCodaliAgent(agent, fallbackSlug) {
|
|
|
1918
2245
|
: configText(config, "apiKey", "api_key"),
|
|
1919
2246
|
localRunner,
|
|
1920
2247
|
runnerKind: localRunner?.runnerKind,
|
|
2248
|
+
generativeRunnerKind: localRunner?.generativeRunnerKind,
|
|
1921
2249
|
authMode: localRunner?.authMode ?? configAuthMode,
|
|
1922
2250
|
dummyBearerToken: localRunner?.dummyBearerToken,
|
|
1923
2251
|
headers: localRunner?.headers,
|
|
@@ -1933,6 +2261,10 @@ function mapMcodaAgentToCodaliAgent(agent, fallbackSlug) {
|
|
|
1933
2261
|
capabilities: normalizeCapabilities(agent.capabilities),
|
|
1934
2262
|
contextWindow: optionalNumber(agent.contextWindow, agent.context_window) ?? undefined,
|
|
1935
2263
|
maxOutputTokens: optionalNumber(agent.maxOutputTokens, agent.max_output_tokens) ?? undefined,
|
|
2264
|
+
generativeOperations,
|
|
2265
|
+
inputModalities: localRunner?.inputModalities ?? inferGenerativeModalities(generativeOperations, "input"),
|
|
2266
|
+
outputModalities: localRunner?.outputModalities ?? inferGenerativeModalities(generativeOperations, "output"),
|
|
2267
|
+
publicModelId: localRunner?.publicModelId
|
|
1936
2268
|
};
|
|
1937
2269
|
}
|
|
1938
2270
|
function buildCodaliWorkspace(job) {
|
|
@@ -1958,6 +2290,7 @@ const PRE_START_JOB_ERROR_CODES = new Set([
|
|
|
1958
2290
|
"selected_agent_unavailable",
|
|
1959
2291
|
"selected_agent_auth_unavailable",
|
|
1960
2292
|
"selected_agent_unhealthy",
|
|
2293
|
+
"unsupported_operation",
|
|
1961
2294
|
"validation_failed",
|
|
1962
2295
|
"docdex_context_missing",
|
|
1963
2296
|
"docdex_api_key_missing",
|
|
@@ -2148,7 +2481,7 @@ function buildCodaliJob(job) {
|
|
|
2148
2481
|
return {
|
|
2149
2482
|
id: optionalText(payload.id) || job.job_id,
|
|
2150
2483
|
jobType,
|
|
2151
|
-
input: payload.input ?? messagesToPrompt(job.openai_request.messages),
|
|
2484
|
+
input: payload.input ?? messagesToPrompt(job.openai_request.messages ?? []),
|
|
2152
2485
|
context: objectRecord(payload.context) || undefined,
|
|
2153
2486
|
tenant: objectRecord(payload.tenant) || undefined,
|
|
2154
2487
|
requester: objectRecord(payload.requester) || undefined,
|
|
@@ -2182,7 +2515,7 @@ function buildCodaliPolicy(job) {
|
|
|
2182
2515
|
}
|
|
2183
2516
|
function normalizeGatewayConversation(job, value) {
|
|
2184
2517
|
const record = objectRecord(value);
|
|
2185
|
-
const rawMessages = Array.isArray(record?.messages) ? record.messages : job.openai_request.messages;
|
|
2518
|
+
const rawMessages = Array.isArray(record?.messages) ? record.messages : (job.openai_request.messages ?? []);
|
|
2186
2519
|
const messages = rawMessages
|
|
2187
2520
|
.map((entry) => {
|
|
2188
2521
|
const message = objectRecord(entry);
|
|
@@ -2279,7 +2612,7 @@ function buildCodaliGateway(job) {
|
|
|
2279
2612
|
const payload = job.codali_gateway;
|
|
2280
2613
|
if (!payload)
|
|
2281
2614
|
return undefined;
|
|
2282
|
-
const query = optionalText(payload.query) || messagesToPrompt(job.openai_request.messages);
|
|
2615
|
+
const query = optionalText(payload.query) || messagesToPrompt(job.openai_request.messages ?? []);
|
|
2283
2616
|
return {
|
|
2284
2617
|
id: optionalText(payload.id) || job.job_id,
|
|
2285
2618
|
query,
|
|
@@ -3903,6 +4236,7 @@ export class SelfHostedNodeRuntime {
|
|
|
3903
4236
|
this.recentFailures = [];
|
|
3904
4237
|
this.lifecyclePollingDisabled = false;
|
|
3905
4238
|
this.config = config;
|
|
4239
|
+
this.fetchImpl = deps?.fetchImpl || fetch;
|
|
3906
4240
|
this.gateway =
|
|
3907
4241
|
deps?.gateway ||
|
|
3908
4242
|
new MswarmSelfHostedNodeClient({
|
|
@@ -3911,7 +4245,7 @@ export class SelfHostedNodeRuntime {
|
|
|
3911
4245
|
jobsStartPathTemplate: config.jobsStartPathTemplate,
|
|
3912
4246
|
jobsEventsPathTemplate: config.jobsEventsPathTemplate,
|
|
3913
4247
|
jobsResultPathTemplate: config.jobsResultPathTemplate,
|
|
3914
|
-
fetchImpl:
|
|
4248
|
+
fetchImpl: this.fetchImpl,
|
|
3915
4249
|
timeoutMs: config.requestTimeoutMs
|
|
3916
4250
|
});
|
|
3917
4251
|
this.mcoda =
|
|
@@ -3933,14 +4267,14 @@ export class SelfHostedNodeRuntime {
|
|
|
3933
4267
|
deps?.ollama ||
|
|
3934
4268
|
new OllamaClient({
|
|
3935
4269
|
baseUrl: config.ollamaBaseUrl,
|
|
3936
|
-
fetchImpl:
|
|
4270
|
+
fetchImpl: this.fetchImpl,
|
|
3937
4271
|
timeoutMs: config.requestTimeoutMs
|
|
3938
4272
|
});
|
|
3939
4273
|
this.jobOllama =
|
|
3940
4274
|
deps?.ollama ||
|
|
3941
4275
|
new OllamaClient({
|
|
3942
4276
|
baseUrl: config.ollamaBaseUrl,
|
|
3943
|
-
fetchImpl:
|
|
4277
|
+
fetchImpl: this.fetchImpl,
|
|
3944
4278
|
timeoutMs: config.jobTimeoutMs
|
|
3945
4279
|
});
|
|
3946
4280
|
this.capabilityRunner = deps?.capabilityRunner || defaultCommandRunner;
|
|
@@ -4025,11 +4359,16 @@ export class SelfHostedNodeRuntime {
|
|
|
4025
4359
|
});
|
|
4026
4360
|
}
|
|
4027
4361
|
jobResultPayload(result) {
|
|
4362
|
+
const operation = normalizeSelfHostedGenerativeOperationName(result.operation) ?? "chat.completions";
|
|
4028
4363
|
return {
|
|
4029
4364
|
...result,
|
|
4030
4365
|
node_id: this.config.nodeId,
|
|
4031
4366
|
...(result.status === "success"
|
|
4032
|
-
?
|
|
4367
|
+
? result.usage
|
|
4368
|
+
? { usage: result.usage }
|
|
4369
|
+
: operation === "chat.completions"
|
|
4370
|
+
? { usage: openAiUsage(result.openai_response) }
|
|
4371
|
+
: {}
|
|
4033
4372
|
: {})
|
|
4034
4373
|
};
|
|
4035
4374
|
}
|
|
@@ -4397,7 +4736,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4397
4736
|
}
|
|
4398
4737
|
const body = {
|
|
4399
4738
|
model: optionalText(agent.model) || job.openai_request.model,
|
|
4400
|
-
messages: job.openai_request.messages,
|
|
4739
|
+
messages: job.openai_request.messages ?? [],
|
|
4401
4740
|
tools: job.openai_request.tools,
|
|
4402
4741
|
stream: false
|
|
4403
4742
|
};
|
|
@@ -4412,7 +4751,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4412
4751
|
if (job.openai_request.stop !== undefined)
|
|
4413
4752
|
body.stop = job.openai_request.stop;
|
|
4414
4753
|
const url = `${baseUrl.replace(/\/+$/, "")}/chat/completions`;
|
|
4415
|
-
const response = await
|
|
4754
|
+
const response = await this.fetchImpl(url, { method: "POST", headers, body: JSON.stringify(body) });
|
|
4416
4755
|
const text = await response.text();
|
|
4417
4756
|
let payload = null;
|
|
4418
4757
|
try {
|
|
@@ -4426,6 +4765,335 @@ export class SelfHostedNodeRuntime {
|
|
|
4426
4765
|
}
|
|
4427
4766
|
return payload;
|
|
4428
4767
|
}
|
|
4768
|
+
generativeOperationForAgent(agent, operation) {
|
|
4769
|
+
const descriptor = agent.generativeOperations.find((entry) => entry.operation === operation);
|
|
4770
|
+
if (!descriptor) {
|
|
4771
|
+
throw new SelfHostedPreStartJobError("unsupported_operation", `selected local mcoda agent ${agent.slug} does not expose ${operation}`);
|
|
4772
|
+
}
|
|
4773
|
+
return descriptor;
|
|
4774
|
+
}
|
|
4775
|
+
prepareOpenAiGenerativePassthrough(job, agent, operation) {
|
|
4776
|
+
const descriptor = this.generativeOperationForAgent(agent, operation);
|
|
4777
|
+
if (job.openai_request.stream === true) {
|
|
4778
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} does not support streaming through the self-hosted relay`);
|
|
4779
|
+
}
|
|
4780
|
+
const baseUrl = optionalText(agent.baseUrl);
|
|
4781
|
+
if (!baseUrl) {
|
|
4782
|
+
throw new SelfHostedPreStartJobError("selected_agent_unavailable", `agent has no local runner base URL for ${operation}`);
|
|
4783
|
+
}
|
|
4784
|
+
let parsedBaseUrl;
|
|
4785
|
+
try {
|
|
4786
|
+
parsedBaseUrl = new URL(baseUrl);
|
|
4787
|
+
}
|
|
4788
|
+
catch {
|
|
4789
|
+
throw new SelfHostedPreStartJobError("selected_agent_unavailable", `agent has an invalid local runner base URL for ${operation}`);
|
|
4790
|
+
}
|
|
4791
|
+
if ((parsedBaseUrl.protocol !== "http:" && parsedBaseUrl.protocol !== "https:") ||
|
|
4792
|
+
parsedBaseUrl.username ||
|
|
4793
|
+
parsedBaseUrl.password ||
|
|
4794
|
+
parsedBaseUrl.search ||
|
|
4795
|
+
parsedBaseUrl.hash) {
|
|
4796
|
+
throw new SelfHostedPreStartJobError("selected_agent_unavailable", `agent local runner base URL is not valid for ${operation}`);
|
|
4797
|
+
}
|
|
4798
|
+
const prompt = optionalText(job.openai_request.prompt);
|
|
4799
|
+
if (!prompt) {
|
|
4800
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} requires a non-empty prompt`);
|
|
4801
|
+
}
|
|
4802
|
+
const rawResponseFormat = job.openai_request.response_format;
|
|
4803
|
+
if (rawResponseFormat !== undefined && rawResponseFormat !== null && typeof rawResponseFormat !== "string") {
|
|
4804
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} response_format must be a string`);
|
|
4805
|
+
}
|
|
4806
|
+
const responseFormats = descriptor.responseFormats?.map((entry) => entry.toLowerCase()) ??
|
|
4807
|
+
(operation === "images.generations"
|
|
4808
|
+
? ["b64_json"]
|
|
4809
|
+
: operation === "audio.generations"
|
|
4810
|
+
? ["wav", "flac", "mp3", "ogg"]
|
|
4811
|
+
: ["webm", "webp", "avi"]);
|
|
4812
|
+
const requestedResponseFormat = (optionalText(rawResponseFormat) ||
|
|
4813
|
+
(operation === "images.generations"
|
|
4814
|
+
? "b64_json"
|
|
4815
|
+
: operation === "audio.generations"
|
|
4816
|
+
? responseFormats[0] || "wav"
|
|
4817
|
+
: responseFormats[0] || "webm")).toLowerCase();
|
|
4818
|
+
if (!responseFormats.includes(requestedResponseFormat) ||
|
|
4819
|
+
(operation === "images.generations"
|
|
4820
|
+
? requestedResponseFormat !== "b64_json"
|
|
4821
|
+
: operation === "audio.generations"
|
|
4822
|
+
? !AUDIO_GENERATION_RESPONSE_FORMATS.has(requestedResponseFormat)
|
|
4823
|
+
: !VIDEO_GENERATION_RESPONSE_FORMATS.has(requestedResponseFormat))) {
|
|
4824
|
+
throw new SelfHostedPreStartJobError("validation_failed", operation === "images.generations"
|
|
4825
|
+
? `${operation} requires response_format b64_json through the self-hosted relay`
|
|
4826
|
+
: operation === "audio.generations"
|
|
4827
|
+
? `${operation} response_format must be one of wav, flac, mp3, or ogg`
|
|
4828
|
+
: `${operation} response_format must be one of webm, webp, or avi`);
|
|
4829
|
+
}
|
|
4830
|
+
const body = {};
|
|
4831
|
+
for (const parameter of descriptor.requestParameterAllowlist) {
|
|
4832
|
+
if (parameter === "model") {
|
|
4833
|
+
body.model = optionalText(agent.model) || job.openai_request.model;
|
|
4834
|
+
}
|
|
4835
|
+
else if (parameter === "response_format") {
|
|
4836
|
+
body.response_format = requestedResponseFormat;
|
|
4837
|
+
}
|
|
4838
|
+
else if (job.openai_request[parameter] !== undefined) {
|
|
4839
|
+
body[parameter] = job.openai_request[parameter];
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
body.model = optionalText(agent.model) || job.openai_request.model;
|
|
4843
|
+
body.prompt = prompt;
|
|
4844
|
+
body.response_format = requestedResponseFormat;
|
|
4845
|
+
const n = body.n;
|
|
4846
|
+
if (n !== undefined && (!Number.isSafeInteger(n) || n <= 0)) {
|
|
4847
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} n must be a positive integer`);
|
|
4848
|
+
}
|
|
4849
|
+
const durationSeconds = body.duration_seconds;
|
|
4850
|
+
if (durationSeconds !== undefined &&
|
|
4851
|
+
(typeof durationSeconds !== "number" || !Number.isFinite(durationSeconds) || durationSeconds <= 0)) {
|
|
4852
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} duration_seconds must be a positive number`);
|
|
4853
|
+
}
|
|
4854
|
+
const sampleRate = body.sample_rate;
|
|
4855
|
+
if (sampleRate !== undefined &&
|
|
4856
|
+
(!Number.isSafeInteger(sampleRate) || sampleRate <= 0)) {
|
|
4857
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} sample_rate must be a positive integer`);
|
|
4858
|
+
}
|
|
4859
|
+
const fps = body.fps;
|
|
4860
|
+
if (fps !== undefined && (!Number.isSafeInteger(fps) || fps <= 0)) {
|
|
4861
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} fps must be a positive integer`);
|
|
4862
|
+
}
|
|
4863
|
+
const videoFrames = body.video_frames;
|
|
4864
|
+
if (videoFrames !== undefined &&
|
|
4865
|
+
(!Number.isSafeInteger(videoFrames) || videoFrames <= 0)) {
|
|
4866
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} video_frames must be a positive integer`);
|
|
4867
|
+
}
|
|
4868
|
+
if (operation === "videos.generations" &&
|
|
4869
|
+
durationSeconds !== undefined &&
|
|
4870
|
+
videoFrames !== undefined) {
|
|
4871
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} accepts duration_seconds or video_frames, not both`);
|
|
4872
|
+
}
|
|
4873
|
+
const steps = body.steps;
|
|
4874
|
+
if (steps !== undefined && (!Number.isSafeInteger(steps) || steps <= 0)) {
|
|
4875
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} steps must be a positive integer`);
|
|
4876
|
+
}
|
|
4877
|
+
const highNoiseSteps = body.high_noise_steps;
|
|
4878
|
+
if (highNoiseSteps !== undefined &&
|
|
4879
|
+
(!Number.isSafeInteger(highNoiseSteps) || highNoiseSteps <= 0)) {
|
|
4880
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} high_noise_steps must be a positive integer`);
|
|
4881
|
+
}
|
|
4882
|
+
const seed = body.seed;
|
|
4883
|
+
if (seed !== undefined && !Number.isSafeInteger(seed)) {
|
|
4884
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} seed must be an integer`);
|
|
4885
|
+
}
|
|
4886
|
+
if (body.size !== undefined && typeof body.size !== "string") {
|
|
4887
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} size must be a string`);
|
|
4888
|
+
}
|
|
4889
|
+
if (body.negative_prompt !== undefined && typeof body.negative_prompt !== "string") {
|
|
4890
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} negative_prompt must be a string`);
|
|
4891
|
+
}
|
|
4892
|
+
const limits = descriptor.limits;
|
|
4893
|
+
if (limits?.maxPromptChars && prompt.length > limits.maxPromptChars) {
|
|
4894
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} prompt exceeds the configured limit`);
|
|
4895
|
+
}
|
|
4896
|
+
if (limits?.maxNegativePromptChars &&
|
|
4897
|
+
typeof body.negative_prompt === "string" &&
|
|
4898
|
+
body.negative_prompt.length > limits.maxNegativePromptChars) {
|
|
4899
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} negative_prompt exceeds the configured limit`);
|
|
4900
|
+
}
|
|
4901
|
+
if (limits?.maxN && typeof n === "number" && n > limits.maxN) {
|
|
4902
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} n exceeds the configured limit`);
|
|
4903
|
+
}
|
|
4904
|
+
if (limits?.minDurationSeconds &&
|
|
4905
|
+
typeof durationSeconds === "number" &&
|
|
4906
|
+
durationSeconds < limits.minDurationSeconds) {
|
|
4907
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} duration_seconds is below the configured minimum`);
|
|
4908
|
+
}
|
|
4909
|
+
if (limits?.maxDurationSeconds &&
|
|
4910
|
+
typeof durationSeconds === "number" &&
|
|
4911
|
+
durationSeconds > limits.maxDurationSeconds) {
|
|
4912
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} duration_seconds exceeds the configured limit`);
|
|
4913
|
+
}
|
|
4914
|
+
if (limits?.maxSampleRate &&
|
|
4915
|
+
typeof sampleRate === "number" &&
|
|
4916
|
+
sampleRate > limits.maxSampleRate) {
|
|
4917
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} sample_rate exceeds the configured limit`);
|
|
4918
|
+
}
|
|
4919
|
+
if (limits?.minFps && typeof fps === "number" && fps < limits.minFps) {
|
|
4920
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} fps is below the configured minimum`);
|
|
4921
|
+
}
|
|
4922
|
+
if (limits?.maxFps && typeof fps === "number" && fps > limits.maxFps) {
|
|
4923
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} fps exceeds the configured limit`);
|
|
4924
|
+
}
|
|
4925
|
+
if (limits?.minVideoFrames &&
|
|
4926
|
+
typeof videoFrames === "number" &&
|
|
4927
|
+
videoFrames < limits.minVideoFrames) {
|
|
4928
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} video_frames is below the configured minimum`);
|
|
4929
|
+
}
|
|
4930
|
+
if (limits?.maxVideoFrames &&
|
|
4931
|
+
typeof videoFrames === "number" &&
|
|
4932
|
+
videoFrames > limits.maxVideoFrames) {
|
|
4933
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} video_frames exceeds the configured limit`);
|
|
4934
|
+
}
|
|
4935
|
+
if (limits?.maxSteps && typeof steps === "number" && steps > limits.maxSteps) {
|
|
4936
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} steps exceeds the configured limit`);
|
|
4937
|
+
}
|
|
4938
|
+
if (limits?.maxHighNoiseSteps &&
|
|
4939
|
+
typeof highNoiseSteps === "number" &&
|
|
4940
|
+
highNoiseSteps > limits.maxHighNoiseSteps) {
|
|
4941
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} high_noise_steps exceeds the configured limit`);
|
|
4942
|
+
}
|
|
4943
|
+
if (typeof body.size === "string") {
|
|
4944
|
+
const sizeMatch = /^([1-9][0-9]*)x([1-9][0-9]*)$/.exec(body.size);
|
|
4945
|
+
if (!sizeMatch) {
|
|
4946
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} size must use WIDTHxHEIGHT`);
|
|
4947
|
+
}
|
|
4948
|
+
const width = Number(sizeMatch[1]);
|
|
4949
|
+
const height = Number(sizeMatch[2]);
|
|
4950
|
+
if ((limits?.minWidth && width < limits.minWidth) ||
|
|
4951
|
+
(limits?.minHeight && height < limits.minHeight)) {
|
|
4952
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} size is below the configured minimum`);
|
|
4953
|
+
}
|
|
4954
|
+
if ((limits?.maxWidth && width > limits.maxWidth) ||
|
|
4955
|
+
(limits?.maxHeight && height > limits.maxHeight) ||
|
|
4956
|
+
(limits?.maxPixels && width * height > limits.maxPixels)) {
|
|
4957
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} size exceeds the configured limit`);
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
if (operation === "images.generations" &&
|
|
4961
|
+
agent.generativeRunnerKind === "stable-diffusion-cpp") {
|
|
4962
|
+
const sanitizedPrompt = sanitizeStableDiffusionCppExtraArgsTags(prompt);
|
|
4963
|
+
if (!sanitizedPrompt) {
|
|
4964
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} prompt is empty after removing reserved stable-diffusion.cpp tags`);
|
|
4965
|
+
}
|
|
4966
|
+
const controlledArgs = {};
|
|
4967
|
+
if (typeof body.negative_prompt === "string") {
|
|
4968
|
+
controlledArgs.negative_prompt =
|
|
4969
|
+
sanitizeStableDiffusionCppExtraArgsTags(body.negative_prompt);
|
|
4970
|
+
}
|
|
4971
|
+
if (typeof seed === "number") {
|
|
4972
|
+
controlledArgs.seed = seed;
|
|
4973
|
+
}
|
|
4974
|
+
if (typeof steps === "number") {
|
|
4975
|
+
controlledArgs.sample_params = { sample_steps: steps };
|
|
4976
|
+
}
|
|
4977
|
+
body.prompt =
|
|
4978
|
+
Object.keys(controlledArgs).length > 0
|
|
4979
|
+
? `${sanitizedPrompt}\n<sd_cpp_extra_args>${JSON.stringify(controlledArgs)}</sd_cpp_extra_args>`
|
|
4980
|
+
: sanitizedPrompt;
|
|
4981
|
+
delete body.negative_prompt;
|
|
4982
|
+
delete body.seed;
|
|
4983
|
+
delete body.steps;
|
|
4984
|
+
}
|
|
4985
|
+
const serializedBody = JSON.stringify(body);
|
|
4986
|
+
if (limits?.maxRequestBytes && Buffer.byteLength(serializedBody, "utf8") > limits.maxRequestBytes) {
|
|
4987
|
+
throw new SelfHostedPreStartJobError("validation_failed", `${operation} request exceeds the configured byte limit`);
|
|
4988
|
+
}
|
|
4989
|
+
const headers = {};
|
|
4990
|
+
const bearer = agent.authMode === "bearer"
|
|
4991
|
+
? optionalText(agent.apiKey)
|
|
4992
|
+
: agent.authMode === "dummy-bearer"
|
|
4993
|
+
? optionalText(agent.dummyBearerToken) || "local"
|
|
4994
|
+
: undefined;
|
|
4995
|
+
if (bearer) {
|
|
4996
|
+
headers.authorization = `Bearer ${bearer}`;
|
|
4997
|
+
}
|
|
4998
|
+
for (const [key, value] of Object.entries(agent.headers ?? {})) {
|
|
4999
|
+
if (typeof value === "string") {
|
|
5000
|
+
headers[key.toLowerCase()] = value;
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
headers["content-type"] = "application/json";
|
|
5004
|
+
return {
|
|
5005
|
+
descriptor,
|
|
5006
|
+
url: `${baseUrl.replace(/\/+$/, "")}${descriptor.path}`,
|
|
5007
|
+
headers,
|
|
5008
|
+
body
|
|
5009
|
+
};
|
|
5010
|
+
}
|
|
5011
|
+
async executeOpenAiGenerativePassthrough(prepared, operation) {
|
|
5012
|
+
const outputByteLimit = generativeOutputByteLimit(operation, prepared.descriptor.limits?.maxOutputBytes);
|
|
5013
|
+
const jsonResponseByteLimit = generativeJsonResponseByteLimit(outputByteLimit);
|
|
5014
|
+
const controller = new AbortController();
|
|
5015
|
+
const timeout = setTimeout(() => controller.abort(), this.config.jobTimeoutMs);
|
|
5016
|
+
let payload;
|
|
5017
|
+
try {
|
|
5018
|
+
const response = await this.fetchImpl(prepared.url, {
|
|
5019
|
+
method: "POST",
|
|
5020
|
+
headers: prepared.headers,
|
|
5021
|
+
body: JSON.stringify(prepared.body),
|
|
5022
|
+
signal: controller.signal
|
|
5023
|
+
});
|
|
5024
|
+
if (!response.ok) {
|
|
5025
|
+
await response.body?.cancel().catch(() => undefined);
|
|
5026
|
+
throw new Error(`local ${operation} runner request failed with status ${response.status}`);
|
|
5027
|
+
}
|
|
5028
|
+
const responseText = await readBoundedResponseText(response, jsonResponseByteLimit, `local ${operation} runner response exceeds the configured byte limit`);
|
|
5029
|
+
try {
|
|
5030
|
+
payload = JSON.parse(responseText);
|
|
5031
|
+
}
|
|
5032
|
+
catch {
|
|
5033
|
+
throw new Error(`local ${operation} runner returned invalid JSON`);
|
|
5034
|
+
}
|
|
5035
|
+
}
|
|
5036
|
+
catch (error) {
|
|
5037
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
5038
|
+
throw new Error(`local ${operation} runner timeout`);
|
|
5039
|
+
}
|
|
5040
|
+
throw error;
|
|
5041
|
+
}
|
|
5042
|
+
finally {
|
|
5043
|
+
clearTimeout(timeout);
|
|
5044
|
+
}
|
|
5045
|
+
if (!isRecord(payload) || !Array.isArray(payload.data) || payload.data.length === 0) {
|
|
5046
|
+
throw new Error(`local ${operation} runner returned an invalid base64 response`);
|
|
5047
|
+
}
|
|
5048
|
+
const limits = prepared.descriptor.limits;
|
|
5049
|
+
if (limits?.maxN && payload.data.length > limits.maxN) {
|
|
5050
|
+
throw new Error(`local ${operation} runner returned too many outputs`);
|
|
5051
|
+
}
|
|
5052
|
+
let decodedOutputBytes = 0;
|
|
5053
|
+
const normalizedData = [];
|
|
5054
|
+
for (const item of payload.data) {
|
|
5055
|
+
const encodedPayload = isRecord(item) && operation === "audio.generations"
|
|
5056
|
+
? optionalText(item.b64_audio) || optionalText(item.b64_json)
|
|
5057
|
+
: isRecord(item) && operation === "videos.generations"
|
|
5058
|
+
? optionalText(item.b64_video) || optionalText(item.b64_json)
|
|
5059
|
+
: isRecord(item)
|
|
5060
|
+
? optionalText(item.b64_json)
|
|
5061
|
+
: null;
|
|
5062
|
+
if (!isRecord(item) || !encodedPayload) {
|
|
5063
|
+
throw new Error(`local ${operation} runner returned an invalid base64 response`);
|
|
5064
|
+
}
|
|
5065
|
+
const decodedBytes = base64DecodedByteLength(encodedPayload);
|
|
5066
|
+
if (decodedBytes === null) {
|
|
5067
|
+
throw new Error(`local ${operation} runner returned an invalid base64 response`);
|
|
5068
|
+
}
|
|
5069
|
+
decodedOutputBytes += decodedBytes;
|
|
5070
|
+
if (decodedOutputBytes > outputByteLimit) {
|
|
5071
|
+
throw new Error(`local ${operation} runner response exceeds the configured byte limit`);
|
|
5072
|
+
}
|
|
5073
|
+
const mimeType = optionalText(item.mime_type);
|
|
5074
|
+
if (mimeType &&
|
|
5075
|
+
prepared.descriptor.outputMimeTypes?.length &&
|
|
5076
|
+
!prepared.descriptor.outputMimeTypes.includes(mimeType.toLowerCase())) {
|
|
5077
|
+
throw new Error(`local ${operation} runner returned unsupported mime_type ${mimeType}`);
|
|
5078
|
+
}
|
|
5079
|
+
if ((operation === "audio.generations" || operation === "videos.generations") &&
|
|
5080
|
+
!optionalText(operation === "audio.generations" ? item.b64_audio : item.b64_video) &&
|
|
5081
|
+
optionalText(item.b64_json)) {
|
|
5082
|
+
const normalizedItem = {
|
|
5083
|
+
...item,
|
|
5084
|
+
[operation === "audio.generations" ? "b64_audio" : "b64_video"]: encodedPayload
|
|
5085
|
+
};
|
|
5086
|
+
delete normalizedItem.b64_json;
|
|
5087
|
+
normalizedData.push(normalizedItem);
|
|
5088
|
+
}
|
|
5089
|
+
else {
|
|
5090
|
+
normalizedData.push(item);
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
return operation === "audio.generations" || operation === "videos.generations"
|
|
5094
|
+
? { ...payload, data: normalizedData }
|
|
5095
|
+
: payload;
|
|
5096
|
+
}
|
|
4429
5097
|
async resolveMcodaAgentForJob(job) {
|
|
4430
5098
|
const selectedSourceAgentSlug = optionalText(job.source_agent_slug);
|
|
4431
5099
|
const selectedAgentSlug = optionalText(job.agent_slug);
|
|
@@ -4647,6 +5315,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4647
5315
|
const startedAt = Date.now();
|
|
4648
5316
|
this.beginExecutionTelemetry("llm");
|
|
4649
5317
|
let selectedAgent;
|
|
5318
|
+
let operation = "chat.completions";
|
|
4650
5319
|
let jobStarted = false;
|
|
4651
5320
|
const progressEvents = [];
|
|
4652
5321
|
const streamEvents = [];
|
|
@@ -4676,6 +5345,9 @@ export class SelfHostedNodeRuntime {
|
|
|
4676
5345
|
const result = {
|
|
4677
5346
|
job_id: job.job_id,
|
|
4678
5347
|
request_id: job.request_id,
|
|
5348
|
+
...(normalizeSelfHostedGenerativeOperationName(job.operation)
|
|
5349
|
+
? { operation: normalizeSelfHostedGenerativeOperationName(job.operation) }
|
|
5350
|
+
: {}),
|
|
4679
5351
|
status: "failed",
|
|
4680
5352
|
pre_start_failure: true,
|
|
4681
5353
|
error: { code: "validation_failed", message: "job node_id does not match this node" }
|
|
@@ -4689,7 +5361,20 @@ export class SelfHostedNodeRuntime {
|
|
|
4689
5361
|
return result;
|
|
4690
5362
|
}
|
|
4691
5363
|
try {
|
|
5364
|
+
try {
|
|
5365
|
+
operation = resolveSelfHostedInvocationOperation(job.operation);
|
|
5366
|
+
}
|
|
5367
|
+
catch {
|
|
5368
|
+
throw new SelfHostedPreStartJobError("unsupported_operation", `unsupported self-hosted operation ${optionalText(job.operation) || String(job.operation)}`);
|
|
5369
|
+
}
|
|
4692
5370
|
if (job.provider === "ollama") {
|
|
5371
|
+
if (operation !== "chat.completions") {
|
|
5372
|
+
throw new SelfHostedPreStartJobError("unsupported_operation", `Ollama relay jobs do not support ${operation}`);
|
|
5373
|
+
}
|
|
5374
|
+
const chatMessages = job.openai_request.messages;
|
|
5375
|
+
if (!Array.isArray(chatMessages)) {
|
|
5376
|
+
throw new SelfHostedPreStartJobError("validation_failed", "chat.completions requires messages");
|
|
5377
|
+
}
|
|
4693
5378
|
const options = {};
|
|
4694
5379
|
if (job.openai_request.temperature !== undefined)
|
|
4695
5380
|
options.temperature = job.openai_request.temperature;
|
|
@@ -4702,7 +5387,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4702
5387
|
await acknowledgeStarted();
|
|
4703
5388
|
const ollamaResult = await this.jobOllama.chat({
|
|
4704
5389
|
model: job.model || job.openai_request.model,
|
|
4705
|
-
messages:
|
|
5390
|
+
messages: chatMessages,
|
|
4706
5391
|
options,
|
|
4707
5392
|
format: resolveOllamaResponseFormat(job.openai_request.response_format)
|
|
4708
5393
|
});
|
|
@@ -4731,6 +5416,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4731
5416
|
const invocationResult = {
|
|
4732
5417
|
job_id: job.job_id,
|
|
4733
5418
|
request_id: job.request_id,
|
|
5419
|
+
operation,
|
|
4734
5420
|
status: "success",
|
|
4735
5421
|
openai_response: buildOpenAIChatCompletion({
|
|
4736
5422
|
requestId: job.request_id,
|
|
@@ -4752,12 +5438,43 @@ export class SelfHostedNodeRuntime {
|
|
|
4752
5438
|
});
|
|
4753
5439
|
return invocationResult;
|
|
4754
5440
|
}
|
|
4755
|
-
const taskPreview = messagesToPrompt(job.openai_request.messages);
|
|
4756
|
-
if (!taskPreview) {
|
|
4757
|
-
throw new Error("mcoda invocation prompt is empty");
|
|
4758
|
-
}
|
|
4759
5441
|
const agent = await this.resolveMcodaAgentForJob(job);
|
|
4760
5442
|
selectedAgent = agent;
|
|
5443
|
+
this.generativeOperationForAgent(agent, operation);
|
|
5444
|
+
if (operation === "images.generations" ||
|
|
5445
|
+
operation === "audio.generations" ||
|
|
5446
|
+
operation === "videos.generations") {
|
|
5447
|
+
const prepared = this.prepareOpenAiGenerativePassthrough(job, agent, operation);
|
|
5448
|
+
await acknowledgeStarted(agent);
|
|
5449
|
+
await recordProgress({
|
|
5450
|
+
type: "agent_selected",
|
|
5451
|
+
operation,
|
|
5452
|
+
job_id: job.job_id,
|
|
5453
|
+
request_id: job.request_id,
|
|
5454
|
+
agent_slug: agent.slug,
|
|
5455
|
+
adapter: agent.adapter,
|
|
5456
|
+
local_model: optionalText(agent.model) || job.openai_request.model
|
|
5457
|
+
});
|
|
5458
|
+
const passthrough = await this.executeOpenAiGenerativePassthrough(prepared, operation);
|
|
5459
|
+
this.finishExecutionTelemetry({ executionClass: "llm", startedAt, ok: true });
|
|
5460
|
+
return {
|
|
5461
|
+
job_id: job.job_id,
|
|
5462
|
+
request_id: job.request_id,
|
|
5463
|
+
operation,
|
|
5464
|
+
status: "success",
|
|
5465
|
+
openai_response: passthrough,
|
|
5466
|
+
...(progressEvents.length ? { progress_events: progressEvents } : {}),
|
|
5467
|
+
timing: { local_latency_ms: Date.now() - startedAt }
|
|
5468
|
+
};
|
|
5469
|
+
}
|
|
5470
|
+
const chatMessages = job.openai_request.messages;
|
|
5471
|
+
if (!Array.isArray(chatMessages)) {
|
|
5472
|
+
throw new SelfHostedPreStartJobError("validation_failed", "chat.completions requires messages");
|
|
5473
|
+
}
|
|
5474
|
+
const taskPreview = messagesToPrompt(chatMessages);
|
|
5475
|
+
if (!taskPreview) {
|
|
5476
|
+
throw new SelfHostedPreStartJobError("validation_failed", "mcoda invocation prompt is empty");
|
|
5477
|
+
}
|
|
4761
5478
|
validateRequiredDocdexContext(job, options.attachedMswarmApiKey);
|
|
4762
5479
|
const attachedMswarmApiKey = attachedMswarmApiKeyForDocdex(job, options.attachedMswarmApiKey);
|
|
4763
5480
|
await acknowledgeStarted(agent);
|
|
@@ -4779,6 +5496,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4779
5496
|
const passthroughResult = {
|
|
4780
5497
|
job_id: job.job_id,
|
|
4781
5498
|
request_id: job.request_id,
|
|
5499
|
+
operation,
|
|
4782
5500
|
status: "success",
|
|
4783
5501
|
openai_response: passthrough,
|
|
4784
5502
|
...(progressEvents.length ? { progress_events: progressEvents } : {}),
|
|
@@ -4793,14 +5511,16 @@ export class SelfHostedNodeRuntime {
|
|
|
4793
5511
|
jobId: job.job_id,
|
|
4794
5512
|
requestId: job.request_id,
|
|
4795
5513
|
model: job.openai_request.model,
|
|
4796
|
-
messages:
|
|
5514
|
+
messages: chatMessages,
|
|
4797
5515
|
agent,
|
|
4798
5516
|
workspace: buildCodaliWorkspace(job),
|
|
4799
5517
|
docdex: buildCodaliDocdex(job),
|
|
4800
5518
|
attachedMswarmApiKey,
|
|
4801
5519
|
policy: buildCodaliPolicy(job),
|
|
4802
5520
|
temperature: job.openai_request.temperature,
|
|
4803
|
-
responseFormat: job.openai_request.response_format
|
|
5521
|
+
responseFormat: isRecord(job.openai_request.response_format)
|
|
5522
|
+
? job.openai_request.response_format
|
|
5523
|
+
: null,
|
|
4804
5524
|
stream: job.openai_request.stream === true,
|
|
4805
5525
|
codaliGateway,
|
|
4806
5526
|
codaliJob,
|
|
@@ -4860,6 +5580,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4860
5580
|
const result = {
|
|
4861
5581
|
job_id: job.job_id,
|
|
4862
5582
|
request_id: job.request_id,
|
|
5583
|
+
operation,
|
|
4863
5584
|
status: "success",
|
|
4864
5585
|
openai_response: buildOpenAIChatCompletion({
|
|
4865
5586
|
requestId: job.request_id,
|
|
@@ -4932,6 +5653,7 @@ export class SelfHostedNodeRuntime {
|
|
|
4932
5653
|
const result = {
|
|
4933
5654
|
job_id: job.job_id,
|
|
4934
5655
|
request_id: job.request_id,
|
|
5656
|
+
operation,
|
|
4935
5657
|
status: "failed",
|
|
4936
5658
|
...(!jobStarted ? { pre_start_failure: true } : {}),
|
|
4937
5659
|
error: {
|
|
@@ -5147,6 +5869,9 @@ export class SelfHostedNodeRuntime {
|
|
|
5147
5869
|
if (!job) {
|
|
5148
5870
|
return { executed: false };
|
|
5149
5871
|
}
|
|
5872
|
+
const chatStreaming = (normalizeSelfHostedGenerativeOperationName(job.operation) ?? "chat.completions") ===
|
|
5873
|
+
"chat.completions" &&
|
|
5874
|
+
job.openai_request.stream === true;
|
|
5150
5875
|
const pendingStreamEvents = [];
|
|
5151
5876
|
let streamEventForwardingFailed = false;
|
|
5152
5877
|
let forwardedStreamEventCount = 0;
|
|
@@ -5193,7 +5918,7 @@ export class SelfHostedNodeRuntime {
|
|
|
5193
5918
|
}
|
|
5194
5919
|
},
|
|
5195
5920
|
onOpenAIChunk: async (chunk) => {
|
|
5196
|
-
if (
|
|
5921
|
+
if (!chatStreaming || streamEventForwardingFailed) {
|
|
5197
5922
|
return;
|
|
5198
5923
|
}
|
|
5199
5924
|
pendingStreamEvents.push(chunk);
|
|
@@ -5212,6 +5937,7 @@ export class SelfHostedNodeRuntime {
|
|
|
5212
5937
|
result = {
|
|
5213
5938
|
job_id: job.job_id,
|
|
5214
5939
|
request_id: job.request_id,
|
|
5940
|
+
operation: result.operation,
|
|
5215
5941
|
status: "failed",
|
|
5216
5942
|
error: { code: SELF_HOSTED_PROTOCOL_MISMATCH_CODE, message: error.message },
|
|
5217
5943
|
...(result.stream_events?.length ? { stream_events: result.stream_events } : {}),
|
|
@@ -5219,7 +5945,7 @@ export class SelfHostedNodeRuntime {
|
|
|
5219
5945
|
timing: result.timing
|
|
5220
5946
|
};
|
|
5221
5947
|
}
|
|
5222
|
-
const postedResult =
|
|
5948
|
+
const postedResult = chatStreaming
|
|
5223
5949
|
? streamEventForwardingFailed
|
|
5224
5950
|
? {
|
|
5225
5951
|
...result,
|