@infersec/conduit 1.113.0 → 1.114.1

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/cli.sea.cjs CHANGED
@@ -19900,6 +19900,7 @@ object$5({
19900
19900
  });
19901
19901
 
19902
19902
  const LLMEngineSchema = _enum$1([
19903
+ "custom",
19903
19904
  "exllamav3",
19904
19905
  "llama.cpp",
19905
19906
  "mlx-lm",
@@ -19908,8 +19909,25 @@ const LLMEngineSchema = _enum$1([
19908
19909
  "vllm"
19909
19910
  ]);
19910
19911
  const EngineConfigSchema = object$5({
19912
+ baseUrl: string$2().url().nullable().default(null),
19911
19913
  extraArgs: array$1(string$2()),
19912
19914
  type: LLMEngineSchema
19915
+ })
19916
+ .superRefine((config, ctx) => {
19917
+ if (config.type === "custom" && config.baseUrl === null) {
19918
+ ctx.addIssue({
19919
+ code: "custom",
19920
+ message: "Engine type 'custom' requires a base URL",
19921
+ path: ["baseUrl"]
19922
+ });
19923
+ }
19924
+ if (config.type !== "custom" && config.baseUrl !== null) {
19925
+ ctx.addIssue({
19926
+ code: "custom",
19927
+ message: "Base URL is only valid for engine type 'custom'",
19928
+ path: ["baseUrl"]
19929
+ });
19930
+ }
19913
19931
  });
19914
19932
  const LLMModelFormatSchema = _enum$1([
19915
19933
  // VLLM / SGLang / TensorRT-LLM
@@ -19968,14 +19986,18 @@ const LLMModelSchema = object$5({
19968
19986
  id: string$2().min(1),
19969
19987
  multimodalEnabled: boolean$1(),
19970
19988
  source: discriminatedUnion("type", [
19989
+ // Custom engines: no local model record, serving is external
19971
19990
  object$5({
19972
- irid: IRIDSchema,
19973
- type: literal("storage")
19991
+ type: literal("external")
19974
19992
  }),
19975
19993
  object$5({
19976
19994
  modelSecret: string$2().min(1).nullable(),
19977
19995
  slug: string$2().min(1),
19978
19996
  type: literal("huggingface")
19997
+ }),
19998
+ object$5({
19999
+ irid: IRIDSchema,
20000
+ type: literal("storage")
19979
20001
  })
19980
20002
  ]),
19981
20003
  taskType: LLMModelTaskTypeSchema,
@@ -21119,7 +21141,8 @@ const CreateModelResponseSchema = object$5({
21119
21141
  const CreateSourceBodySchema = object$5({
21120
21142
  contextLength: number$1().int().positive().max(1048576).optional(),
21121
21143
  engineId: ULIDSchema,
21122
- modelID: ULIDSchema,
21144
+ // Optional for custom engines, which serve externally managed models
21145
+ modelID: ULIDSchema.nullable().optional(),
21123
21146
  name: ResourceNameSchema,
21124
21147
  quantizationLabel: string$2().min(1).max(128).optional()
21125
21148
  });
@@ -21168,7 +21191,7 @@ const SourceDetailResponseSchema = object$5({
21168
21191
  const UpdateSourceBodySchema = object$5({
21169
21192
  contextLength: number$1().int().positive().nullable().optional(),
21170
21193
  engineId: ULIDSchema.nullable().optional(),
21171
- modelID: ULIDSchema.optional(),
21194
+ modelID: ULIDSchema.nullable().optional(),
21172
21195
  name: ResourceNameSchema.optional(),
21173
21196
  quantizationLabel: string$2().min(1).max(128).nullable().optional()
21174
21197
  });
@@ -21292,6 +21315,7 @@ const CreateEndpointResponseSchema = object$5({
21292
21315
  id: ULIDSchema
21293
21316
  });
21294
21317
  const EngineOutputSchema = object$5({
21318
+ baseUrl: string$2().nullable(),
21295
21319
  created: string$2(),
21296
21320
  extraArgs: array$1(string$2()),
21297
21321
  id: ULIDSchema,
@@ -21300,11 +21324,29 @@ const EngineOutputSchema = object$5({
21300
21324
  updated: string$2()
21301
21325
  });
21302
21326
  const CreateEngineBodySchema = object$5({
21327
+ baseUrl: string$2().url().nullable().optional(),
21303
21328
  extraArgs: array$1(string$2()).optional(),
21304
21329
  name: ResourceNameSchema,
21305
21330
  type: LLMEngineSchema
21331
+ })
21332
+ .superRefine((body, ctx) => {
21333
+ if (body.type === "custom" && !body.baseUrl) {
21334
+ ctx.addIssue({
21335
+ code: "custom",
21336
+ message: "Engine type 'custom' requires a base URL",
21337
+ path: ["baseUrl"]
21338
+ });
21339
+ }
21340
+ if (body.type !== "custom" && body.baseUrl) {
21341
+ ctx.addIssue({
21342
+ code: "custom",
21343
+ message: "Base URL is only valid for engine type 'custom'",
21344
+ path: ["baseUrl"]
21345
+ });
21346
+ }
21306
21347
  });
21307
21348
  const UpdateEngineBodySchema = object$5({
21349
+ baseUrl: string$2().url().nullable().optional(),
21308
21350
  extraArgs: array$1(string$2()).optional(),
21309
21351
  name: ResourceNameSchema.optional(),
21310
21352
  type: LLMEngineSchema.optional()
@@ -22110,6 +22152,11 @@ const RecommendedModelSchema = object$5({
22110
22152
  const recommendedModels = RecommendedModelSchema.array().parse(modelsData);
22111
22153
 
22112
22154
  const ENGINE_API_COMPATIBILITY = {
22155
+ custom: {
22156
+ nativeAnthropicMessages: false,
22157
+ supportsEmbeddings: true,
22158
+ supportsVision: true
22159
+ },
22113
22160
  exllamav3: {
22114
22161
  nativeAnthropicMessages: false,
22115
22162
  supportsEmbeddings: false,
@@ -111759,6 +111806,27 @@ function registerEndpointCommands({ program }) {
111759
111806
  }
111760
111807
 
111761
111808
  const ENGINE_TYPES = LLMEngineSchema.options;
111809
+ function isValidURL(value) {
111810
+ try {
111811
+ const url = new URL(value);
111812
+ return url.protocol === "http:" || url.protocol === "https:";
111813
+ }
111814
+ catch (_error) {
111815
+ return false;
111816
+ }
111817
+ }
111818
+ function validateEngineBaseURL({ baseUrl, type }) {
111819
+ if (type === "custom" && !baseUrl) {
111820
+ throw new Error("Engine type 'custom' requires --base-url");
111821
+ }
111822
+ if (baseUrl !== undefined && type !== "custom") {
111823
+ throw new Error("--base-url is only valid for engine type 'custom'");
111824
+ }
111825
+ if (baseUrl !== undefined && baseUrl !== "" && !isValidURL(baseUrl)) {
111826
+ throw new Error(`Invalid --base-url value: ${baseUrl}`);
111827
+ }
111828
+ return baseUrl === "" ? null : (baseUrl ?? null);
111829
+ }
111762
111830
  function buildEngineCreateBody(options) {
111763
111831
  if (!options.name) {
111764
111832
  throw new Error("--name is required");
@@ -111769,7 +111837,12 @@ function buildEngineCreateBody(options) {
111769
111837
  if (!ENGINE_TYPES.includes(options.type)) {
111770
111838
  throw new Error(`Invalid engine type: ${options.type} (expected one of: ${ENGINE_TYPES.join(", ")})`);
111771
111839
  }
111840
+ const baseUrl = validateEngineBaseURL({
111841
+ baseUrl: options.baseUrl,
111842
+ type: options.type
111843
+ });
111772
111844
  return {
111845
+ baseUrl,
111773
111846
  extraArgs: options.arg ?? [],
111774
111847
  name: options.name,
111775
111848
  type: options.type
@@ -111787,6 +111860,24 @@ function buildEngineUpdateBody(options) {
111787
111860
  }
111788
111861
  if (options.arg !== undefined)
111789
111862
  body.extraArgs = options.arg;
111863
+ if (options.baseUrl !== undefined) {
111864
+ const targetType = body.type;
111865
+ if (targetType !== undefined) {
111866
+ body.baseUrl = validateEngineBaseURL({
111867
+ baseUrl: options.baseUrl,
111868
+ type: targetType
111869
+ });
111870
+ }
111871
+ else if (options.baseUrl === "") {
111872
+ body.baseUrl = null;
111873
+ }
111874
+ else if (!isValidURL(options.baseUrl)) {
111875
+ throw new Error(`Invalid --base-url value: ${options.baseUrl}`);
111876
+ }
111877
+ else {
111878
+ body.baseUrl = options.baseUrl;
111879
+ }
111880
+ }
111790
111881
  return body;
111791
111882
  }
111792
111883
 
@@ -111800,10 +111891,11 @@ function registerEngineCommands({ program }) {
111800
111891
  .description("Create or update an inference engine resource")
111801
111892
  .option("--api-url <url>", "API base URL (required, no environment variable fallback)")
111802
111893
  .option("--arg <flag>", 'Raw engine CLI flag, repeatable (eg --arg "--flash-attn on")', collect, [])
111894
+ .option("--base-url <url>", "Base URL of an external OpenAI-compatible server (engine type 'custom' only)")
111803
111895
  .option("--id <ulid>", "Target an existing engine by ID (requires --update)")
111804
111896
  .option("--key <value>", "API key (required, no environment variable fallback)")
111805
111897
  .option("--name <name>", "Engine name (matched by --update)")
111806
- .option("--type <type>", "Engine type: llama.cpp|vllm|sglang|tensorrt-llm|mlx-lm|exllamav3")
111898
+ .option("--type <type>", "Engine type: llama.cpp|vllm|sglang|tensorrt-llm|mlx-lm|exllamav3|custom")
111807
111899
  .option("--update", "Update an existing engine matched by name (or --id) instead of erroring")
111808
111900
  .action(async (options) => {
111809
111901
  const { apiURL, apiKey } = resolveManagementConnection(options);
@@ -117803,6 +117895,9 @@ async function getChatTemplateEngineArgs({ engine, model, targetDirectory }) {
117803
117895
  return [];
117804
117896
  const flag = FLAG_BASED_ENGINE_ARGS[engine];
117805
117897
  if (!flag) {
117898
+ if (engine === "custom") {
117899
+ console.warn("[chatTemplate] Custom engines manage their own serving; ignoring chat template override");
117900
+ }
117806
117901
  if (engine === "tensorrt-llm") {
117807
117902
  console.warn("[chatTemplate] TensorRT-LLM does not support chat template overrides; ignoring");
117808
117903
  }
@@ -124700,7 +124795,7 @@ function matchesQuantizationVariant({ filePath, variant }) {
124700
124795
  return segments.slice(0, -1).some(segment => matcher.test(segment));
124701
124796
  }
124702
124797
  async function findQuantizedModelTarget({ model, path }) {
124703
- if (model.source.type === "storage") {
124798
+ if (model.source.type !== "huggingface") {
124704
124799
  throw new Error("Model storage not supported yet");
124705
124800
  }
124706
124801
  if (model.format !== "gguf") {
@@ -125615,7 +125710,11 @@ function sanitizeSegment(value) {
125615
125710
  .replace(new RegExp(`${SEPARATOR}{2,}`, "g"), SEPARATOR);
125616
125711
  }
125617
125712
  function createModelStorageKey(model) {
125618
- const identifier = model.source.type === "huggingface" ? model.source.slug : model.source.irid;
125713
+ const identifier = model.source.type === "huggingface"
125714
+ ? model.source.slug
125715
+ : model.source.type === "storage"
125716
+ ? model.source.irid
125717
+ : model.id;
125619
125718
  return `${model.source.type}${SEPARATOR}${sanitizeSegment(identifier)}`;
125620
125719
  }
125621
125720
 
@@ -125691,8 +125790,11 @@ class ModelManager extends EventEmitter {
125691
125790
  uniqueName;
125692
125791
  contextLength;
125693
125792
  logger;
125793
+ discoveredModelNames = [];
125794
+ customProbeCount = 0;
125694
125795
  engineProcess = null;
125695
125796
  healthPollInterval = null;
125797
+ lastCustomReadinessReport = null;
125696
125798
  lastEngineError = null;
125697
125799
  lifecycleState = "stopped";
125698
125800
  downloadLockHandle = null;
@@ -125723,6 +125825,7 @@ class ModelManager extends EventEmitter {
125723
125825
  }
125724
125826
  async fetchOpenAI(path, opts) {
125725
125827
  switch (this.engine) {
125828
+ case "custom":
125726
125829
  case "exllamav3":
125727
125830
  case "llama.cpp":
125728
125831
  case "mlx-lm":
@@ -125730,6 +125833,9 @@ class ModelManager extends EventEmitter {
125730
125833
  case "tensorrt-llm":
125731
125834
  case "vllm": {
125732
125835
  this.logger.debug(`Fetching from engine: ${path}`);
125836
+ const baseURL = this.engine === "custom"
125837
+ ? this.requireCustomBaseURL()
125838
+ : `http://localhost:${this.enginePort}`;
125733
125839
  const callerSignal = opts?.signal;
125734
125840
  const controller = new AbortController();
125735
125841
  const timeout = setTimeout(() => {
@@ -125740,7 +125846,7 @@ class ModelManager extends EventEmitter {
125740
125846
  : controller.signal;
125741
125847
  try {
125742
125848
  const fetchStartedAt = Date.now();
125743
- const response = await undiciExports.fetch(joinURL(`http://localhost:${this.enginePort}`, path), {
125849
+ const response = await undiciExports.fetch(joinURL(baseURL, path), {
125744
125850
  ...opts,
125745
125851
  dispatcher: ENGINE_AGENT,
125746
125852
  headers: {
@@ -125775,6 +125881,11 @@ class ModelManager extends EventEmitter {
125775
125881
  modelID: this.model.id
125776
125882
  });
125777
125883
  switch (this.engine) {
125884
+ case "custom":
125885
+ if (this.model.chatTemplate) {
125886
+ this.logger.warn("Chat template overrides are ignored for custom engines: the remote server manages its own serving");
125887
+ }
125888
+ break;
125778
125889
  case "exllamav3":
125779
125890
  case "llama.cpp":
125780
125891
  case "mlx-lm":
@@ -125830,7 +125941,9 @@ class ModelManager extends EventEmitter {
125830
125941
  });
125831
125942
  try {
125832
125943
  this.engineProcess = await this.startEngineProcess();
125833
- this.bindEngineProcessEvents(this.engineProcess);
125944
+ if (this.engineProcess) {
125945
+ this.bindEngineProcessEvents(this.engineProcess);
125946
+ }
125834
125947
  this.logger.info("Started LLM engine", {
125835
125948
  agentEngineType: this.engine
125836
125949
  });
@@ -125849,7 +125962,7 @@ class ModelManager extends EventEmitter {
125849
125962
  if (!alreadyEmitted) {
125850
125963
  this.emit("engineError", err);
125851
125964
  }
125852
- if (this.engineProcess) {
125965
+ if (this.engineProcess || this.engine === "custom") {
125853
125966
  this.startHealthPoll();
125854
125967
  }
125855
125968
  throw err;
@@ -125857,6 +125970,9 @@ class ModelManager extends EventEmitter {
125857
125970
  this.lifecycleState = "running";
125858
125971
  this.reachedRunningState = true;
125859
125972
  this.emit("engineReady");
125973
+ if (this.engine === "custom") {
125974
+ this.startHealthPoll();
125975
+ }
125860
125976
  }
125861
125977
  async stop() {
125862
125978
  if (this.lifecycleState === "stopping") {
@@ -125877,6 +125993,8 @@ class ModelManager extends EventEmitter {
125877
125993
  this.clearHealthPoll();
125878
125994
  const processManager = this.engineProcess;
125879
125995
  if (!processManager) {
125996
+ this.stopRequested = true;
125997
+ this.reachedRunningState = false;
125880
125998
  this.lifecycleState = "stopped";
125881
125999
  return;
125882
126000
  }
@@ -125902,11 +126020,27 @@ class ModelManager extends EventEmitter {
125902
126020
  get state() {
125903
126021
  return this.lifecycleState;
125904
126022
  }
126023
+ get resolvedServedModelName() {
126024
+ if (this.engine !== "custom")
126025
+ return null;
126026
+ return this.discoveredModelNames[0] ?? null;
126027
+ }
125905
126028
  get wasRunning() {
125906
126029
  return this.reachedRunningState;
125907
126030
  }
126031
+ get customBaseURL() {
126032
+ if (this.engine !== "custom")
126033
+ return null;
126034
+ const baseUrl = this.engineConfig?.baseUrl;
126035
+ if (typeof baseUrl !== "string" || baseUrl.length === 0)
126036
+ return null;
126037
+ return baseUrl.replace(/\/+$/, "");
126038
+ }
125908
126039
  async checkEngineReadiness() {
125909
126040
  switch (this.engine) {
126041
+ case "custom": {
126042
+ return this.checkCustomReadiness();
126043
+ }
125910
126044
  case "llama.cpp": {
125911
126045
  return this.checkLlamacppReadiness();
125912
126046
  }
@@ -125923,6 +126057,98 @@ class ModelManager extends EventEmitter {
125923
126057
  return "ready";
125924
126058
  }
125925
126059
  }
126060
+ async checkCustomReadiness() {
126061
+ const baseURL = this.customBaseURL;
126062
+ if (!baseURL) {
126063
+ this.reportCustomReadinessChange("unreachable", "", "no base URL configured");
126064
+ return "unreachable";
126065
+ }
126066
+ const probeURL = joinURL(baseURL, "/v1/models");
126067
+ this.customProbeCount++;
126068
+ try {
126069
+ const response = await undiciExports.fetch(probeURL, {
126070
+ method: "GET",
126071
+ signal: AbortSignal.timeout(5000)
126072
+ });
126073
+ if (response.status === 503) {
126074
+ this.logger.debug("Custom engine probe: server loading", {
126075
+ attempt: this.customProbeCount,
126076
+ probeURL
126077
+ });
126078
+ this.reportCustomReadinessChange("loading", baseURL, "server loading (HTTP 503)");
126079
+ return "loading";
126080
+ }
126081
+ if (!response.ok) {
126082
+ const reason = `HTTP ${response.status} from /v1/models`;
126083
+ this.logger.debug("Custom engine probe: not ready", {
126084
+ attempt: this.customProbeCount,
126085
+ probeURL,
126086
+ reason
126087
+ });
126088
+ this.reportCustomReadinessChange("unreachable", baseURL, reason);
126089
+ return "unreachable";
126090
+ }
126091
+ const payload = (await response.json());
126092
+ const models = Array.isArray(payload.data) ? payload.data : [];
126093
+ const modelIDs = models
126094
+ .map(model => {
126095
+ if (model === null || typeof model !== "object")
126096
+ return null;
126097
+ const id = model.id;
126098
+ return typeof id === "string" ? id : null;
126099
+ })
126100
+ .filter((id) => id !== null);
126101
+ if (modelIDs.length === 0) {
126102
+ this.logger.debug("Custom engine probe: no models exposed yet", {
126103
+ attempt: this.customProbeCount,
126104
+ probeURL
126105
+ });
126106
+ this.reportCustomReadinessChange("loading", baseURL, "/v1/models returned no models");
126107
+ return "loading";
126108
+ }
126109
+ if (modelIDs.join("\n") !== this.discoveredModelNames.join("\n")) {
126110
+ this.discoveredModelNames = modelIDs;
126111
+ this.logger.info("Discovered models on custom engine endpoint", {
126112
+ engineBaseURL: baseURL,
126113
+ models: modelIDs,
126114
+ selectedModel: modelIDs[0]
126115
+ });
126116
+ }
126117
+ return "ready";
126118
+ }
126119
+ catch (error) {
126120
+ const reason = asError(error).message;
126121
+ this.logger.debug("Custom engine probe: request failed", {
126122
+ attempt: this.customProbeCount,
126123
+ probeURL,
126124
+ reason
126125
+ });
126126
+ this.reportCustomReadinessChange("unreachable", baseURL, reason);
126127
+ return "unreachable";
126128
+ }
126129
+ }
126130
+ reportCustomReadinessChange(readiness, baseURL, reason) {
126131
+ const key = `${readiness}:${reason}`;
126132
+ if (key === this.lastCustomReadinessReport) {
126133
+ // Same outcome as the last report: surface a heartbeat every 15
126134
+ // attempts (~30s while booting) so progress stays visible.
126135
+ if (this.customProbeCount % 15 === 0) {
126136
+ this.logger.info("Still waiting for custom engine endpoint", {
126137
+ attempt: this.customProbeCount,
126138
+ engineBaseURL: baseURL,
126139
+ reason
126140
+ });
126141
+ }
126142
+ return;
126143
+ }
126144
+ this.lastCustomReadinessReport = key;
126145
+ this.logger.warn(readiness === "loading"
126146
+ ? "Custom engine endpoint is loading — waiting for /v1/models to expose a model"
126147
+ : "Custom engine endpoint unreachable — verify the server is running and reachable from conduit", {
126148
+ engineBaseURL: baseURL,
126149
+ reason
126150
+ });
126151
+ }
125926
126152
  async checkGenericHealthReadiness() {
125927
126153
  try {
125928
126154
  const response = await undiciExports.fetch(joinURL(`http://localhost:${this.enginePort}`, "/health"), {
@@ -125978,23 +126204,41 @@ class ModelManager extends EventEmitter {
125978
126204
  }
125979
126205
  }
125980
126206
  async waitForEngineReady() {
125981
- const maxWaitMs = 15 * 60 * 1000;
126207
+ const maxWaitMs = Number.parseInt(process.env.ENGINE_STARTUP_TIMEOUT_MS ?? "", 10) || 15 * 60 * 1000;
125982
126208
  const pollIntervalMs = 2000;
125983
126209
  const start = Date.now();
126210
+ if (this.engine === "custom") {
126211
+ this.logger.info(`Connecting to external OpenAI-compatible server at ${this.customBaseURL ?? "(no base URL configured)"} — serving begins once /v1/models exposes a model`, {
126212
+ engineBaseURL: this.customBaseURL ?? ""
126213
+ });
126214
+ }
125984
126215
  while (Date.now() - start < maxWaitMs) {
125985
- if (this.lifecycleState === "stopping") {
126216
+ if (this.lifecycleState === "stopping" || this.stopRequested) {
125986
126217
  throw new Error("LLM engine startup interrupted by stop request");
125987
126218
  }
125988
- if (!this.engineProcess) {
126219
+ if (!this.engineProcess && this.engine !== "custom") {
125989
126220
  throw new Error("LLM engine process exited before readiness checks completed");
125990
126221
  }
125991
126222
  const readiness = await this.checkEngineReadiness();
125992
126223
  if (readiness === "ready") {
126224
+ // A stop() may have landed while the readiness request was
126225
+ // in flight; re-check before declaring ready.
126226
+ const lifecycleState = this.lifecycleState;
126227
+ if (lifecycleState === "stopping" || this.stopRequested) {
126228
+ throw new Error("LLM engine startup interrupted by stop request");
126229
+ }
125993
126230
  return;
125994
126231
  }
125995
126232
  await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
125996
126233
  }
125997
126234
  const stderrTail = this.engineProcess?.stderr?.slice(-1e3);
126235
+ if (this.engine === "custom") {
126236
+ const baseURL = this.customBaseURL ?? "(missing)";
126237
+ const discovered = this.discoveredModelNames;
126238
+ throw new Error(discovered.length > 0
126239
+ ? `Custom engine endpoint at ${baseURL} exposed models (${discovered.join(", ")}) but never reported ready within ${Math.round(maxWaitMs / 1000)}s`
126240
+ : `Custom engine endpoint at ${baseURL} did not expose a model via /v1/models within ${Math.round(maxWaitMs / 1000)}s. Verify the server is running and reachable from conduit (if conduit runs in a container, use the host address instead of localhost)`);
126241
+ }
125998
126242
  throw new Error(stderrTail
125999
126243
  ? `LLM engine failed readiness checks within timeout. Last engine output:\n${stderrTail}`
126000
126244
  : "LLM engine failed readiness checks within timeout");
@@ -126007,19 +126251,32 @@ class ModelManager extends EventEmitter {
126007
126251
  }
126008
126252
  startHealthPoll() {
126009
126253
  this.clearHealthPoll();
126010
- this.logger.info("Starting background health poll for errored engine");
126254
+ this.logger.info("Starting background engine health poll", {
126255
+ agentEngineType: this.engine
126256
+ });
126011
126257
  this.healthPollInterval = setInterval(() => {
126012
- if (!this.engineProcess) {
126258
+ if (!this.engineProcess && this.engine !== "custom") {
126013
126259
  this.clearHealthPoll();
126014
126260
  return;
126015
126261
  }
126016
126262
  this.checkEngineReadiness()
126017
126263
  .then(readiness => {
126018
126264
  if (readiness === "ready") {
126019
- this.clearHealthPoll();
126020
- this.lifecycleState = "running";
126021
- this.reachedRunningState = true;
126022
- this.emit("engineReady");
126265
+ if (this.lifecycleState === "errored" ||
126266
+ this.lifecycleState === "starting") {
126267
+ this.lifecycleState = "running";
126268
+ this.reachedRunningState = true;
126269
+ this.emit("engineReady");
126270
+ }
126271
+ if (this.engine !== "custom") {
126272
+ this.clearHealthPoll();
126273
+ }
126274
+ return;
126275
+ }
126276
+ if (this.engine === "custom" &&
126277
+ readiness === "unreachable" &&
126278
+ this.lifecycleState === "running") {
126279
+ this.recordEngineError(new Error(`Custom engine endpoint unreachable: ${this.customBaseURL}`));
126023
126280
  }
126024
126281
  })
126025
126282
  .catch(() => {
@@ -126044,6 +126301,15 @@ class ModelManager extends EventEmitter {
126044
126301
  this.lastEngineError = err;
126045
126302
  this.emit("engineError", err);
126046
126303
  }
126304
+ requireCustomBaseURL() {
126305
+ const baseURL = this.customBaseURL;
126306
+ if (!baseURL) {
126307
+ throw new ConfigurationInvalidError({
126308
+ message: "Custom engine requires a base URL"
126309
+ });
126310
+ }
126311
+ return baseURL;
126312
+ }
126047
126313
  async releaseDownloadLock() {
126048
126314
  const handle = this.downloadLockHandle;
126049
126315
  if (!handle)
@@ -126110,6 +126376,8 @@ class ModelManager extends EventEmitter {
126110
126376
  async startEngineProcess() {
126111
126377
  const targetDir = path$1.join(this.modelsDirectory, this.uniqueName);
126112
126378
  switch (this.engine) {
126379
+ case "custom":
126380
+ return null;
126113
126381
  case "exllamav3":
126114
126382
  return startExllamav3.call(this, {
126115
126383
  enginePort: this.enginePort,
@@ -126421,8 +126689,9 @@ function isEngineUsageChunk(value) {
126421
126689
  }
126422
126690
  return true;
126423
126691
  }
126424
- function monitorEngineResponseStream({ agentEngineType, body, contextLength, engineConfig, engineType, logger, onComplete, requestBodyBytes, requestPath, requestStartedAt }) {
126692
+ function monitorEngineResponseStream({ agentEngineType, body, contextLength, engineConfig, engineType, logger, onComplete, requestBodyBytes, requestPath, requestStartedAt, responseModelName }) {
126425
126693
  const startedAt = requestStartedAt ?? Date.now();
126694
+ const clientModelName = responseModelName ?? null;
126426
126695
  const passThrough = new require$$0$8.PassThrough();
126427
126696
  passThrough.on("error", (error) => {
126428
126697
  logger.error("Engine response stream error", {
@@ -126434,53 +126703,61 @@ function monitorEngineResponseStream({ agentEngineType, body, contextLength, eng
126434
126703
  let firstChunkAt = null;
126435
126704
  let usage = null;
126436
126705
  let buffer = "";
126706
+ let pendingFragment = "";
126437
126707
  let completed = false;
126438
- function modifyChunkWithUsage(chunk) {
126439
- const text = chunk.toString("utf8");
126440
- const lines = text.split("\n");
126441
- const modifiedLines = [];
126442
- for (const rawLine of lines) {
126443
- const line = rawLine.trim();
126444
- if (!line.startsWith("data:")) {
126445
- modifiedLines.push(rawLine);
126446
- continue;
126708
+ function rewriteDataLine(rawLine) {
126709
+ const line = rawLine.trim();
126710
+ if (!line.startsWith("data:")) {
126711
+ return rawLine;
126712
+ }
126713
+ const payload = line.slice(5).trim();
126714
+ if (!payload || payload === "[DONE]") {
126715
+ return rawLine;
126716
+ }
126717
+ try {
126718
+ const parsed = JSON.parse(payload);
126719
+ let modified = false;
126720
+ if (coerceToolCallArguments(parsed)) {
126721
+ modified = true;
126447
126722
  }
126448
- const payload = line.slice(5).trim();
126449
- if (!payload || payload === "[DONE]") {
126450
- modifiedLines.push(rawLine);
126451
- continue;
126723
+ if (clientModelName !== null &&
126724
+ typeof parsed.model === "string" &&
126725
+ parsed.model !== clientModelName) {
126726
+ parsed.model = clientModelName;
126727
+ modified = true;
126452
126728
  }
126453
- try {
126454
- const parsed = JSON.parse(payload);
126455
- let modified = false;
126456
- if (coerceToolCallArguments(parsed)) {
126729
+ if (parsed.usage) {
126730
+ const usageChunk = parsed.usage;
126731
+ const effectiveContext = getEffectiveContextLength({
126732
+ contextLength,
126733
+ engineConfig,
126734
+ engineType
126735
+ });
126736
+ if (usageChunk.context_usage === undefined &&
126737
+ usageChunk.prompt_tokens !== undefined &&
126738
+ effectiveContext !== null) {
126739
+ usageChunk.context_usage = usageChunk.prompt_tokens / effectiveContext;
126457
126740
  modified = true;
126458
126741
  }
126459
- if (parsed.usage) {
126460
- const usageChunk = parsed.usage;
126461
- const effectiveContext = getEffectiveContextLength({
126462
- contextLength,
126463
- engineConfig,
126464
- engineType
126465
- });
126466
- if (usageChunk.context_usage === undefined &&
126467
- usageChunk.prompt_tokens !== undefined &&
126468
- effectiveContext !== null) {
126469
- usageChunk.context_usage = usageChunk.prompt_tokens / effectiveContext;
126470
- modified = true;
126471
- }
126472
- }
126473
- if (modified) {
126474
- modifiedLines.push("data: " + JSON.stringify(parsed));
126475
- continue;
126476
- }
126477
126742
  }
126478
- catch (_error) {
126479
- // Ignore malformed chunks
126743
+ if (modified) {
126744
+ return "data: " + JSON.stringify(parsed);
126480
126745
  }
126481
- modifiedLines.push(rawLine);
126482
126746
  }
126483
- return Buffer.from(modifiedLines.join("\n"), "utf8");
126747
+ catch (_error) {
126748
+ // Ignore malformed chunks
126749
+ }
126750
+ return rawLine;
126751
+ }
126752
+ // SSE events can split across transport chunks: hold back the trailing
126753
+ // (newline-less) fragment and only rewrite complete data lines, so a
126754
+ // partial JSON event is never forwarded with its upstream model name.
126755
+ function modifyChunkWithUsage(chunk, flush = false) {
126756
+ const combined = pendingFragment + chunk.toString("utf8");
126757
+ const lines = combined.split("\n");
126758
+ pendingFragment = flush ? "" : (lines.pop() ?? "");
126759
+ const modifiedLines = lines.map(rewriteDataLine);
126760
+ return Buffer.from(modifiedLines.length > 0 ? modifiedLines.join("\n") + (flush ? "" : "\n") : "", "utf8");
126484
126761
  }
126485
126762
  function parseUsageFromBuffer() {
126486
126763
  const lines = buffer.split("\n");
@@ -126576,6 +126853,9 @@ function monitorEngineResponseStream({ agentEngineType, body, contextLength, eng
126576
126853
  if (buffer.length > 0) {
126577
126854
  parseUsageFromBuffer();
126578
126855
  }
126856
+ if (pendingFragment.length > 0) {
126857
+ passThrough.write(modifyChunkWithUsage(Buffer.from("", "utf8"), true));
126858
+ }
126579
126859
  logEngineMetrics({
126580
126860
  agentEngineType,
126581
126861
  level: "info",
@@ -126618,9 +126898,11 @@ function monitorEngineResponseStream({ agentEngineType, body, contextLength, eng
126618
126898
  stream: passThrough
126619
126899
  };
126620
126900
  }
126621
- function monitorEngineResponseSingle({ agentEngineType, body, contextLength, engineConfig, engineType, logger, onComplete, requestBodyBytes, requestPath, requestStartedAt }) {
126901
+ function monitorEngineResponseSingle({ agentEngineType, body, contextLength, engineConfig, engineType, logger, onComplete, requestBodyBytes, requestPath, requestStartedAt, responseModelName }) {
126622
126902
  const maxUsageCaptureBytes = 1024 * 1024;
126623
126903
  const startedAt = requestStartedAt ?? Date.now();
126904
+ const clientModelName = responseModelName ?? null;
126905
+ const rewriteBuffer = clientModelName !== null ? [] : null;
126624
126906
  const passThrough = new require$$0$8.PassThrough();
126625
126907
  passThrough.on("error", (error) => {
126626
126908
  logger.error("Engine response stream error", {
@@ -126676,7 +126958,12 @@ function monitorEngineResponseSingle({ agentEngineType, body, contextLength, eng
126676
126958
  usageChunks.length = 0;
126677
126959
  }
126678
126960
  }
126679
- passThrough.write(chunkBuffer);
126961
+ if (rewriteBuffer) {
126962
+ rewriteBuffer.push(chunkBuffer);
126963
+ }
126964
+ else {
126965
+ passThrough.write(chunkBuffer);
126966
+ }
126680
126967
  });
126681
126968
  body.once("error", err => {
126682
126969
  logEngineMetrics({
@@ -126736,6 +127023,24 @@ function monitorEngineResponseSingle({ agentEngineType, body, contextLength, eng
126736
127023
  responseBytes,
126737
127024
  usage
126738
127025
  });
127026
+ if (rewriteBuffer) {
127027
+ const original = Buffer.concat(rewriteBuffer);
127028
+ let output = original;
127029
+ try {
127030
+ const parsed = JSON.parse(original.toString("utf8"));
127031
+ if (parsed !== null &&
127032
+ typeof parsed === "object" &&
127033
+ typeof parsed.model === "string" &&
127034
+ parsed.model !== clientModelName) {
127035
+ parsed.model = clientModelName;
127036
+ output = Buffer.from(JSON.stringify(parsed), "utf8");
127037
+ }
127038
+ }
127039
+ catch (_error) {
127040
+ // Non-JSON body: pass through untouched
127041
+ }
127042
+ passThrough.write(output);
127043
+ }
126739
127044
  finalize(null);
126740
127045
  passThrough.end();
126741
127046
  });
@@ -126872,7 +127177,7 @@ function applyChatTemplateKwargs({ body, model }) {
126872
127177
  }
126873
127178
  return payload;
126874
127179
  }
126875
- function serializeRequestBody$1(body, { model, path } = {}) {
127180
+ function serializeRequestBody$1(body, { model, path, servedModelName } = {}) {
126876
127181
  if (!isPlainObject$a(body)) {
126877
127182
  const payload = typeof body === "string" ? body : JSON.stringify(body);
126878
127183
  return {
@@ -126881,6 +127186,9 @@ function serializeRequestBody$1(body, { model, path } = {}) {
126881
127186
  };
126882
127187
  }
126883
127188
  let requestPayload = { ...body };
127189
+ if (servedModelName) {
127190
+ requestPayload.model = servedModelName;
127191
+ }
126884
127192
  if (path === "/v1/chat/completions" && model) {
126885
127193
  requestPayload = applyChatTemplateKwargs({ body: requestPayload, model });
126886
127194
  }
@@ -126937,8 +127245,15 @@ async function proxyEmbeddingsRoute({ body, conduitConfiguration, endpointId, lo
126937
127245
  }
126938
127246
  const engineType = conduitConfiguration.engineConfig?.type ?? null;
126939
127247
  const engineConfig = conduitConfiguration.engineConfig ?? null;
127248
+ const servedModelName = modelManager.resolvedServedModelName;
127249
+ const clientModelName = isPlainObject$a(body) && typeof body.model === "string" ? body.model : null;
126940
127250
  const serializedBody = isPlainObject$a(body)
126941
- ? JSON.stringify(body)
127251
+ ? JSON.stringify(servedModelName
127252
+ ? {
127253
+ ...body,
127254
+ model: servedModelName
127255
+ }
127256
+ : body)
126942
127257
  : typeof body === "string"
126943
127258
  ? body
126944
127259
  : JSON.stringify(body);
@@ -127056,14 +127371,27 @@ async function proxyEmbeddingsRoute({ body, conduitConfiguration, endpointId, lo
127056
127371
  onComplete: onMonitoringComplete,
127057
127372
  requestBodyBytes,
127058
127373
  requestPath: "/v1/embeddings",
127059
- requestStartedAt
127374
+ requestStartedAt,
127375
+ responseModelName: servedModelName ? clientModelName : null
127060
127376
  });
127061
127377
  return {
127062
127378
  body: monitoredResponse.stream,
127063
- headers: Object.fromEntries(response.headers.entries()),
127379
+ headers: buildProxyResponseHeaders(response.headers, Boolean(servedModelName)),
127064
127380
  status: response.status
127065
127381
  };
127066
127382
  }
127383
+ /**
127384
+ * Forwards upstream headers, dropping content-length when the response body
127385
+ * is rewritten (model-name substitution changes its length; a stale
127386
+ * content-length desyncs the stream and aborts downstream readers).
127387
+ */
127388
+ function buildProxyResponseHeaders(headers, rewriteActive) {
127389
+ const forwarded = Object.fromEntries(headers.entries());
127390
+ if (rewriteActive) {
127391
+ delete forwarded["content-length"];
127392
+ }
127393
+ return forwarded;
127394
+ }
127067
127395
  async function proxyOpenAIStreamingRoute({ body, conduitConfiguration, endpointId, logger, modelID, modelManager, path, reportMetrics, signal }) {
127068
127396
  function normalizeTokenCount(value) {
127069
127397
  if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
@@ -127081,8 +127409,10 @@ async function proxyOpenAIStreamingRoute({ body, conduitConfiguration, endpointI
127081
127409
  }
127082
127410
  const engineType = conduitConfiguration.engineConfig?.type ?? null;
127083
127411
  const engineConfig = conduitConfiguration.engineConfig ?? null;
127412
+ const servedModelName = modelManager.resolvedServedModelName;
127413
+ const clientModelName = isPlainObject$a(body) && typeof body.model === "string" ? body.model : null;
127084
127414
  const effectiveBody = modelManager.model.multimodalEnabled ? body : stripImagesFromBody(body);
127085
- const { bytes: requestBodyBytes, payload: serializedBody } = serializeRequestBody$1(effectiveBody, { model: modelManager.model, path });
127415
+ const { bytes: requestBodyBytes, payload: serializedBody } = serializeRequestBody$1(effectiveBody, { model: modelManager.model, path, servedModelName });
127086
127416
  const requestStartedAt = Date.now();
127087
127417
  const requestBody = JSON.parse(serializedBody);
127088
127418
  const streamRequested = requestBody.stream === true;
@@ -127222,7 +127552,8 @@ async function proxyOpenAIStreamingRoute({ body, conduitConfiguration, endpointI
127222
127552
  onComplete: onMonitoringComplete,
127223
127553
  requestBodyBytes,
127224
127554
  requestPath: path,
127225
- requestStartedAt
127555
+ requestStartedAt,
127556
+ responseModelName: servedModelName ? clientModelName : null
127226
127557
  })
127227
127558
  : monitorEngineResponseSingle({
127228
127559
  agentEngineType: engineType ?? "unknown",
@@ -127234,11 +127565,12 @@ async function proxyOpenAIStreamingRoute({ body, conduitConfiguration, endpointI
127234
127565
  onComplete: onMonitoringComplete,
127235
127566
  requestBodyBytes,
127236
127567
  requestPath: path,
127237
- requestStartedAt
127568
+ requestStartedAt,
127569
+ responseModelName: servedModelName ? clientModelName : null
127238
127570
  });
127239
127571
  return {
127240
127572
  body: monitoredResponse.stream,
127241
- headers: Object.fromEntries(response.headers.entries()),
127573
+ headers: buildProxyResponseHeaders(response.headers, Boolean(servedModelName)),
127242
127574
  status: response.status
127243
127575
  };
127244
127576
  }
@@ -158743,13 +159075,16 @@ async function detectDockerVersion() {
158743
159075
  * Flattens flat CLI extra-arg tokens into [arg, value] pairs, sorted by ARG NAME (ascending, ties by
158744
159076
  * value). `--flag=value` pairs split on the first `=`; a bare `--flag` consumes the following token
158745
159077
  * as its value when that token does not start with "-" (classic CLI convention); anything else
158746
- * (flags, non-strings) is dropped.
159078
+ * (flags, non-strings) is dropped. Secret-like option values are masked before pairing so they
159079
+ * never reach execution reports.
158747
159080
  */
158748
159081
  function pairExtraArgs(tokens) {
158749
159082
  if (!Array.isArray(tokens)) {
158750
159083
  return [];
158751
159084
  }
158752
- const list = tokens;
159085
+ // Non-string tokens become empty strings: they keep their position as a
159086
+ // non-consumable barrier while allowing secret masking over string tokens.
159087
+ const list = redactSecretArgs(tokens.map(token => (typeof token === "string" ? token : "")));
158753
159088
  const pairs = [];
158754
159089
  let index = 0;
158755
159090
  while (index < list.length) {
@@ -158885,9 +159220,9 @@ async function createApplication({ abortController, apiClient, configuration, lo
158885
159220
  }
158886
159221
  const reporter = new EngineExecutionReporter({
158887
159222
  buildContext: () => {
158888
- const engineType = (conduitConfiguration.engineConfig?.type ??
158889
- "llama.cpp");
159223
+ const engineType = conduitConfiguration.engineConfig?.type ?? "llama.cpp";
158890
159224
  const versions = {
159225
+ custom: null,
158891
159226
  exllamav3: machine?.exllamav3Version ?? null,
158892
159227
  "llama.cpp": machine?.llamaCppVersion ?? null,
158893
159228
  "mlx-lm": machine?.mlxlmVersion ?? null,
@@ -159087,23 +159422,33 @@ async function createApplication({ abortController, apiClient, configuration, lo
159087
159422
  logger.info("Engine start requested");
159088
159423
  reporter.beginStartup(new Date());
159089
159424
  try {
159090
- conduitStateManager.setState({
159091
- modelFileName,
159092
- modelName,
159093
- state: "downloadingModelFiles",
159094
- totalProgress: {
159095
- file: 0,
159096
- total: 0
159097
- }
159098
- });
159099
- await conduitStateReportManager.reportNow();
159425
+ if (modelManager.engine === "custom") {
159426
+ conduitStateManager.setState({
159427
+ state: "bootingEngine"
159428
+ });
159429
+ await conduitStateReportManager.reportNow();
159430
+ }
159431
+ else {
159432
+ conduitStateManager.setState({
159433
+ modelFileName,
159434
+ modelName,
159435
+ state: "downloadingModelFiles",
159436
+ totalProgress: {
159437
+ file: 0,
159438
+ total: 0
159439
+ }
159440
+ });
159441
+ await conduitStateReportManager.reportNow();
159442
+ }
159100
159443
  await modelManager.prepare({
159101
159444
  onDownloadProgress: reportDownloadProgress
159102
159445
  });
159103
- conduitStateManager.setState({
159104
- state: "bootingEngine"
159105
- });
159106
- await conduitStateReportManager.reportNow();
159446
+ if (modelManager.engine !== "custom") {
159447
+ conduitStateManager.setState({
159448
+ state: "bootingEngine"
159449
+ });
159450
+ await conduitStateReportManager.reportNow();
159451
+ }
159107
159452
  await modelManager.start();
159108
159453
  }
159109
159454
  catch (error) {
@@ -159374,7 +159719,11 @@ function createModelManagerFromConfig(conduitConfiguration, configuration, logge
159374
159719
  return new ModelManager({
159375
159720
  contextLength: conduitConfiguration.contextLength ?? null,
159376
159721
  engineConfig: engineConfig
159377
- ? { extraArgs: engineConfig.extraArgs, type: engineConfig.type }
159722
+ ? {
159723
+ baseUrl: engineConfig.baseUrl ?? null,
159724
+ extraArgs: engineConfig.extraArgs,
159725
+ type: engineConfig.type
159726
+ }
159378
159727
  : null,
159379
159728
  enginePort: configuration.enginePort,
159380
159729
  engineType: engineConfig?.type ?? "llama.cpp",
@@ -159385,7 +159734,11 @@ function createModelManagerFromConfig(conduitConfiguration, configuration, logge
159385
159734
  }
159386
159735
  function getConduitModelFileName(configuration) {
159387
159736
  const { source } = configuration.targetModel;
159388
- return source.type === "huggingface" ? source.slug : source.irid;
159737
+ if (source.type === "huggingface")
159738
+ return source.slug;
159739
+ if (source.type === "storage")
159740
+ return source.irid;
159741
+ return configuration.targetModel.id;
159389
159742
  }
159390
159743
  function getConduitModelName(configuration) {
159391
159744
  return configuration.targetModel.id;
@@ -364053,16 +364406,15 @@ function buildSourceCreateBody(options) {
364053
364406
  if (!options.engine) {
364054
364407
  throw new Error("--engine is required (engine ID from `engine create`)");
364055
364408
  }
364056
- if (!options.model) {
364057
- throw new Error("--model is required (model ID from `models create`)");
364058
- }
364059
364409
  validateULID({ flagName: "--engine", value: options.engine });
364060
- validateULID({ flagName: "--model", value: options.model });
364061
364410
  const body = {
364062
364411
  engineId: options.engine,
364063
- modelID: options.model,
364412
+ modelID: options.model ?? null,
364064
364413
  name: options.name
364065
364414
  };
364415
+ if (options.model !== undefined) {
364416
+ validateULID({ flagName: "--model", value: options.model });
364417
+ }
364066
364418
  if (options.quant !== undefined) {
364067
364419
  validateQuant(options.quant);
364068
364420
  body.quantizationLabel = options.quant;
@@ -364104,7 +364456,7 @@ function registerSourceCommands({ program }) {
364104
364456
  .option("--engine <id>", "Engine ID to run this source with (required)")
364105
364457
  .option("--id <ulid>", "Target an existing source by ID (requires --update)")
364106
364458
  .option("--key <value>", "API key (required, no environment variable fallback)")
364107
- .option("--model <id>", "Model ID to serve (required)")
364459
+ .option("--model <id>", "Model ID to serve (required unless the engine type is custom)")
364108
364460
  .option("--name <name>", "Source name (matched by --update)")
364109
364461
  .option("--quant <label>", "Quantization variant label (eg Q4_K_M)")
364110
364462
  .option("--update", "Update an existing source matched by name (or --id) instead of erroring")