@axlsdk/axl 0.20.1 → 0.21.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/README.md CHANGED
@@ -71,6 +71,13 @@ Presets: `openrouter`, `azure`, `xai`, `deepseek`, `mistral`, `groq`, `bedrock`,
71
71
 
72
72
  Each provider also accepts an opt-in `rateLimit` (`{ maxConcurrent?, minIntervalMs?, acquireTimeoutMs? }`) for proactive client-side pacing on top of the automatic 429/503/529 backoff — useful when a large fan-out (e.g. an eval) shares one API key. It caps in-flight request concurrency (not token throughput) for that provider's chat calls. See [Providers → Rate limiting](../../docs/providers.md#rate-limiting-opt-in).
73
73
 
74
+ Built-in providers require HTTPS for remote endpoints and never follow redirects.
75
+ Literal loopback HTTP remains automatic for local inference; Docker service names,
76
+ private-network URLs, and other remote HTTP endpoints require the provider-local
77
+ `dangerouslyAllowInsecureHttp: true` acknowledgement. Keep Axl and proprietary
78
+ system prompts on your trusted backend—see
79
+ [Prompt confidentiality](../../docs/security.md#prompt-confidentiality-and-the-trusted-backend).
80
+
74
81
  State store options: `'memory'` (default), `'sqlite'` (requires `better-sqlite3`), or a `RedisStore` instance for multi-process deployments. See [State Stores](#state-stores).
75
82
 
76
83
  ### Tools, Agents, and Workflows
package/dist/index.cjs CHANGED
@@ -705,7 +705,7 @@ async function fetchWithRetry(input, init, opts) {
705
705
  for (let attempt = 0; ; attempt++) {
706
706
  let res;
707
707
  try {
708
- res = await fetch(input, init);
708
+ res = await fetch(input, { ...init, redirect: "manual" });
709
709
  } catch (err) {
710
710
  if (isAbortError2(err, init?.signal ?? void 0)) throw err;
711
711
  if (attempt >= maxRetries) {
@@ -911,6 +911,38 @@ function isBuiltinTablePricingEligible(profile, context) {
911
911
  return typeof effectiveTier === "string" && policy.supportedServiceTiers.includes(effectiveTier);
912
912
  }
913
913
 
914
+ // src/http-transport.ts
915
+ function assertSafeProviderBaseUrl(baseUrl, surface, dangerouslyAllowInsecureHttp = false) {
916
+ assertSafeHttpEndpoint(baseUrl, surface, dangerouslyAllowInsecureHttp, "prompts and credentials");
917
+ }
918
+ function assertSafeHttpEndpoint(endpoint, surface, dangerouslyAllowInsecureHttp = false, exposedData = "sensitive data") {
919
+ let url;
920
+ try {
921
+ url = new URL(endpoint);
922
+ } catch {
923
+ throw unsafeTransportError(surface, "a valid absolute HTTP(S) URL", exposedData);
924
+ }
925
+ if (url.protocol === "https:") return;
926
+ if (url.protocol !== "http:") {
927
+ throw unsafeTransportError(surface, "an HTTPS URL", exposedData);
928
+ }
929
+ if (dangerouslyAllowInsecureHttp || isLiteralLoopbackHost(url.hostname)) return;
930
+ throw unsafeTransportError(
931
+ surface,
932
+ "an HTTPS URL, or set dangerouslyAllowInsecureHttp: true only for a deliberately insecure endpoint",
933
+ exposedData
934
+ );
935
+ }
936
+ function isLiteralLoopbackHost(hostname) {
937
+ return hostname === "localhost" || hostname === "localhost." || /^127(?:\.\d{1,3}){3}$/.test(hostname) || hostname === "[::1]";
938
+ }
939
+ function unsafeTransportError(surface, remediation, exposedData) {
940
+ return new AxlError(
941
+ "UNSAFE_TRANSPORT",
942
+ `${surface} requires ${remediation}. Remote HTTP can expose ${exposedData} in transit.`
943
+ );
944
+ }
945
+
914
946
  // src/providers/openai-compatible.ts
915
947
  function resolvePerModel(value, model, fallback) {
916
948
  if (value === void 0) return fallback;
@@ -1054,6 +1086,11 @@ var OpenAICompatibleProvider = class {
1054
1086
  const envBase = p.envBaseUrl ? process.env[p.envBaseUrl] : void 0;
1055
1087
  const explicitBase = options.baseUrl ?? envBase;
1056
1088
  this.baseUrl = (explicitBase ?? p.defaultBaseUrl).replace(/\/$/, "");
1089
+ assertSafeProviderBaseUrl(
1090
+ this.baseUrl,
1091
+ `${p.label ?? p.name} provider`,
1092
+ options.dangerouslyAllowInsecureHttp
1093
+ );
1057
1094
  this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
1058
1095
  const label = p.label ?? p.name;
1059
1096
  if (p.requireExplicitBaseUrl && explicitBase === void 0) {
@@ -1954,6 +1991,11 @@ var OpenAIResponsesProvider = class {
1954
1991
  constructor(options = {}) {
1955
1992
  this.apiKeySource = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
1956
1993
  this.baseUrl = (options.baseUrl ?? process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/$/, "");
1994
+ assertSafeProviderBaseUrl(
1995
+ this.baseUrl,
1996
+ "OpenAI Responses provider",
1997
+ options.dangerouslyAllowInsecureHttp
1998
+ );
1957
1999
  this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
1958
2000
  if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
1959
2001
  throw new Error("OpenAI API key is required. Set OPENAI_API_KEY or pass apiKey in options.");
@@ -2709,6 +2751,11 @@ var AnthropicProvider = class {
2709
2751
  constructor(options = {}) {
2710
2752
  this.apiKeySource = options.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "";
2711
2753
  this.baseUrl = (options.baseUrl ?? "https://api.anthropic.com/v1").replace(/\/$/, "");
2754
+ assertSafeProviderBaseUrl(
2755
+ this.baseUrl,
2756
+ "Anthropic provider",
2757
+ options.dangerouslyAllowInsecureHttp
2758
+ );
2712
2759
  this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
2713
2760
  if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
2714
2761
  throw new Error(
@@ -3399,6 +3446,11 @@ var GeminiProvider = class {
3399
3446
  /\/$/,
3400
3447
  ""
3401
3448
  );
3449
+ assertSafeProviderBaseUrl(
3450
+ this.baseUrl,
3451
+ "Google provider",
3452
+ options.dangerouslyAllowInsecureHttp
3453
+ );
3402
3454
  this.governor = options.rateLimit ? new RateLimiter(options.rateLimit) : void 0;
3403
3455
  if (typeof this.apiKeySource === "string" && !this.apiKeySource) {
3404
3456
  throw new Error("Google API key is required. Set GOOGLE_API_KEY or pass apiKey in options.");
@@ -4286,6 +4338,7 @@ var builtinFactories = {
4286
4338
  return new OpenAIProvider({
4287
4339
  apiKey: opts.apiKey,
4288
4340
  baseUrl: opts.baseUrl,
4341
+ dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
4289
4342
  rateLimit: opts.rateLimit
4290
4343
  });
4291
4344
  },
@@ -4298,6 +4351,7 @@ var builtinFactories = {
4298
4351
  return new OpenAIResponsesProvider({
4299
4352
  apiKey: opts.apiKey,
4300
4353
  baseUrl: opts.baseUrl,
4354
+ dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
4301
4355
  rateLimit: opts.rateLimit
4302
4356
  });
4303
4357
  },
@@ -4306,6 +4360,7 @@ var builtinFactories = {
4306
4360
  return new AnthropicProvider({
4307
4361
  apiKey: opts.apiKey,
4308
4362
  baseUrl: opts.baseUrl,
4363
+ dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
4309
4364
  rateLimit: opts.rateLimit
4310
4365
  });
4311
4366
  },
@@ -4314,6 +4369,7 @@ var builtinFactories = {
4314
4369
  return new GeminiProvider({
4315
4370
  apiKey: opts.apiKey,
4316
4371
  baseUrl: opts.baseUrl,
4372
+ dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
4317
4373
  rateLimit: opts.rateLimit
4318
4374
  });
4319
4375
  }
@@ -4325,6 +4381,7 @@ function presetFactory(profile) {
4325
4381
  profile,
4326
4382
  apiKey: opts.apiKey,
4327
4383
  baseUrl: opts.baseUrl,
4384
+ dangerouslyAllowInsecureHttp: opts.dangerouslyAllowInsecureHttp,
4328
4385
  authHeader: opts.authHeader,
4329
4386
  rateLimit: opts.rateLimit
4330
4387
  });
@@ -11335,6 +11392,14 @@ var HttpMcpClient = class {
11335
11392
  this.config = config;
11336
11393
  this.name = config.name;
11337
11394
  this.baseUrl = (config.uri ?? "").replace(/\/$/, "");
11395
+ if (this.baseUrl) {
11396
+ assertSafeHttpEndpoint(
11397
+ this.baseUrl,
11398
+ `MCP server "${this.name}"`,
11399
+ config.dangerouslyAllowInsecureHttp,
11400
+ "tool arguments and results"
11401
+ );
11402
+ }
11338
11403
  }
11339
11404
  name;
11340
11405
  tools = [];
@@ -11364,7 +11429,9 @@ var HttpMcpClient = class {
11364
11429
  method: "POST",
11365
11430
  headers: { "Content-Type": "application/json" },
11366
11431
  body: JSON.stringify(request),
11367
- signal: AbortSignal.timeout(timeoutMs)
11432
+ signal: AbortSignal.timeout(timeoutMs),
11433
+ // Tool calls can be non-idempotent: never forward or retry their body.
11434
+ redirect: "manual"
11368
11435
  });
11369
11436
  if (!res.ok) {
11370
11437
  throw new Error(`MCP HTTP error (${res.status}): ${await res.text()}`);
@@ -14156,6 +14223,11 @@ var OpenAIEmbedder = class {
14156
14223
  }
14157
14224
  this.model = options.model ?? "text-embedding-3-small";
14158
14225
  this.baseUrl = options.baseUrl ?? "https://api.openai.com";
14226
+ assertSafeProviderBaseUrl(
14227
+ this.baseUrl,
14228
+ "OpenAI embedder",
14229
+ options.dangerouslyAllowInsecureHttp
14230
+ );
14159
14231
  this.dimensions = options.dimensions ?? 1536;
14160
14232
  }
14161
14233
  async embed(texts, signal) {