nexus-agents 2.54.1 → 2.55.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.
@@ -2792,8 +2792,108 @@ function createClaudeAdapter(config) {
2792
2792
  var PROVIDER_ENV_KEYS = {
2793
2793
  anthropic: "ANTHROPIC_API_KEY",
2794
2794
  openai: "OPENAI_API_KEY",
2795
- google: "GOOGLE_AI_API_KEY"
2795
+ google: "GOOGLE_AI_API_KEY",
2796
+ "custom-openai": "NEXUS_CUSTOM_API_KEY"
2796
2797
  };
2798
+ var CUSTOM_API_BASE_URL_ENV = "NEXUS_CUSTOM_API_BASE_URL";
2799
+ var CUSTOM_API_ALLOW_PRIVATE_ENV = "NEXUS_CUSTOM_API_ALLOW_PRIVATE";
2800
+
2801
+ // src/adapters/sdk/custom-api-validation.ts
2802
+ import { isIPv4, isIPv6 } from "net";
2803
+ function validateCustomApiBaseUrl(raw, opts = {}) {
2804
+ if (raw === void 0 || raw.trim() === "") {
2805
+ return err(
2806
+ new ConfigError(
2807
+ "Custom API base URL is required but missing. Set NEXUS_CUSTOM_API_BASE_URL or pass `baseUrl` in config."
2808
+ )
2809
+ );
2810
+ }
2811
+ let url;
2812
+ try {
2813
+ url = new URL(raw);
2814
+ } catch {
2815
+ return err(new ConfigError(`Custom API base URL is not a valid URL: ${raw}`));
2816
+ }
2817
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
2818
+ return err(
2819
+ new ConfigError(`Custom API base URL must use http or https, got "${url.protocol}" in ${raw}`)
2820
+ );
2821
+ }
2822
+ const allowPrivate = opts.allowPrivate === true || resolveAllowPrivateFromEnv();
2823
+ if (!allowPrivate) {
2824
+ const rejection = classifyPrivateHost(url.hostname);
2825
+ if (rejection !== null) {
2826
+ return err(
2827
+ new ConfigError(
2828
+ `Custom API base URL rejected (SSRF guard, reason="${rejection.reason}"): ${rejection.message}. Set ${CUSTOM_API_ALLOW_PRIVATE_ENV}=1 to bypass if the gateway runs on a trusted internal host.`
2829
+ )
2830
+ );
2831
+ }
2832
+ }
2833
+ return ok(url);
2834
+ }
2835
+ function resolveAllowPrivateFromEnv() {
2836
+ const v = process.env[CUSTOM_API_ALLOW_PRIVATE_ENV];
2837
+ return v === "1" || v === "true";
2838
+ }
2839
+ function classifyPrivateHost(hostname) {
2840
+ const stripped = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
2841
+ const normalized = stripped.toLowerCase();
2842
+ if (isIPv4(normalized)) {
2843
+ return classifyIPv4(normalized);
2844
+ }
2845
+ if (isIPv6(normalized)) {
2846
+ return classifyIPv6(normalized);
2847
+ }
2848
+ if (normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local")) {
2849
+ return {
2850
+ reason: "loopback",
2851
+ message: `hostname "${hostname}" resolves to loopback/mDNS`
2852
+ };
2853
+ }
2854
+ return null;
2855
+ }
2856
+ var IPV4_RULES = [
2857
+ { match: (a) => a === 127, reason: "loopback", label: "IPv4 loopback" },
2858
+ { match: (a) => a === 10, reason: "private_range", label: "IPv4 private (10/8)" },
2859
+ {
2860
+ match: (a, b) => a === 172 && b >= 16 && b <= 31,
2861
+ reason: "private_range",
2862
+ label: "IPv4 private (172.16/12)"
2863
+ },
2864
+ {
2865
+ match: (a, b) => a === 192 && b === 168,
2866
+ reason: "private_range",
2867
+ label: "IPv4 private (192.168/16)"
2868
+ },
2869
+ {
2870
+ match: (a, b) => a === 169 && b === 254,
2871
+ reason: "link_local",
2872
+ label: "IPv4 link-local (169.254/16 \u2014 AWS IMDS)"
2873
+ },
2874
+ { match: (a) => a === 0, reason: "reserved", label: "IPv4 reserved (0/8)" }
2875
+ ];
2876
+ function classifyIPv4(ip) {
2877
+ const parts = ip.split(".").map((p) => Number.parseInt(p, 10));
2878
+ if (parts.length !== 4 || parts.some((n) => Number.isNaN(n))) return null;
2879
+ const [a, b] = parts;
2880
+ for (const rule of IPV4_RULES) {
2881
+ if (rule.match(a, b)) {
2882
+ return { reason: rule.reason, message: `${rule.label} (${ip})` };
2883
+ }
2884
+ }
2885
+ return null;
2886
+ }
2887
+ function classifyIPv6(ip) {
2888
+ const lower = ip.toLowerCase();
2889
+ if (lower === "::1") return { reason: "loopback", message: `IPv6 loopback (${ip})` };
2890
+ if (lower.startsWith("fe80:"))
2891
+ return { reason: "link_local", message: `IPv6 link-local (${ip})` };
2892
+ if (/^fc|^fd/.test(lower)) {
2893
+ return { reason: "private_range", message: `IPv6 unique-local (${ip}, fc00::/7)` };
2894
+ }
2895
+ return null;
2896
+ }
2797
2897
 
2798
2898
  // src/adapters/sdk/sdk-adapter.ts
2799
2899
  function extractProviderFactory(mod, factoryName) {
@@ -2822,6 +2922,13 @@ function resolveApiKey(providerId, configKey) {
2822
2922
  const envVar = PROVIDER_ENV_KEYS[providerId];
2823
2923
  return process.env[envVar];
2824
2924
  }
2925
+ function resolveAndValidateCustomBaseUrl(config) {
2926
+ if (config.providerId !== "custom-openai") return void 0;
2927
+ const raw = config.baseUrl ?? process.env[CUSTOM_API_BASE_URL_ENV];
2928
+ const validated = validateCustomApiBaseUrl(raw);
2929
+ if (!validated.ok) throw validated.error;
2930
+ return validated.value.toString();
2931
+ }
2825
2932
  function mapFinishReason(reason) {
2826
2933
  switch (reason) {
2827
2934
  case "stop":
@@ -2853,6 +2960,8 @@ var SdkAdapter = class extends BaseAdapter {
2853
2960
  model;
2854
2961
  sdkFunctions;
2855
2962
  sdkConfig;
2963
+ /** Validated base URL for custom-openai provider; undefined for built-ins. */
2964
+ customBaseUrl;
2856
2965
  /** Inflight init promise for coalescing concurrent calls (Issue #1438). */
2857
2966
  initPromise;
2858
2967
  constructor(config, logger11) {
@@ -2868,6 +2977,7 @@ var SdkAdapter = class extends BaseAdapter {
2868
2977
  });
2869
2978
  this.sdkProviderId = config.providerId;
2870
2979
  this.sdkConfig = config;
2980
+ this.customBaseUrl = resolveAndValidateCustomBaseUrl(config);
2871
2981
  }
2872
2982
  /**
2873
2983
  * Lazily initialize the AI SDK model and functions.
@@ -2926,6 +3036,14 @@ var SdkAdapter = class extends BaseAdapter {
2926
3036
  const provider = factory({ apiKey });
2927
3037
  return { model: provider(this.modelId) };
2928
3038
  }
3039
+ case "custom-openai": {
3040
+ const mod = await import("@ai-sdk/openai");
3041
+ const factory = extractProviderFactory(mod, "createOpenAI");
3042
+ const opts = { apiKey };
3043
+ if (this.customBaseUrl !== void 0) opts["baseURL"] = this.customBaseUrl;
3044
+ const provider = factory(opts);
3045
+ return { model: provider(this.modelId) };
3046
+ }
2929
3047
  }
2930
3048
  }
2931
3049
  /**
@@ -3115,9 +3233,34 @@ function tryApiAdapter(config, logger11) {
3115
3233
  reason: `Using Google AI API via AI SDK (model: ${geminiModelId})`
3116
3234
  };
3117
3235
  }
3236
+ const custom = tryCustomOpenAiAdapter(logger11);
3237
+ if (custom !== null) return custom;
3118
3238
  logger11.info("No API keys available for any provider");
3119
3239
  return null;
3120
3240
  }
3241
+ function tryCustomOpenAiAdapter(logger11) {
3242
+ const customKey = resolveApiKeyFromEnv(void 0, "NEXUS_CUSTOM_API_KEY");
3243
+ const customBaseUrl = process.env["NEXUS_CUSTOM_API_BASE_URL"];
3244
+ if (customKey === void 0 || customBaseUrl === void 0 || customBaseUrl === "") {
3245
+ return null;
3246
+ }
3247
+ const customModelId = process.env["NEXUS_CUSTOM_MODEL"] ?? "gpt-4o";
3248
+ logger11.info("Using custom-openai SDK adapter", {
3249
+ model: customModelId,
3250
+ baseUrl: customBaseUrl
3251
+ });
3252
+ return {
3253
+ adapter: new SdkAdapter({
3254
+ providerId: "custom-openai",
3255
+ modelId: customModelId,
3256
+ apiKey: customKey,
3257
+ baseUrl: customBaseUrl
3258
+ }),
3259
+ source: "api",
3260
+ name: "custom-openai",
3261
+ reason: `Using custom OpenAI-compatible gateway at ${customBaseUrl} (model: ${customModelId})`
3262
+ };
3263
+ }
3121
3264
  async function selectCliFirst(config, logger11, cache) {
3122
3265
  const cliResult = await tryCliAdapter(config, logger11, cache);
3123
3266
  if (cliResult !== null) return cliResult;
@@ -12936,4 +13079,4 @@ export {
12936
13079
  CONSENSUS_VOTE_OUTPUT_SCHEMA,
12937
13080
  registerConsensusVoteTool
12938
13081
  };
12939
- //# sourceMappingURL=chunk-LSHIG4SR.js.map
13082
+ //# sourceMappingURL=chunk-HQ43NDJW.js.map