@axlsdk/axl 0.22.1 → 0.22.2

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
@@ -67,7 +67,7 @@ agent({ model: 'deepseek:deepseek-reasoner' }); // DEEPSEEK_API_KEY
67
67
  agent({ model: 'ollama:llama3' }); // local — no key, $0
68
68
  ```
69
69
 
70
- Presets: `openrouter`, `azure`, `xai`, `deepseek`, `mistral`, `groq`, `bedrock`, and self-hosted `ollama` / `vllm` / `lmstudio` / `llamacpp` / `sglang`. The unified `effort` knob and per-call cost tracking work across them. OpenRouter passes image input through to the selected model/route, but Axl does not claim every catalog model supports images, tools, or transcription. Build your own by cloning a `ProviderProfile`; see [provider details](../../docs/providers.md#openai-compatible-providers--presets) and [multimodal input](../../docs/multimodal-input.md).
70
+ Presets: `openrouter`, `azure`, `xai`, `deepseek`, `mistral`, `groq`, `bedrock`, and self-hosted `ollama` / `vllm` / `lmstudio` / `llamacpp` / `sglang`. The unified `effort` knob and per-call cost tracking work across them. OpenAI Responses, Anthropic, Google, and OpenRouter accept image input without hard-coding a model catalog; the selected upstream model remains authoritative. Completed-file transcription is a separate, narrower contract. Build your own compatible provider by cloning a `ProviderProfile`; see [provider details](../../docs/providers.md) and [multimodal input](../../docs/multimodal-input.md).
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
 
package/dist/index.cjs CHANGED
@@ -1427,7 +1427,8 @@ var OpenAICompatibleProvider = class {
1427
1427
  return this.name === "openrouter" && model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64"] } } : {};
1428
1428
  }
1429
1429
  validateInput(request) {
1430
- const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
1430
+ const modelOverride = request.providerOptions?.model;
1431
+ const effectiveModel = typeof modelOverride === "string" ? modelOverride : request.model;
1431
1432
  const fail = (source, feature) => {
1432
1433
  throw new UnsupportedModelInputError({
1433
1434
  provider: this.name,
@@ -1437,6 +1438,9 @@ var OpenAICompatibleProvider = class {
1437
1438
  ...feature ? { feature } : {}
1438
1439
  });
1439
1440
  };
1441
+ if (request.providerOptions && "model" in request.providerOptions && (typeof modelOverride !== "string" || modelOverride.trim().length === 0)) {
1442
+ fail(void 0, "invalid model providerOptions");
1443
+ }
1440
1444
  if (this.name !== "openrouter" || effectiveModel.trim().length === 0) {
1441
1445
  fail(void 0, "image input for this model");
1442
1446
  }
@@ -2377,13 +2381,6 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
2377
2381
  };
2378
2382
 
2379
2383
  // src/providers/openai-responses.ts
2380
- var OPENAI_RESPONSES_IMAGE_MODELS = /* @__PURE__ */ new Set([
2381
- "gpt-4o",
2382
- "gpt-4o-2024-08-06",
2383
- "gpt-4o-2024-11-20",
2384
- "gpt-4o-mini",
2385
- "gpt-4o-mini-2024-07-18"
2386
- ]);
2387
2384
  function base64FromSource(source) {
2388
2385
  return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
2389
2386
  "base64"
@@ -2422,10 +2419,11 @@ function responseImageParts(parts) {
2422
2419
  var OpenAIResponsesProvider = class {
2423
2420
  name = "openai-responses";
2424
2421
  inputCapabilities(model) {
2425
- return OPENAI_RESPONSES_IMAGE_MODELS.has(model) ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
2422
+ return model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
2426
2423
  }
2427
2424
  validateInput(request) {
2428
- const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
2425
+ const modelOverride = request.providerOptions?.model;
2426
+ const effectiveModel = typeof modelOverride === "string" ? modelOverride : request.model;
2429
2427
  const fail = (source, feature) => {
2430
2428
  throw new UnsupportedModelInputError({
2431
2429
  provider: this.name,
@@ -2435,7 +2433,10 @@ var OpenAIResponsesProvider = class {
2435
2433
  ...feature ? { feature } : {}
2436
2434
  });
2437
2435
  };
2438
- if (!OPENAI_RESPONSES_IMAGE_MODELS.has(effectiveModel)) {
2436
+ if (request.providerOptions && "model" in request.providerOptions && (typeof modelOverride !== "string" || modelOverride.trim().length === 0)) {
2437
+ fail(void 0, "invalid model providerOptions");
2438
+ }
2439
+ if (effectiveModel.trim().length === 0) {
2439
2440
  fail(void 0, "image input for this model");
2440
2441
  }
2441
2442
  if (request.providerOptions && "input" in request.providerOptions) {
@@ -2563,7 +2564,7 @@ var OpenAIResponsesProvider = class {
2563
2564
  const resolved = resolveThinkingOptions(options);
2564
2565
  const { includeThoughts } = resolved;
2565
2566
  const wireEffort = resolveOpenAIReasoningEffort(effectiveModel, resolved);
2566
- const stripTemp = oSeries || reasoningCapable && wireEffort !== void 0;
2567
+ const stripTemp = oSeries || supportsMaxReasoningEffort(effectiveModel) || reasoningCapable && wireEffort !== void 0;
2567
2568
  const systemMessages = messages.filter((m) => m.role === "system");
2568
2569
  const nonSystemMessages = messages.filter((m) => m.role !== "system");
2569
2570
  const body = {
@@ -2911,7 +2912,6 @@ function anthropicImageBlocks(parts) {
2911
2912
  }
2912
2913
  var ANTHROPIC_API_VERSION = "2023-06-01";
2913
2914
  var ANTHROPIC_FILES_BETA = "files-api-2025-04-14";
2914
- var ANTHROPIC_IMAGE_MODELS = /* @__PURE__ */ new Set(["claude-sonnet-4-5", "claude-opus-4-8"]);
2915
2915
  function hasAnthropicProviderFileImage(messages) {
2916
2916
  return messages.some(
2917
2917
  (message) => Array.isArray(message.content) && message.content.some(
@@ -3030,6 +3030,28 @@ var ANTHROPIC_RATES = {
3030
3030
  function isValidTokenCount(value) {
3031
3031
  return value !== void 0 && Number.isSafeInteger(value) && value >= 0;
3032
3032
  }
3033
+ function anthropicStreamErrorStatus(type) {
3034
+ switch (type) {
3035
+ case "invalid_request_error":
3036
+ return 400;
3037
+ case "authentication_error":
3038
+ return 401;
3039
+ case "permission_error":
3040
+ return 403;
3041
+ case "not_found_error":
3042
+ return 404;
3043
+ case "request_too_large":
3044
+ return 413;
3045
+ case "rate_limit_error":
3046
+ return 429;
3047
+ case "api_error":
3048
+ return 500;
3049
+ case "overloaded_error":
3050
+ return 529;
3051
+ default:
3052
+ return 0;
3053
+ }
3054
+ }
3033
3055
  function estimateAnthropicCost(model, usage, now = /* @__PURE__ */ new Date()) {
3034
3056
  if (!model || !isValidTokenCount(usage.inputTokens) || !isValidTokenCount(usage.outputTokens)) {
3035
3057
  return void 0;
@@ -3279,10 +3301,11 @@ function mergeAnthropicUsage(initial, update) {
3279
3301
  var AnthropicProvider = class {
3280
3302
  name = "anthropic";
3281
3303
  inputCapabilities(model) {
3282
- return ANTHROPIC_IMAGE_MODELS.has(model) ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
3304
+ return model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
3283
3305
  }
3284
3306
  validateInput(request) {
3285
- const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
3307
+ const modelOverride = request.providerOptions?.model;
3308
+ const effectiveModel = typeof modelOverride === "string" ? modelOverride : request.model;
3286
3309
  const fail = (source, feature) => {
3287
3310
  throw new UnsupportedModelInputError({
3288
3311
  provider: this.name,
@@ -3292,7 +3315,10 @@ var AnthropicProvider = class {
3292
3315
  ...feature ? { feature } : {}
3293
3316
  });
3294
3317
  };
3295
- if (!ANTHROPIC_IMAGE_MODELS.has(effectiveModel)) {
3318
+ if (request.providerOptions && "model" in request.providerOptions && (typeof modelOverride !== "string" || modelOverride.trim().length === 0)) {
3319
+ fail(void 0, "invalid model providerOptions");
3320
+ }
3321
+ if (effectiveModel.trim().length === 0) {
3296
3322
  fail(void 0, "image input for this model");
3297
3323
  }
3298
3324
  if (request.providerOptions && "messages" in request.providerOptions) {
@@ -3417,7 +3443,7 @@ var AnthropicProvider = class {
3417
3443
  if (!res.body) {
3418
3444
  throw new Error("Anthropic stream response has no body");
3419
3445
  }
3420
- yield* this.parseSSEStream(res.body, pricingContext);
3446
+ yield* this.parseSSEStream(res.body, pricingContext, res.headers);
3421
3447
  }
3422
3448
  // ---------------------------------------------------------------------------
3423
3449
  // Internal: request building
@@ -3660,7 +3686,7 @@ ${jsonInstruction}` : jsonInstruction;
3660
3686
  // ---------------------------------------------------------------------------
3661
3687
  // Internal: SSE stream parsing
3662
3688
  // ---------------------------------------------------------------------------
3663
- async *parseSSEStream(body, pricingContext) {
3689
+ async *parseSSEStream(body, pricingContext, responseHeaders) {
3664
3690
  const reader = body.getReader();
3665
3691
  const decoder = new TextDecoder();
3666
3692
  let buffer = "";
@@ -3706,6 +3732,15 @@ ${jsonInstruction}` : jsonInstruction;
3706
3732
  continue;
3707
3733
  }
3708
3734
  switch (event.type) {
3735
+ case "error": {
3736
+ const status = anthropicStreamErrorStatus(event.error?.type);
3737
+ throw buildProviderError({
3738
+ provider: this.name,
3739
+ status,
3740
+ headers: responseHeaders,
3741
+ message: event.error?.message ?? "Anthropic stream failed"
3742
+ });
3743
+ }
3709
3744
  case "content_block_start": {
3710
3745
  const block = event.content_block;
3711
3746
  if (block?.type === "tool_use") {
@@ -3957,7 +3992,6 @@ var GEMINI_PRICING = {
3957
3992
  "gemini-3.5-flash-lite": { input: 3e-7, cached: 3e-8, output: 25e-7 },
3958
3993
  "gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
3959
3994
  };
3960
- var GEMINI_INTERACTIONS_IMAGE_MODELS = /* @__PURE__ */ new Set(["gemini-3.7-flash"]);
3961
3995
  function isValidTokenCount2(value) {
3962
3996
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
3963
3997
  }
@@ -3967,6 +4001,55 @@ function isStandardGeminiRequestTier(value) {
3967
4001
  function isDefinitiveStandardGeminiResponseTier(value) {
3968
4002
  return value === "SERVICE_TIER_UNSPECIFIED" || value === "SERVICE_TIER_STANDARD" || value === "standard";
3969
4003
  }
4004
+ function geminiInteractionErrorStatus(code) {
4005
+ if (typeof code === "number" && Number.isInteger(code) && code >= 100 && code <= 599) {
4006
+ return code;
4007
+ }
4008
+ switch (code) {
4009
+ case "invalid_request":
4010
+ case "invalid_argument":
4011
+ case "parameter_unknown":
4012
+ case "failed_precondition":
4013
+ return 400;
4014
+ case "authentication":
4015
+ case "unauthenticated":
4016
+ return 401;
4017
+ case "permission_denied":
4018
+ return 403;
4019
+ case "not_found":
4020
+ case "model_not_found":
4021
+ return 404;
4022
+ case "request_timeout":
4023
+ return 408;
4024
+ case "conflict":
4025
+ case "already_exists":
4026
+ return 409;
4027
+ case "request_too_large":
4028
+ return 413;
4029
+ case "out_of_range":
4030
+ return 416;
4031
+ case "quota_exceeded":
4032
+ case "resource_exhausted":
4033
+ case "rate_limit_exceeded":
4034
+ return 429;
4035
+ case "cancelled":
4036
+ return 499;
4037
+ case "api_error":
4038
+ case "internal":
4039
+ case "internal_server_error":
4040
+ return 500;
4041
+ case "bad_gateway":
4042
+ return 502;
4043
+ case "unavailable":
4044
+ case "service_unavailable":
4045
+ return 503;
4046
+ case "deadline_exceeded":
4047
+ case "gateway_timeout":
4048
+ return 504;
4049
+ default:
4050
+ return void 0;
4051
+ }
4052
+ }
3970
4053
  function isEligibleGeminiPricing(context, hasDefinitiveStandardResponseTier, hasInvalidResponseTier) {
3971
4054
  return context.eligibleRequest && isStandardGeminiRequestTier(context.serviceTier) && hasDefinitiveStandardResponseTier && !hasInvalidResponseTier;
3972
4055
  }
@@ -4048,7 +4131,9 @@ var GEMINI_3X_MIN_THINKING_LEVEL = /* @__PURE__ */ new Map([
4048
4131
  ["gemini-3.6-flash", "minimal"],
4049
4132
  // L4 live evidence: Interactions gemini-3.7-flash accepts low/medium/high,
4050
4133
  // but rejects minimal. Keep this exact entry isolated from legacy aliases.
4051
- ["gemini-3.7-flash", "low"]
4134
+ ["gemini-3.7-flash", "low"],
4135
+ // Current-model live evidence: gemini-3.8-flash has the same low floor.
4136
+ ["gemini-3.8-flash", "low"]
4052
4137
  ]);
4053
4138
  var GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE = /* @__PURE__ */ new Set([
4054
4139
  "gemini-3.5-flash-lite",
@@ -4076,10 +4161,11 @@ function warnGemini3xEffortNone(model) {
4076
4161
  var GeminiProvider = class {
4077
4162
  name = "google";
4078
4163
  inputCapabilities(model) {
4079
- return GEMINI_INTERACTIONS_IMAGE_MODELS.has(model) ? { image: { sources: ["bytes", "base64", "provider-file"] } } : {};
4164
+ return model.trim().length > 0 ? { image: { sources: ["bytes", "base64", "provider-file"] } } : {};
4080
4165
  }
4081
4166
  validateInput(request) {
4082
- const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
4167
+ const modelOverride = request.providerOptions?.model;
4168
+ const effectiveModel = typeof modelOverride === "string" ? modelOverride : request.model;
4083
4169
  const fail = (source, feature) => {
4084
4170
  throw new UnsupportedModelInputError({
4085
4171
  provider: this.name,
@@ -4089,7 +4175,10 @@ var GeminiProvider = class {
4089
4175
  ...feature ? { feature } : {}
4090
4176
  });
4091
4177
  };
4092
- if (!GEMINI_INTERACTIONS_IMAGE_MODELS.has(effectiveModel)) {
4178
+ if (request.providerOptions && "model" in request.providerOptions && (typeof modelOverride !== "string" || modelOverride.trim().length === 0)) {
4179
+ fail(void 0, "invalid model providerOptions");
4180
+ }
4181
+ if (effectiveModel.trim().length === 0) {
4093
4182
  fail(void 0, "image input for this model");
4094
4183
  }
4095
4184
  if (request.providerOptions) {
@@ -4281,7 +4370,7 @@ var GeminiProvider = class {
4281
4370
  });
4282
4371
  }
4283
4372
  if (!res.body) throw new Error("Gemini Interactions stream has no body");
4284
- yield* this.parseInteractionSSEStream(res.body);
4373
+ yield* this.parseInteractionSSEStream(res.body, res.headers);
4285
4374
  }
4286
4375
  buildInteractionRequestBody(messages, options, stream) {
4287
4376
  const model = this.requestModel(options);
@@ -4351,6 +4440,7 @@ var GeminiProvider = class {
4351
4440
  return body;
4352
4441
  }
4353
4442
  assertSafeInteractionOptions(options) {
4443
+ const modelOverride = options.providerOptions?.model;
4354
4444
  const model = this.requestModel(options);
4355
4445
  const fail = (feature) => {
4356
4446
  throw new UnsupportedModelInputError({
@@ -4360,7 +4450,10 @@ var GeminiProvider = class {
4360
4450
  feature
4361
4451
  });
4362
4452
  };
4363
- if (!GEMINI_INTERACTIONS_IMAGE_MODELS.has(model)) fail("image input for this model");
4453
+ if (options.providerOptions && "model" in options.providerOptions && (typeof modelOverride !== "string" || modelOverride.trim().length === 0)) {
4454
+ fail("invalid model providerOptions");
4455
+ }
4456
+ if (model.trim().length === 0) fail("image input for this model");
4364
4457
  const forbidden = [
4365
4458
  "input",
4366
4459
  "contents",
@@ -4436,7 +4529,7 @@ var GeminiProvider = class {
4436
4529
  providerMetadata: json.steps?.length ? { geminiInteractionSteps: json.steps.filter(isGeminiInteractionStep) } : void 0
4437
4530
  };
4438
4531
  }
4439
- async *parseInteractionSSEStream(body) {
4532
+ async *parseInteractionSSEStream(body, responseHeaders) {
4440
4533
  const reader = body.getReader();
4441
4534
  const decoder = new TextDecoder();
4442
4535
  let buffer = "";
@@ -4462,7 +4555,22 @@ var GeminiProvider = class {
4462
4555
  continue;
4463
4556
  }
4464
4557
  if (event.event_type === "error") {
4465
- throw new Error(event.error?.message ?? "Gemini Interactions stream failed");
4558
+ const status = geminiInteractionErrorStatus(event.error?.code);
4559
+ const message = event.error?.message ?? "Gemini Interactions stream failed";
4560
+ if (status !== void 0) {
4561
+ throw buildProviderError({
4562
+ provider: this.name,
4563
+ status,
4564
+ headers: responseHeaders,
4565
+ message
4566
+ });
4567
+ }
4568
+ throw new ProviderError({
4569
+ provider: this.name,
4570
+ status: 0,
4571
+ retryable: false,
4572
+ message
4573
+ });
4466
4574
  }
4467
4575
  if (event.event_type === "step.start" && event.step) {
4468
4576
  const index = event.index;