@infersec/conduit 1.114.0 → 1.115.0

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.js CHANGED
@@ -125777,8 +125777,10 @@ class ModelManager extends EventEmitter {
125777
125777
  contextLength;
125778
125778
  logger;
125779
125779
  discoveredModelNames = [];
125780
+ customProbeCount = 0;
125780
125781
  engineProcess = null;
125781
125782
  healthPollInterval = null;
125783
+ lastCustomReadinessReport = null;
125782
125784
  lastEngineError = null;
125783
125785
  lifecycleState = "stopped";
125784
125786
  downloadLockHandle = null;
@@ -126016,7 +126018,9 @@ class ModelManager extends EventEmitter {
126016
126018
  if (this.engine !== "custom")
126017
126019
  return null;
126018
126020
  const baseUrl = this.engineConfig?.baseUrl;
126019
- return typeof baseUrl === "string" && baseUrl.length > 0 ? baseUrl : null;
126021
+ if (typeof baseUrl !== "string" || baseUrl.length === 0)
126022
+ return null;
126023
+ return baseUrl.replace(/\/+$/, "");
126020
126024
  }
126021
126025
  async checkEngineReadiness() {
126022
126026
  switch (this.engine) {
@@ -126042,17 +126046,32 @@ class ModelManager extends EventEmitter {
126042
126046
  async checkCustomReadiness() {
126043
126047
  const baseURL = this.customBaseURL;
126044
126048
  if (!baseURL) {
126049
+ this.reportCustomReadinessChange("unreachable", "", "no base URL configured");
126045
126050
  return "unreachable";
126046
126051
  }
126052
+ const probeURL = joinURL(baseURL, "/v1/models");
126053
+ this.customProbeCount++;
126047
126054
  try {
126048
- const response = await undiciExports.fetch(joinURL(baseURL, "/v1/models"), {
126055
+ const response = await undiciExports.fetch(probeURL, {
126049
126056
  method: "GET",
126050
126057
  signal: AbortSignal.timeout(5000)
126051
126058
  });
126052
126059
  if (response.status === 503) {
126060
+ this.logger.debug("Custom engine probe: server loading", {
126061
+ attempt: this.customProbeCount,
126062
+ probeURL
126063
+ });
126064
+ this.reportCustomReadinessChange("loading", baseURL, "server loading (HTTP 503)");
126053
126065
  return "loading";
126054
126066
  }
126055
126067
  if (!response.ok) {
126068
+ const reason = `HTTP ${response.status} from /v1/models`;
126069
+ this.logger.debug("Custom engine probe: not ready", {
126070
+ attempt: this.customProbeCount,
126071
+ probeURL,
126072
+ reason
126073
+ });
126074
+ this.reportCustomReadinessChange("unreachable", baseURL, reason);
126056
126075
  return "unreachable";
126057
126076
  }
126058
126077
  const payload = (await response.json());
@@ -126066,24 +126085,56 @@ class ModelManager extends EventEmitter {
126066
126085
  })
126067
126086
  .filter((id) => id !== null);
126068
126087
  if (modelIDs.length === 0) {
126069
- this.logger.warn("Custom engine endpoint exposed no models via /v1/models", {
126070
- engineBaseURL: baseURL
126088
+ this.logger.debug("Custom engine probe: no models exposed yet", {
126089
+ attempt: this.customProbeCount,
126090
+ probeURL
126071
126091
  });
126092
+ this.reportCustomReadinessChange("loading", baseURL, "/v1/models returned no models");
126072
126093
  return "loading";
126073
126094
  }
126074
126095
  if (modelIDs.join("\n") !== this.discoveredModelNames.join("\n")) {
126096
+ this.discoveredModelNames = modelIDs;
126075
126097
  this.logger.info("Discovered models on custom engine endpoint", {
126076
126098
  engineBaseURL: baseURL,
126077
- models: modelIDs
126099
+ models: modelIDs,
126100
+ selectedModel: modelIDs[0]
126078
126101
  });
126079
- this.discoveredModelNames = modelIDs;
126080
126102
  }
126081
126103
  return "ready";
126082
126104
  }
126083
- catch (_error) {
126105
+ catch (error) {
126106
+ const reason = asError(error).message;
126107
+ this.logger.debug("Custom engine probe: request failed", {
126108
+ attempt: this.customProbeCount,
126109
+ probeURL,
126110
+ reason
126111
+ });
126112
+ this.reportCustomReadinessChange("unreachable", baseURL, reason);
126084
126113
  return "unreachable";
126085
126114
  }
126086
126115
  }
126116
+ reportCustomReadinessChange(readiness, baseURL, reason) {
126117
+ const key = `${readiness}:${reason}`;
126118
+ if (key === this.lastCustomReadinessReport) {
126119
+ // Same outcome as the last report: surface a heartbeat every 15
126120
+ // attempts (~30s while booting) so progress stays visible.
126121
+ if (this.customProbeCount % 15 === 0) {
126122
+ this.logger.info("Still waiting for custom engine endpoint", {
126123
+ attempt: this.customProbeCount,
126124
+ engineBaseURL: baseURL,
126125
+ reason
126126
+ });
126127
+ }
126128
+ return;
126129
+ }
126130
+ this.lastCustomReadinessReport = key;
126131
+ this.logger.warn(readiness === "loading"
126132
+ ? "Custom engine endpoint is loading — waiting for /v1/models to expose a model"
126133
+ : "Custom engine endpoint unreachable — verify the server is running and reachable from conduit", {
126134
+ engineBaseURL: baseURL,
126135
+ reason
126136
+ });
126137
+ }
126087
126138
  async checkGenericHealthReadiness() {
126088
126139
  try {
126089
126140
  const response = await undiciExports.fetch(joinURL(`http://localhost:${this.enginePort}`, "/health"), {
@@ -126142,6 +126193,11 @@ class ModelManager extends EventEmitter {
126142
126193
  const maxWaitMs = Number.parseInt(process.env.ENGINE_STARTUP_TIMEOUT_MS ?? "", 10) || 15 * 60 * 1000;
126143
126194
  const pollIntervalMs = 2000;
126144
126195
  const start = Date.now();
126196
+ if (this.engine === "custom") {
126197
+ this.logger.info(`Connecting to external OpenAI-compatible server at ${this.customBaseURL ?? "(no base URL configured)"} — serving begins once /v1/models exposes a model`, {
126198
+ engineBaseURL: this.customBaseURL ?? ""
126199
+ });
126200
+ }
126145
126201
  while (Date.now() - start < maxWaitMs) {
126146
126202
  if (this.lifecycleState === "stopping" || this.stopRequested) {
126147
126203
  throw new Error("LLM engine startup interrupted by stop request");
@@ -126162,6 +126218,13 @@ class ModelManager extends EventEmitter {
126162
126218
  await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
126163
126219
  }
126164
126220
  const stderrTail = this.engineProcess?.stderr?.slice(-1e3);
126221
+ if (this.engine === "custom") {
126222
+ const baseURL = this.customBaseURL ?? "(missing)";
126223
+ const discovered = this.discoveredModelNames;
126224
+ throw new Error(discovered.length > 0
126225
+ ? `Custom engine endpoint at ${baseURL} exposed models (${discovered.join(", ")}) but never reported ready within ${Math.round(maxWaitMs / 1000)}s`
126226
+ : `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)`);
126227
+ }
126165
126228
  throw new Error(stderrTail
126166
126229
  ? `LLM engine failed readiness checks within timeout. Last engine output:\n${stderrTail}`
126167
126230
  : "LLM engine failed readiness checks within timeout");
@@ -127299,10 +127362,22 @@ async function proxyEmbeddingsRoute({ body, conduitConfiguration, endpointId, lo
127299
127362
  });
127300
127363
  return {
127301
127364
  body: monitoredResponse.stream,
127302
- headers: Object.fromEntries(response.headers.entries()),
127365
+ headers: buildProxyResponseHeaders(response.headers, Boolean(servedModelName)),
127303
127366
  status: response.status
127304
127367
  };
127305
127368
  }
127369
+ /**
127370
+ * Forwards upstream headers, dropping content-length when the response body
127371
+ * is rewritten (model-name substitution changes its length; a stale
127372
+ * content-length desyncs the stream and aborts downstream readers).
127373
+ */
127374
+ function buildProxyResponseHeaders(headers, rewriteActive) {
127375
+ const forwarded = Object.fromEntries(headers.entries());
127376
+ if (rewriteActive) {
127377
+ delete forwarded["content-length"];
127378
+ }
127379
+ return forwarded;
127380
+ }
127306
127381
  async function proxyOpenAIStreamingRoute({ body, conduitConfiguration, endpointId, logger, modelID, modelManager, path, reportMetrics, signal }) {
127307
127382
  function normalizeTokenCount(value) {
127308
127383
  if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
@@ -127481,7 +127556,7 @@ async function proxyOpenAIStreamingRoute({ body, conduitConfiguration, endpointI
127481
127556
  });
127482
127557
  return {
127483
127558
  body: monitoredResponse.stream,
127484
- headers: Object.fromEntries(response.headers.entries()),
127559
+ headers: buildProxyResponseHeaders(response.headers, Boolean(servedModelName)),
127485
127560
  status: response.status
127486
127561
  };
127487
127562
  }
@@ -139118,23 +139193,33 @@ async function createApplication({ abortController, apiClient, configuration, lo
139118
139193
  logger.info("Engine start requested");
139119
139194
  reporter.beginStartup(new Date());
139120
139195
  try {
139121
- conduitStateManager.setState({
139122
- modelFileName,
139123
- modelName,
139124
- state: "downloadingModelFiles",
139125
- totalProgress: {
139126
- file: 0,
139127
- total: 0
139128
- }
139129
- });
139130
- await conduitStateReportManager.reportNow();
139196
+ if (modelManager.engine === "custom") {
139197
+ conduitStateManager.setState({
139198
+ state: "bootingEngine"
139199
+ });
139200
+ await conduitStateReportManager.reportNow();
139201
+ }
139202
+ else {
139203
+ conduitStateManager.setState({
139204
+ modelFileName,
139205
+ modelName,
139206
+ state: "downloadingModelFiles",
139207
+ totalProgress: {
139208
+ file: 0,
139209
+ total: 0
139210
+ }
139211
+ });
139212
+ await conduitStateReportManager.reportNow();
139213
+ }
139131
139214
  await modelManager.prepare({
139132
139215
  onDownloadProgress: reportDownloadProgress
139133
139216
  });
139134
- conduitStateManager.setState({
139135
- state: "bootingEngine"
139136
- });
139137
- await conduitStateReportManager.reportNow();
139217
+ if (modelManager.engine !== "custom") {
139218
+ conduitStateManager.setState({
139219
+ state: "bootingEngine"
139220
+ });
139221
+ await conduitStateReportManager.reportNow();
139222
+ }
139138
139223
  await modelManager.start();
139139
139224
  }
139140
139225
  catch (error) {
@@ -344092,16 +344177,15 @@ function buildSourceCreateBody(options) {
344092
344177
  if (!options.engine) {
344093
344178
  throw new Error("--engine is required (engine ID from `engine create`)");
344094
344179
  }
344095
- if (!options.model) {
344096
- throw new Error("--model is required (model ID from `models create`)");
344097
- }
344098
344180
  validateULID({ flagName: "--engine", value: options.engine });
344099
- validateULID({ flagName: "--model", value: options.model });
344100
344181
  const body = {
344101
344182
  engineId: options.engine,
344102
- modelID: options.model,
344183
+ modelID: options.model ?? null,
344103
344184
  name: options.name
344104
344185
  };
344186
+ if (options.model !== undefined) {
344187
+ validateULID({ flagName: "--model", value: options.model });
344188
+ }
344105
344189
  if (options.quant !== undefined) {
344106
344190
  validateQuant(options.quant);
344107
344191
  body.quantizationLabel = options.quant;
@@ -344143,7 +344227,7 @@ function registerSourceCommands({ program }) {
344143
344227
  .option("--engine <id>", "Engine ID to run this source with (required)")
344144
344228
  .option("--id <ulid>", "Target an existing source by ID (requires --update)")
344145
344229
  .option("--key <value>", "API key (required, no environment variable fallback)")
344146
- .option("--model <id>", "Model ID to serve (required)")
344230
+ .option("--model <id>", "Model ID to serve (required unless the engine type is custom)")
344147
344231
  .option("--name <name>", "Source name (matched by --update)")
344148
344232
  .option("--quant <label>", "Quantization variant label (eg Q4_K_M)")
344149
344233
  .option("--update", "Update an existing source matched by name (or --id) instead of erroring")
package/dist/cli.sea.cjs CHANGED
@@ -125791,8 +125791,10 @@ class ModelManager extends EventEmitter {
125791
125791
  contextLength;
125792
125792
  logger;
125793
125793
  discoveredModelNames = [];
125794
+ customProbeCount = 0;
125794
125795
  engineProcess = null;
125795
125796
  healthPollInterval = null;
125797
+ lastCustomReadinessReport = null;
125796
125798
  lastEngineError = null;
125797
125799
  lifecycleState = "stopped";
125798
125800
  downloadLockHandle = null;
@@ -126030,7 +126032,9 @@ class ModelManager extends EventEmitter {
126030
126032
  if (this.engine !== "custom")
126031
126033
  return null;
126032
126034
  const baseUrl = this.engineConfig?.baseUrl;
126033
- return typeof baseUrl === "string" && baseUrl.length > 0 ? baseUrl : null;
126035
+ if (typeof baseUrl !== "string" || baseUrl.length === 0)
126036
+ return null;
126037
+ return baseUrl.replace(/\/+$/, "");
126034
126038
  }
126035
126039
  async checkEngineReadiness() {
126036
126040
  switch (this.engine) {
@@ -126056,17 +126060,32 @@ class ModelManager extends EventEmitter {
126056
126060
  async checkCustomReadiness() {
126057
126061
  const baseURL = this.customBaseURL;
126058
126062
  if (!baseURL) {
126063
+ this.reportCustomReadinessChange("unreachable", "", "no base URL configured");
126059
126064
  return "unreachable";
126060
126065
  }
126066
+ const probeURL = joinURL(baseURL, "/v1/models");
126067
+ this.customProbeCount++;
126061
126068
  try {
126062
- const response = await undiciExports.fetch(joinURL(baseURL, "/v1/models"), {
126069
+ const response = await undiciExports.fetch(probeURL, {
126063
126070
  method: "GET",
126064
126071
  signal: AbortSignal.timeout(5000)
126065
126072
  });
126066
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)");
126067
126079
  return "loading";
126068
126080
  }
126069
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);
126070
126089
  return "unreachable";
126071
126090
  }
126072
126091
  const payload = (await response.json());
@@ -126080,24 +126099,56 @@ class ModelManager extends EventEmitter {
126080
126099
  })
126081
126100
  .filter((id) => id !== null);
126082
126101
  if (modelIDs.length === 0) {
126083
- this.logger.warn("Custom engine endpoint exposed no models via /v1/models", {
126084
- engineBaseURL: baseURL
126102
+ this.logger.debug("Custom engine probe: no models exposed yet", {
126103
+ attempt: this.customProbeCount,
126104
+ probeURL
126085
126105
  });
126106
+ this.reportCustomReadinessChange("loading", baseURL, "/v1/models returned no models");
126086
126107
  return "loading";
126087
126108
  }
126088
126109
  if (modelIDs.join("\n") !== this.discoveredModelNames.join("\n")) {
126110
+ this.discoveredModelNames = modelIDs;
126089
126111
  this.logger.info("Discovered models on custom engine endpoint", {
126090
126112
  engineBaseURL: baseURL,
126091
- models: modelIDs
126113
+ models: modelIDs,
126114
+ selectedModel: modelIDs[0]
126092
126115
  });
126093
- this.discoveredModelNames = modelIDs;
126094
126116
  }
126095
126117
  return "ready";
126096
126118
  }
126097
- catch (_error) {
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);
126098
126127
  return "unreachable";
126099
126128
  }
126100
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
+ }
126101
126152
  async checkGenericHealthReadiness() {
126102
126153
  try {
126103
126154
  const response = await undiciExports.fetch(joinURL(`http://localhost:${this.enginePort}`, "/health"), {
@@ -126156,6 +126207,11 @@ class ModelManager extends EventEmitter {
126156
126207
  const maxWaitMs = Number.parseInt(process.env.ENGINE_STARTUP_TIMEOUT_MS ?? "", 10) || 15 * 60 * 1000;
126157
126208
  const pollIntervalMs = 2000;
126158
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
+ }
126159
126215
  while (Date.now() - start < maxWaitMs) {
126160
126216
  if (this.lifecycleState === "stopping" || this.stopRequested) {
126161
126217
  throw new Error("LLM engine startup interrupted by stop request");
@@ -126176,6 +126232,13 @@ class ModelManager extends EventEmitter {
126176
126232
  await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
126177
126233
  }
126178
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
+ }
126179
126242
  throw new Error(stderrTail
126180
126243
  ? `LLM engine failed readiness checks within timeout. Last engine output:\n${stderrTail}`
126181
126244
  : "LLM engine failed readiness checks within timeout");
@@ -127313,10 +127376,22 @@ async function proxyEmbeddingsRoute({ body, conduitConfiguration, endpointId, lo
127313
127376
  });
127314
127377
  return {
127315
127378
  body: monitoredResponse.stream,
127316
- headers: Object.fromEntries(response.headers.entries()),
127379
+ headers: buildProxyResponseHeaders(response.headers, Boolean(servedModelName)),
127317
127380
  status: response.status
127318
127381
  };
127319
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
+ }
127320
127395
  async function proxyOpenAIStreamingRoute({ body, conduitConfiguration, endpointId, logger, modelID, modelManager, path, reportMetrics, signal }) {
127321
127396
  function normalizeTokenCount(value) {
127322
127397
  if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
@@ -127495,7 +127570,7 @@ async function proxyOpenAIStreamingRoute({ body, conduitConfiguration, endpointI
127495
127570
  });
127496
127571
  return {
127497
127572
  body: monitoredResponse.stream,
127498
- headers: Object.fromEntries(response.headers.entries()),
127573
+ headers: buildProxyResponseHeaders(response.headers, Boolean(servedModelName)),
127499
127574
  status: response.status
127500
127575
  };
127501
127576
  }
@@ -159347,23 +159422,33 @@ async function createApplication({ abortController, apiClient, configuration, lo
159347
159422
  logger.info("Engine start requested");
159348
159423
  reporter.beginStartup(new Date());
159349
159424
  try {
159350
- conduitStateManager.setState({
159351
- modelFileName,
159352
- modelName,
159353
- state: "downloadingModelFiles",
159354
- totalProgress: {
159355
- file: 0,
159356
- total: 0
159357
- }
159358
- });
159359
- 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
+ }
159360
159443
  await modelManager.prepare({
159361
159444
  onDownloadProgress: reportDownloadProgress
159362
159445
  });
159363
- conduitStateManager.setState({
159364
- state: "bootingEngine"
159365
- });
159366
- await conduitStateReportManager.reportNow();
159446
+ if (modelManager.engine !== "custom") {
159447
+ conduitStateManager.setState({
159448
+ state: "bootingEngine"
159449
+ });
159450
+ await conduitStateReportManager.reportNow();
159451
+ }
159367
159452
  await modelManager.start();
159368
159453
  }
159369
159454
  catch (error) {
@@ -364321,16 +364406,15 @@ function buildSourceCreateBody(options) {
364321
364406
  if (!options.engine) {
364322
364407
  throw new Error("--engine is required (engine ID from `engine create`)");
364323
364408
  }
364324
- if (!options.model) {
364325
- throw new Error("--model is required (model ID from `models create`)");
364326
- }
364327
364409
  validateULID({ flagName: "--engine", value: options.engine });
364328
- validateULID({ flagName: "--model", value: options.model });
364329
364410
  const body = {
364330
364411
  engineId: options.engine,
364331
- modelID: options.model,
364412
+ modelID: options.model ?? null,
364332
364413
  name: options.name
364333
364414
  };
364415
+ if (options.model !== undefined) {
364416
+ validateULID({ flagName: "--model", value: options.model });
364417
+ }
364334
364418
  if (options.quant !== undefined) {
364335
364419
  validateQuant(options.quant);
364336
364420
  body.quantizationLabel = options.quant;
@@ -364372,7 +364456,7 @@ function registerSourceCommands({ program }) {
364372
364456
  .option("--engine <id>", "Engine ID to run this source with (required)")
364373
364457
  .option("--id <ulid>", "Target an existing source by ID (requires --update)")
364374
364458
  .option("--key <value>", "API key (required, no environment variable fallback)")
364375
- .option("--model <id>", "Model ID to serve (required)")
364459
+ .option("--model <id>", "Model ID to serve (required unless the engine type is custom)")
364376
364460
  .option("--name <name>", "Source name (matched by --update)")
364377
364461
  .option("--quant <label>", "Quantization variant label (eg Q4_K_M)")
364378
364462
  .option("--update", "Update an existing source matched by name (or --id) instead of erroring")
@@ -19,8 +19,10 @@ export declare class ModelManager extends EventEmitter<ModelManagerEvents> {
19
19
  readonly contextLength: number | null;
20
20
  protected readonly logger: Logger;
21
21
  private discoveredModelNames;
22
+ private customProbeCount;
22
23
  private engineProcess;
23
24
  private healthPollInterval;
25
+ private lastCustomReadinessReport;
24
26
  private lastEngineError;
25
27
  private lifecycleState;
26
28
  private downloadLockHandle;
@@ -55,6 +57,7 @@ export declare class ModelManager extends EventEmitter<ModelManagerEvents> {
55
57
  private get customBaseURL();
56
58
  private checkEngineReadiness;
57
59
  private checkCustomReadiness;
60
+ private reportCustomReadinessChange;
58
61
  private checkGenericHealthReadiness;
59
62
  private checkLlamacppReadiness;
60
63
  private checkVLLMReadiness;
@@ -44,6 +44,12 @@ export declare function proxyEmbeddingsRoute({ body, conduitConfiguration, endpo
44
44
  status: number;
45
45
  statusText: string;
46
46
  }>;
47
+ /**
48
+ * Forwards upstream headers, dropping content-length when the response body
49
+ * is rewritten (model-name substitution changes its length; a stale
50
+ * content-length desyncs the stream and aborts downstream readers).
51
+ */
52
+ export declare function buildProxyResponseHeaders(headers: Headers, rewriteActive: boolean): Record<string, string>;
47
53
  export declare function proxyOpenAIStreamingRoute({ body, conduitConfiguration, endpointId, logger, modelID, modelManager, path, reportMetrics, signal }: {
48
54
  body: unknown;
49
55
  conduitConfiguration: InferenceAgentConfiguration;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@infersec/conduit",
3
3
  "description": "End user conduit agent for connecting local LLMs to the cloud.",
4
- "version": "1.114.0",
4
+ "version": "1.115.0",
5
5
  "bin": {
6
6
  "infersec-conduit": "./dist/cli.js"
7
7
  },