@axlsdk/axl 0.22.1 → 0.22.3

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/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
  }
@@ -2336,17 +2340,22 @@ function resolveOpenAIReasoningEffort(model, resolved) {
2336
2340
  }
2337
2341
  return resolved.thinkingDisabled ? clampReasoningEffort(model, "none") : void 0;
2338
2342
  }
2339
- var warnedChatMaxDowngrades = /* @__PURE__ */ new Set();
2340
2343
  function resolveOpenAIChatReasoningEffort(model, resolved) {
2341
2344
  const effort = resolveOpenAIReasoningEffort(model, resolved);
2342
- if (effort !== "max") return effort;
2343
- if (resolved.activeEffort === "max" && !warnedChatMaxDowngrades.has(model)) {
2344
- warnedChatMaxDowngrades.add(model);
2345
- console.warn(
2346
- `[axl] OpenAI Chat Completions does not accept effort "max" for ${model}; using "xhigh". Use the OpenAI Responses provider for native "max".`
2347
- );
2348
- }
2349
- return "xhigh";
2345
+ return effort === "max" ? "xhigh" : effort;
2346
+ }
2347
+ function resolveOpenAIEffortResolution(options, resolveWireEffort, endpoint) {
2348
+ const resolved = resolveThinkingOptions(options);
2349
+ if (resolved.effort === void 0 || resolved.hasBudgetOverride) return void 0;
2350
+ const model = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
2351
+ const effective = resolveWireEffort(model, resolved);
2352
+ if (effective === void 0 || effective === resolved.effort) return void 0;
2353
+ return {
2354
+ requested: resolved.effort,
2355
+ effective,
2356
+ clamped: true,
2357
+ cause: `${endpoint} does not accept reasoning effort '${resolved.effort}' for ${model}; using '${effective}'`
2358
+ };
2350
2359
  }
2351
2360
  var openaiReasoningEmit = (body, resolved, model) => {
2352
2361
  const oSeries = isOSeriesModel(model);
@@ -2371,19 +2380,21 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
2371
2380
  constructor(options = {}) {
2372
2381
  super({ profile: OPENAI_PROFILE, ...options });
2373
2382
  }
2383
+ /** Report a clamped `effort` — Chat Completions caps `'max'` at `'xhigh'` and
2384
+ * rejects `'none'` before gpt-5.1 (clamped to `'minimal'`). */
2385
+ effortResolution(options) {
2386
+ return resolveOpenAIEffortResolution(
2387
+ options,
2388
+ resolveOpenAIChatReasoningEffort,
2389
+ "OpenAI Chat Completions"
2390
+ );
2391
+ }
2374
2392
  computeCost(model, usage, _reportedCost, context) {
2375
2393
  return usage ? estimateDirectOpenAICost(model, usage, { ...context, baseUrl: this.baseUrl }) : void 0;
2376
2394
  }
2377
2395
  };
2378
2396
 
2379
2397
  // 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
2398
  function base64FromSource(source) {
2388
2399
  return source.type === "base64" ? source.data : Buffer.from(source.data.buffer, source.data.byteOffset, source.data.byteLength).toString(
2389
2400
  "base64"
@@ -2422,10 +2433,17 @@ function responseImageParts(parts) {
2422
2433
  var OpenAIResponsesProvider = class {
2423
2434
  name = "openai-responses";
2424
2435
  inputCapabilities(model) {
2425
- return OPENAI_RESPONSES_IMAGE_MODELS.has(model) ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
2436
+ return model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
2437
+ }
2438
+ /** Report a clamped `effort`. Responses accepts the native `'max'` tier that
2439
+ * Chat Completions caps, but still clamps `'none'` before gpt-5.1, `'xhigh'`
2440
+ * below gpt-5.2, and everything to `'high'` on gpt-5-pro. */
2441
+ effortResolution(options) {
2442
+ return resolveOpenAIEffortResolution(options, resolveOpenAIReasoningEffort, "OpenAI Responses");
2426
2443
  }
2427
2444
  validateInput(request) {
2428
- const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
2445
+ const modelOverride = request.providerOptions?.model;
2446
+ const effectiveModel = typeof modelOverride === "string" ? modelOverride : request.model;
2429
2447
  const fail = (source, feature) => {
2430
2448
  throw new UnsupportedModelInputError({
2431
2449
  provider: this.name,
@@ -2435,7 +2453,10 @@ var OpenAIResponsesProvider = class {
2435
2453
  ...feature ? { feature } : {}
2436
2454
  });
2437
2455
  };
2438
- if (!OPENAI_RESPONSES_IMAGE_MODELS.has(effectiveModel)) {
2456
+ if (request.providerOptions && "model" in request.providerOptions && (typeof modelOverride !== "string" || modelOverride.trim().length === 0)) {
2457
+ fail(void 0, "invalid model providerOptions");
2458
+ }
2459
+ if (effectiveModel.trim().length === 0) {
2439
2460
  fail(void 0, "image input for this model");
2440
2461
  }
2441
2462
  if (request.providerOptions && "input" in request.providerOptions) {
@@ -2563,7 +2584,7 @@ var OpenAIResponsesProvider = class {
2563
2584
  const resolved = resolveThinkingOptions(options);
2564
2585
  const { includeThoughts } = resolved;
2565
2586
  const wireEffort = resolveOpenAIReasoningEffort(effectiveModel, resolved);
2566
- const stripTemp = oSeries || reasoningCapable && wireEffort !== void 0;
2587
+ const stripTemp = oSeries || supportsMaxReasoningEffort(effectiveModel) || reasoningCapable && wireEffort !== void 0;
2567
2588
  const systemMessages = messages.filter((m) => m.role === "system");
2568
2589
  const nonSystemMessages = messages.filter((m) => m.role !== "system");
2569
2590
  const body = {
@@ -2911,7 +2932,6 @@ function anthropicImageBlocks(parts) {
2911
2932
  }
2912
2933
  var ANTHROPIC_API_VERSION = "2023-06-01";
2913
2934
  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
2935
  function hasAnthropicProviderFileImage(messages) {
2916
2936
  return messages.some(
2917
2937
  (message) => Array.isArray(message.content) && message.content.some(
@@ -3030,6 +3050,28 @@ var ANTHROPIC_RATES = {
3030
3050
  function isValidTokenCount(value) {
3031
3051
  return value !== void 0 && Number.isSafeInteger(value) && value >= 0;
3032
3052
  }
3053
+ function anthropicStreamErrorStatus(type) {
3054
+ switch (type) {
3055
+ case "invalid_request_error":
3056
+ return 400;
3057
+ case "authentication_error":
3058
+ return 401;
3059
+ case "permission_error":
3060
+ return 403;
3061
+ case "not_found_error":
3062
+ return 404;
3063
+ case "request_too_large":
3064
+ return 413;
3065
+ case "rate_limit_error":
3066
+ return 429;
3067
+ case "api_error":
3068
+ return 500;
3069
+ case "overloaded_error":
3070
+ return 529;
3071
+ default:
3072
+ return 0;
3073
+ }
3074
+ }
3033
3075
  function estimateAnthropicCost(model, usage, now = /* @__PURE__ */ new Date()) {
3034
3076
  if (!model || !isValidTokenCount(usage.inputTokens) || !isValidTokenCount(usage.outputTokens)) {
3035
3077
  return void 0;
@@ -3074,14 +3116,6 @@ function budgetToClaudeEffort(budget) {
3074
3116
  if (budget <= THINKING_BUDGETS.xhigh) return "xhigh";
3075
3117
  return "max";
3076
3118
  }
3077
- var warnedFableDisable = /* @__PURE__ */ new Set();
3078
- function warnFableDisable(model) {
3079
- if (warnedFableDisable.has(model)) return;
3080
- warnedFableDisable.add(model);
3081
- console.warn(
3082
- `[axl] thinking cannot be disabled on Anthropic ${model}; using adaptive thinking at effort 'low'.`
3083
- );
3084
- }
3085
3119
  function resolveClaudeThinking(model, resolved) {
3086
3120
  const capability = resolveClaudeCapability(model);
3087
3121
  if (!capability) return { stripTemperature: false };
@@ -3101,11 +3135,14 @@ function resolveClaudeThinking(model, resolved) {
3101
3135
  }
3102
3136
  if (resolved.thinkingDisabled) {
3103
3137
  if (capability.thinking === "adaptive-always-on") {
3104
- warnFableDisable(model);
3105
3138
  return {
3106
3139
  thinking: { type: "adaptive" },
3107
3140
  outputConfig: { effort: "low" },
3108
- stripTemperature: true
3141
+ stripTemperature: true,
3142
+ clamp: {
3143
+ effective: "low",
3144
+ cause: `thinking cannot be disabled on Anthropic ${model}; using adaptive thinking at effort 'low'`
3145
+ }
3109
3146
  };
3110
3147
  }
3111
3148
  if (activeEffort && capability.disableAt && !isEffortAtOrBelow(activeEffort, capability.disableAt)) {
@@ -3167,7 +3204,16 @@ function resolveClaudeThinking(model, resolved) {
3167
3204
  return {
3168
3205
  thinking: { type: "enabled", budget_tokens: budget },
3169
3206
  manualBudget: budget,
3170
- stripTemperature: true
3207
+ stripTemperature: true,
3208
+ // Legacy models take `budget_tokens`, not an effort level, and have no
3209
+ // `effortLevels` — so `clampAnthropicEffort` folds 'max'/'xhigh' down to
3210
+ // the 'high' tier and the request silently carries that tier's budget.
3211
+ // Report it: an unreported budget-tier downgrade is exactly the silent
3212
+ // clamp this capability exists to expose.
3213
+ clamp: activeEffort === resolved.activeEffort ? void 0 : {
3214
+ effective: activeEffort,
3215
+ cause: `Anthropic ${model} takes thinking budget_tokens rather than an effort level, and its ceiling tier is '${activeEffort}'; effort '${resolved.activeEffort}' sends budget_tokens ${budget}`
3216
+ }
3171
3217
  };
3172
3218
  }
3173
3219
  return { stripTemperature: capability.stripTemperature ?? false };
@@ -3278,11 +3324,32 @@ function mergeAnthropicUsage(initial, update) {
3278
3324
  }
3279
3325
  var AnthropicProvider = class {
3280
3326
  name = "anthropic";
3327
+ /** Report a clamped `effort`. Anthropic clamps to each model's supported
3328
+ * `output_config.effort` levels, and models that always think fall back to
3329
+ * adaptive `'low'` when asked to disable thinking. Only reported when the
3330
+ * effective level is what the request body actually carries; an explicit
3331
+ * `thinkingBudget` is documented to override `effort`, so it is not a clamp. */
3332
+ effortResolution(options) {
3333
+ const resolved = resolveThinkingOptions(options);
3334
+ if (resolved.effort === void 0 || resolved.hasBudgetOverride) return void 0;
3335
+ const model = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
3336
+ const config = resolveClaudeThinking(model, resolved);
3337
+ const wireEffort = typeof config.outputConfig?.effort === "string" ? config.outputConfig.effort : void 0;
3338
+ const effective = config.clamp?.effective ?? wireEffort;
3339
+ if (effective === void 0 || effective === resolved.effort) return void 0;
3340
+ return {
3341
+ requested: resolved.effort,
3342
+ effective,
3343
+ clamped: true,
3344
+ cause: config.clamp?.cause ?? `Anthropic ${model} does not support effort '${resolved.effort}'; using '${effective}'`
3345
+ };
3346
+ }
3281
3347
  inputCapabilities(model) {
3282
- return ANTHROPIC_IMAGE_MODELS.has(model) ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
3348
+ return model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
3283
3349
  }
3284
3350
  validateInput(request) {
3285
- const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
3351
+ const modelOverride = request.providerOptions?.model;
3352
+ const effectiveModel = typeof modelOverride === "string" ? modelOverride : request.model;
3286
3353
  const fail = (source, feature) => {
3287
3354
  throw new UnsupportedModelInputError({
3288
3355
  provider: this.name,
@@ -3292,7 +3359,10 @@ var AnthropicProvider = class {
3292
3359
  ...feature ? { feature } : {}
3293
3360
  });
3294
3361
  };
3295
- if (!ANTHROPIC_IMAGE_MODELS.has(effectiveModel)) {
3362
+ if (request.providerOptions && "model" in request.providerOptions && (typeof modelOverride !== "string" || modelOverride.trim().length === 0)) {
3363
+ fail(void 0, "invalid model providerOptions");
3364
+ }
3365
+ if (effectiveModel.trim().length === 0) {
3296
3366
  fail(void 0, "image input for this model");
3297
3367
  }
3298
3368
  if (request.providerOptions && "messages" in request.providerOptions) {
@@ -3417,7 +3487,7 @@ var AnthropicProvider = class {
3417
3487
  if (!res.body) {
3418
3488
  throw new Error("Anthropic stream response has no body");
3419
3489
  }
3420
- yield* this.parseSSEStream(res.body, pricingContext);
3490
+ yield* this.parseSSEStream(res.body, pricingContext, res.headers);
3421
3491
  }
3422
3492
  // ---------------------------------------------------------------------------
3423
3493
  // Internal: request building
@@ -3660,7 +3730,7 @@ ${jsonInstruction}` : jsonInstruction;
3660
3730
  // ---------------------------------------------------------------------------
3661
3731
  // Internal: SSE stream parsing
3662
3732
  // ---------------------------------------------------------------------------
3663
- async *parseSSEStream(body, pricingContext) {
3733
+ async *parseSSEStream(body, pricingContext, responseHeaders) {
3664
3734
  const reader = body.getReader();
3665
3735
  const decoder = new TextDecoder();
3666
3736
  let buffer = "";
@@ -3706,6 +3776,15 @@ ${jsonInstruction}` : jsonInstruction;
3706
3776
  continue;
3707
3777
  }
3708
3778
  switch (event.type) {
3779
+ case "error": {
3780
+ const status = anthropicStreamErrorStatus(event.error?.type);
3781
+ throw buildProviderError({
3782
+ provider: this.name,
3783
+ status,
3784
+ headers: responseHeaders,
3785
+ message: event.error?.message ?? "Anthropic stream failed"
3786
+ });
3787
+ }
3709
3788
  case "content_block_start": {
3710
3789
  const block = event.content_block;
3711
3790
  if (block?.type === "tool_use") {
@@ -3957,7 +4036,6 @@ var GEMINI_PRICING = {
3957
4036
  "gemini-3.5-flash-lite": { input: 3e-7, cached: 3e-8, output: 25e-7 },
3958
4037
  "gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
3959
4038
  };
3960
- var GEMINI_INTERACTIONS_IMAGE_MODELS = /* @__PURE__ */ new Set(["gemini-3.7-flash"]);
3961
4039
  function isValidTokenCount2(value) {
3962
4040
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
3963
4041
  }
@@ -3967,6 +4045,55 @@ function isStandardGeminiRequestTier(value) {
3967
4045
  function isDefinitiveStandardGeminiResponseTier(value) {
3968
4046
  return value === "SERVICE_TIER_UNSPECIFIED" || value === "SERVICE_TIER_STANDARD" || value === "standard";
3969
4047
  }
4048
+ function geminiInteractionErrorStatus(code) {
4049
+ if (typeof code === "number" && Number.isInteger(code) && code >= 100 && code <= 599) {
4050
+ return code;
4051
+ }
4052
+ switch (code) {
4053
+ case "invalid_request":
4054
+ case "invalid_argument":
4055
+ case "parameter_unknown":
4056
+ case "failed_precondition":
4057
+ return 400;
4058
+ case "authentication":
4059
+ case "unauthenticated":
4060
+ return 401;
4061
+ case "permission_denied":
4062
+ return 403;
4063
+ case "not_found":
4064
+ case "model_not_found":
4065
+ return 404;
4066
+ case "request_timeout":
4067
+ return 408;
4068
+ case "conflict":
4069
+ case "already_exists":
4070
+ return 409;
4071
+ case "request_too_large":
4072
+ return 413;
4073
+ case "out_of_range":
4074
+ return 416;
4075
+ case "quota_exceeded":
4076
+ case "resource_exhausted":
4077
+ case "rate_limit_exceeded":
4078
+ return 429;
4079
+ case "cancelled":
4080
+ return 499;
4081
+ case "api_error":
4082
+ case "internal":
4083
+ case "internal_server_error":
4084
+ return 500;
4085
+ case "bad_gateway":
4086
+ return 502;
4087
+ case "unavailable":
4088
+ case "service_unavailable":
4089
+ return 503;
4090
+ case "deadline_exceeded":
4091
+ case "gateway_timeout":
4092
+ return 504;
4093
+ default:
4094
+ return void 0;
4095
+ }
4096
+ }
3970
4097
  function isEligibleGeminiPricing(context, hasDefinitiveStandardResponseTier, hasInvalidResponseTier) {
3971
4098
  return context.eligibleRequest && isStandardGeminiRequestTier(context.serviceTier) && hasDefinitiveStandardResponseTier && !hasInvalidResponseTier;
3972
4099
  }
@@ -4048,7 +4175,9 @@ var GEMINI_3X_MIN_THINKING_LEVEL = /* @__PURE__ */ new Map([
4048
4175
  ["gemini-3.6-flash", "minimal"],
4049
4176
  // L4 live evidence: Interactions gemini-3.7-flash accepts low/medium/high,
4050
4177
  // but rejects minimal. Keep this exact entry isolated from legacy aliases.
4051
- ["gemini-3.7-flash", "low"]
4178
+ ["gemini-3.7-flash", "low"],
4179
+ // Current-model live evidence: gemini-3.8-flash has the same low floor.
4180
+ ["gemini-3.8-flash", "low"]
4052
4181
  ]);
4053
4182
  var GEMINI_MODELS_WITHOUT_PORTABLE_TEMPERATURE = /* @__PURE__ */ new Set([
4054
4183
  "gemini-3.5-flash-lite",
@@ -4065,21 +4194,80 @@ function budgetToThinkingLevel(budgetTokens) {
4065
4194
  function minThinkingLevel(model) {
4066
4195
  return GEMINI_3X_MIN_THINKING_LEVEL.get(model) ?? "minimal";
4067
4196
  }
4068
- var _warned3xEffortNone = /* @__PURE__ */ new Set();
4069
- function warnGemini3xEffortNone(model) {
4070
- if (_warned3xEffortNone.has(model)) return;
4071
- _warned3xEffortNone.add(model);
4072
- console.warn(
4073
- `[axl] effort: 'none' on Gemini 3.x (${model}) maps to the model's minimum thinking level ('${minThinkingLevel(model)}'), not fully disabled. Gemini 3.x models cannot disable thinking entirely.`
4074
- );
4197
+ function resolveGeminiThinking(model, resolved) {
4198
+ const is3x = isGemini3x(model);
4199
+ if (resolved.thinkingDisabled) {
4200
+ const minLevel = minThinkingLevel(model);
4201
+ if (is3x) {
4202
+ return {
4203
+ interactionLevel: minLevel,
4204
+ thinkingLevel: minLevel,
4205
+ thinkingBudget: void 0,
4206
+ attachThoughts: false,
4207
+ // A `thinkingBudget: 0` disable has no unified effort to report against;
4208
+ // only an explicit `effort: 'none'` is a clamp of a requested value.
4209
+ clamp: resolved.effort === "none" ? {
4210
+ effective: minLevel,
4211
+ cause: `Gemini 3.x models cannot disable thinking; using the model minimum thinking level '${minLevel}'`
4212
+ } : void 0
4213
+ };
4214
+ }
4215
+ return {
4216
+ interactionLevel: "minimal",
4217
+ thinkingLevel: void 0,
4218
+ thinkingBudget: 0,
4219
+ attachThoughts: false
4220
+ };
4221
+ }
4222
+ if (resolved.hasBudgetOverride) {
4223
+ const budget = resolved.thinkingBudget;
4224
+ const level = budgetToThinkingLevel(budget);
4225
+ return {
4226
+ interactionLevel: level,
4227
+ thinkingLevel: is3x ? level : void 0,
4228
+ thinkingBudget: is3x ? void 0 : budget,
4229
+ attachThoughts: resolved.includeThoughts
4230
+ };
4231
+ }
4232
+ if (resolved.activeEffort) {
4233
+ const effort = resolved.activeEffort;
4234
+ const budget = THINKING_BUDGETS2[effort] ?? 5e3;
4235
+ if (is3x) {
4236
+ const level = THINKING_LEVELS[effort] ?? "medium";
4237
+ return {
4238
+ interactionLevel: level,
4239
+ thinkingLevel: level,
4240
+ thinkingBudget: void 0,
4241
+ attachThoughts: resolved.includeThoughts,
4242
+ clamp: level === effort ? void 0 : {
4243
+ effective: level,
4244
+ cause: `Gemini 3.x thinking levels cap at 'high'; effort '${effort}' maps to '${level}'`
4245
+ }
4246
+ };
4247
+ }
4248
+ return {
4249
+ interactionLevel: budgetToThinkingLevel(budget),
4250
+ thinkingLevel: void 0,
4251
+ // 2.5 Pro supports a higher max budget (32768) than other 2.5 models (24576).
4252
+ thinkingBudget: effort === "max" && model === "gemini-2.5-pro" ? 32768 : budget,
4253
+ attachThoughts: resolved.includeThoughts
4254
+ };
4255
+ }
4256
+ return {
4257
+ interactionLevel: void 0,
4258
+ thinkingLevel: void 0,
4259
+ thinkingBudget: void 0,
4260
+ attachThoughts: resolved.includeThoughts
4261
+ };
4075
4262
  }
4076
4263
  var GeminiProvider = class {
4077
4264
  name = "google";
4078
4265
  inputCapabilities(model) {
4079
- return GEMINI_INTERACTIONS_IMAGE_MODELS.has(model) ? { image: { sources: ["bytes", "base64", "provider-file"] } } : {};
4266
+ return model.trim().length > 0 ? { image: { sources: ["bytes", "base64", "provider-file"] } } : {};
4080
4267
  }
4081
4268
  validateInput(request) {
4082
- const effectiveModel = typeof request.providerOptions?.model === "string" ? request.providerOptions.model : request.model;
4269
+ const modelOverride = request.providerOptions?.model;
4270
+ const effectiveModel = typeof modelOverride === "string" ? modelOverride : request.model;
4083
4271
  const fail = (source, feature) => {
4084
4272
  throw new UnsupportedModelInputError({
4085
4273
  provider: this.name,
@@ -4089,7 +4277,10 @@ var GeminiProvider = class {
4089
4277
  ...feature ? { feature } : {}
4090
4278
  });
4091
4279
  };
4092
- if (!GEMINI_INTERACTIONS_IMAGE_MODELS.has(effectiveModel)) {
4280
+ if (request.providerOptions && "model" in request.providerOptions && (typeof modelOverride !== "string" || modelOverride.trim().length === 0)) {
4281
+ fail(void 0, "invalid model providerOptions");
4282
+ }
4283
+ if (effectiveModel.trim().length === 0) {
4093
4284
  fail(void 0, "image input for this model");
4094
4285
  }
4095
4286
  if (request.providerOptions) {
@@ -4136,6 +4327,21 @@ var GeminiProvider = class {
4136
4327
  nativeStructuredOutputSupport() {
4137
4328
  return "lossy";
4138
4329
  }
4330
+ /** Report a clamped `effort` (R7a). Gemini 3.x cannot disable thinking and
4331
+ * caps at `'high'`, so `'none'`, `'xhigh'` and `'max'` are not honored
4332
+ * verbatim there. Everything else — including 2.x disabled thinking — is
4333
+ * sent as asked, so there is nothing to report. */
4334
+ effortResolution(options) {
4335
+ const resolved = resolveThinkingOptions(options);
4336
+ if (resolved.effort === void 0) return void 0;
4337
+ const clamp = resolveGeminiThinking(this.requestModel(options), resolved).clamp;
4338
+ return clamp ? {
4339
+ requested: resolved.effort,
4340
+ effective: clamp.effective,
4341
+ clamped: true,
4342
+ cause: clamp.cause
4343
+ } : void 0;
4344
+ }
4139
4345
  baseUrl;
4140
4346
  apiKeySource;
4141
4347
  callCounter = 0;
@@ -4281,7 +4487,7 @@ var GeminiProvider = class {
4281
4487
  });
4282
4488
  }
4283
4489
  if (!res.body) throw new Error("Gemini Interactions stream has no body");
4284
- yield* this.parseInteractionSSEStream(res.body);
4490
+ yield* this.parseInteractionSSEStream(res.body, res.headers);
4285
4491
  }
4286
4492
  buildInteractionRequestBody(messages, options, stream) {
4287
4493
  const model = this.requestModel(options);
@@ -4310,12 +4516,9 @@ var GeminiProvider = class {
4310
4516
  generationConfig.tool_choice = typeof options.toolChoice === "string" ? options.toolChoice === "required" ? "any" : options.toolChoice : { allowed_tools: { mode: "any", tools: [options.toolChoice.function.name] } };
4311
4517
  }
4312
4518
  const thinking = resolveThinkingOptions(options);
4313
- if (thinking.thinkingDisabled) {
4314
- generationConfig.thinking_level = isGemini3x(model) ? minThinkingLevel(model) : "minimal";
4315
- } else if (thinking.hasBudgetOverride) {
4316
- generationConfig.thinking_level = budgetToThinkingLevel(thinking.thinkingBudget);
4317
- } else if (thinking.activeEffort) {
4318
- generationConfig.thinking_level = isGemini3x(model) ? THINKING_LEVELS[thinking.activeEffort] ?? "medium" : budgetToThinkingLevel(THINKING_BUDGETS2[thinking.activeEffort] ?? 5e3);
4519
+ const thinkingPlan = resolveGeminiThinking(model, thinking);
4520
+ if (thinkingPlan.interactionLevel !== void 0) {
4521
+ generationConfig.thinking_level = thinkingPlan.interactionLevel;
4319
4522
  }
4320
4523
  if (thinking.includeThoughts) generationConfig.thinking_summaries = "auto";
4321
4524
  if (Object.keys(generationConfig).length) body.generation_config = generationConfig;
@@ -4351,6 +4554,7 @@ var GeminiProvider = class {
4351
4554
  return body;
4352
4555
  }
4353
4556
  assertSafeInteractionOptions(options) {
4557
+ const modelOverride = options.providerOptions?.model;
4354
4558
  const model = this.requestModel(options);
4355
4559
  const fail = (feature) => {
4356
4560
  throw new UnsupportedModelInputError({
@@ -4360,7 +4564,10 @@ var GeminiProvider = class {
4360
4564
  feature
4361
4565
  });
4362
4566
  };
4363
- if (!GEMINI_INTERACTIONS_IMAGE_MODELS.has(model)) fail("image input for this model");
4567
+ if (options.providerOptions && "model" in options.providerOptions && (typeof modelOverride !== "string" || modelOverride.trim().length === 0)) {
4568
+ fail("invalid model providerOptions");
4569
+ }
4570
+ if (model.trim().length === 0) fail("image input for this model");
4364
4571
  const forbidden = [
4365
4572
  "input",
4366
4573
  "contents",
@@ -4436,7 +4643,7 @@ var GeminiProvider = class {
4436
4643
  providerMetadata: json.steps?.length ? { geminiInteractionSteps: json.steps.filter(isGeminiInteractionStep) } : void 0
4437
4644
  };
4438
4645
  }
4439
- async *parseInteractionSSEStream(body) {
4646
+ async *parseInteractionSSEStream(body, responseHeaders) {
4440
4647
  const reader = body.getReader();
4441
4648
  const decoder = new TextDecoder();
4442
4649
  let buffer = "";
@@ -4462,7 +4669,22 @@ var GeminiProvider = class {
4462
4669
  continue;
4463
4670
  }
4464
4671
  if (event.event_type === "error") {
4465
- throw new Error(event.error?.message ?? "Gemini Interactions stream failed");
4672
+ const status = geminiInteractionErrorStatus(event.error?.code);
4673
+ const message = event.error?.message ?? "Gemini Interactions stream failed";
4674
+ if (status !== void 0) {
4675
+ throw buildProviderError({
4676
+ provider: this.name,
4677
+ status,
4678
+ headers: responseHeaders,
4679
+ message
4680
+ });
4681
+ }
4682
+ throw new ProviderError({
4683
+ provider: this.name,
4684
+ status: 0,
4685
+ retryable: false,
4686
+ message
4687
+ });
4466
4688
  }
4467
4689
  if (event.event_type === "step.start" && event.step) {
4468
4690
  const index = event.index;
@@ -4732,50 +4954,17 @@ var GeminiProvider = class {
4732
4954
  if (Object.keys(generationConfig).length > 0) {
4733
4955
  body.generationConfig = generationConfig;
4734
4956
  }
4735
- const {
4736
- effort,
4737
- thinkingBudget,
4738
- includeThoughts,
4739
- thinkingDisabled,
4740
- activeEffort,
4741
- hasBudgetOverride
4742
- } = resolveThinkingOptions(options);
4743
- if (thinkingDisabled) {
4744
- if (isGemini3x(requestModel)) {
4745
- if (effort === "none") {
4746
- warnGemini3xEffortNone(requestModel);
4747
- }
4748
- generationConfig.thinkingConfig = { thinkingLevel: minThinkingLevel(requestModel) };
4749
- } else {
4750
- generationConfig.thinkingConfig = { thinkingBudget: 0 };
4751
- }
4752
- if (!body.generationConfig) body.generationConfig = generationConfig;
4753
- } else if (hasBudgetOverride) {
4754
- const config = {};
4755
- if (isGemini3x(requestModel)) {
4756
- config.thinkingLevel = budgetToThinkingLevel(thinkingBudget);
4757
- } else {
4758
- config.thinkingBudget = thinkingBudget;
4759
- }
4760
- if (includeThoughts) config.includeThoughts = true;
4761
- generationConfig.thinkingConfig = config;
4762
- if (!body.generationConfig) body.generationConfig = generationConfig;
4763
- } else if (activeEffort) {
4764
- const config = {};
4765
- if (isGemini3x(requestModel)) {
4766
- config.thinkingLevel = THINKING_LEVELS[activeEffort] ?? "medium";
4767
- } else {
4768
- if (activeEffort === "max" && requestModel === "gemini-2.5-pro") {
4769
- config.thinkingBudget = 32768;
4770
- } else {
4771
- config.thinkingBudget = THINKING_BUDGETS2[activeEffort] ?? 5e3;
4772
- }
4773
- }
4774
- if (includeThoughts) config.includeThoughts = true;
4775
- generationConfig.thinkingConfig = config;
4776
- if (!body.generationConfig) body.generationConfig = generationConfig;
4777
- } else if (includeThoughts) {
4778
- generationConfig.thinkingConfig = { includeThoughts: true };
4957
+ const thinkingPlan = resolveGeminiThinking(requestModel, resolveThinkingOptions(options));
4958
+ const thinkingConfig = {};
4959
+ if (thinkingPlan.thinkingLevel !== void 0) {
4960
+ thinkingConfig.thinkingLevel = thinkingPlan.thinkingLevel;
4961
+ }
4962
+ if (thinkingPlan.thinkingBudget !== void 0) {
4963
+ thinkingConfig.thinkingBudget = thinkingPlan.thinkingBudget;
4964
+ }
4965
+ if (thinkingPlan.attachThoughts) thinkingConfig.includeThoughts = true;
4966
+ if (Object.keys(thinkingConfig).length > 0) {
4967
+ generationConfig.thinkingConfig = thinkingConfig;
4779
4968
  if (!body.generationConfig) body.generationConfig = generationConfig;
4780
4969
  }
4781
4970
  if (options.toolChoice !== void 0) {
@@ -7410,6 +7599,9 @@ var AXL_EVENT_TYPES = [
7410
7599
  // structured-output cliffs (oversized appended schema, dropped refinements,
7411
7600
  // streaming disabled, zero-guidance `schemaPrompt:'none'`).
7412
7601
  "schema_diagnostic",
7602
+ // Provider capability diagnostics — the resolved provider could not honor a
7603
+ // portable request knob verbatim (first kind: a clamped `effort`).
7604
+ "provider_diagnostic",
7413
7605
  // Observability
7414
7606
  "log",
7415
7607
  "memory_remember",
@@ -7459,6 +7651,7 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
7459
7651
  "pipeline",
7460
7652
  "verify",
7461
7653
  "schema_diagnostic",
7654
+ "provider_diagnostic",
7462
7655
  "workflow_start",
7463
7656
  "workflow_end",
7464
7657
  "transcription_start",
@@ -8387,6 +8580,10 @@ var LEGACY_REDACTION_RULES = {
8387
8580
  // which is user content or PII (field paths are schema-shape, preserved like
8388
8581
  // `string_delta.path`). Passthrough, as an explicit reviewed decision.
8389
8582
  schema_diagnostic: passthrough,
8583
+ // `provider_diagnostic` carries only provider capability metadata — a provider
8584
+ // name, a model id, and two effort level strings. No application data or user
8585
+ // content ever reaches it. Passthrough, as an explicit reviewed decision.
8586
+ provider_diagnostic: passthrough,
8390
8587
  log: (e) => {
8391
8588
  if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
8392
8589
  return { ...e, data: walkObjectOneLevel(e.data, LOG_PRESERVE_KEYS) };
@@ -8517,6 +8714,7 @@ var COMMON_V2_REDACTION_RULES = {
8517
8714
  string_delta: reuseLegacyRule("string_delta"),
8518
8715
  verify: reuseLegacyRule("verify"),
8519
8716
  schema_diagnostic: reuseLegacyRule("schema_diagnostic"),
8717
+ provider_diagnostic: reuseLegacyRule("provider_diagnostic"),
8520
8718
  log: reuseLegacyRule("log"),
8521
8719
  memory_remember: reuseLegacyRule("memory_remember"),
8522
8720
  memory_recall: reuseLegacyRule("memory_recall"),
@@ -8612,13 +8810,14 @@ function detectDroppedRefinements(schema) {
8612
8810
  var DOCS_URL = "docs/observability.md#schema-diagnostics";
8613
8811
  var MAX_WARNED_KEYS = 2048;
8614
8812
  var warnedDiagnosticKeys = /* @__PURE__ */ new Set();
8615
- function warnSchemaDiagnosticOnce(key, message, silent) {
8813
+ function warnDiagnosticOnce(key, message, silent, docs = DOCS_URL) {
8616
8814
  if (silent || process.env.AXL_DIAGNOSTICS_SILENT === "true") return;
8617
8815
  if (warnedDiagnosticKeys.has(key)) return;
8618
8816
  if (warnedDiagnosticKeys.size >= MAX_WARNED_KEYS) warnedDiagnosticKeys.clear();
8619
8817
  warnedDiagnosticKeys.add(key);
8620
- console.warn(`[axl] ${message} See ${DOCS_URL}.`);
8818
+ console.warn(`[axl] ${message} See ${docs}.`);
8621
8819
  }
8820
+ var warnSchemaDiagnosticOnce = warnDiagnosticOnce;
8622
8821
 
8623
8822
  // src/config.ts
8624
8823
  function defineConfig(config) {
@@ -9468,7 +9667,22 @@ function appendRetryMessages(messages, content, feedbackMessage, providerMetadat
9468
9667
  content,
9469
9668
  ...providerMetadata ? { providerMetadata } : {}
9470
9669
  });
9471
- messages.push({ role: "system", content: feedbackMessage });
9670
+ messages.push({ role: "user", content: feedbackMessage });
9671
+ }
9672
+ function isRetryAbort(value) {
9673
+ return typeof value === "object" && value !== null && value.retry === false;
9674
+ }
9675
+ function describeValue(value) {
9676
+ if (value === null) return "null";
9677
+ if (Array.isArray(value)) return `an array of length ${value.length}`;
9678
+ if (typeof value === "object") {
9679
+ const keys = Object.keys(value);
9680
+ return keys.length > 0 ? `an object with keys [${keys.join(", ")}]` : "an empty object";
9681
+ }
9682
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
9683
+ return `the ${typeof value} ${JSON.stringify(value)}`;
9684
+ }
9685
+ return `a value of type ${typeof value}`;
9472
9686
  }
9473
9687
  function estimateMessagesTokens(messages) {
9474
9688
  let total = 0;
@@ -10405,6 +10619,7 @@ ${appendedSchemaText}`;
10405
10619
  provider,
10406
10620
  model
10407
10621
  });
10622
+ this.emitProviderDiagnostics(agent2, options, { provider, model, providerOptions });
10408
10623
  if (handoffMessages && handoffMessages.length > 0) {
10409
10624
  const handoffContext = handoffMessages.filter(
10410
10625
  (m) => m.role === "assistant" || m.role === "tool"
@@ -10769,7 +10984,11 @@ ${appendedSchemaText}`;
10769
10984
  retries: options.retries,
10770
10985
  metadata: options.metadata,
10771
10986
  validate: options.validate,
10772
- validateRetries: options.validateRetries
10987
+ validateRetries: options.validateRetries,
10988
+ // This seam serves both callers of a handoff: a plain `ctx.ask()` on an
10989
+ // agent with `handoffs`, and the routed `ctx.delegate()` path, whose
10990
+ // terminal ask is the handoff target. The hook travels like `validate`.
10991
+ retryFeedback: options.retryFeedback
10773
10992
  } : void 0;
10774
10993
  const handoffFn = () => askStorage.run(targetFrame, async () => {
10775
10994
  const delegateOriginal = agent2._delegateOriginalInput;
@@ -10918,9 +11137,10 @@ ${appendedSchemaText}`;
10918
11137
  const attempt = guardrailOutputRetries + 1;
10919
11138
  const maxAttempts = maxGuardrailRetries + 1;
10920
11139
  const onBlock = guardrails.onBlock ?? "throw";
11140
+ const blockReason = outputResult.reason ?? "Output blocked by guardrail";
10921
11141
  let feedbackMessage;
10922
11142
  if (outputResult.block && onBlock === "retry" && guardrailOutputRetries < maxGuardrailRetries) {
10923
- feedbackMessage = `Your previous response was blocked by a safety guardrail: ${outputResult.reason ?? "Output blocked"}. Please provide a different response that complies with the guidelines.`;
11143
+ feedbackMessage = `Your previous response was blocked by a safety guardrail: ${blockReason}. Please provide a different response that complies with the guidelines.`;
10924
11144
  }
10925
11145
  this.emitEvent({
10926
11146
  type: "guardrail",
@@ -10943,31 +11163,43 @@ ${appendedSchemaText}`;
10943
11163
  });
10944
11164
  if (outputResult.block) {
10945
11165
  if (feedbackMessage) {
10946
- this.emitEvent({
10947
- type: "pipeline",
10948
- agent: agent2._name,
10949
- status: "failed",
11166
+ const decision = await this.resolveRetryFeedback(options?.retryFeedback, {
10950
11167
  stage: "guardrail",
10951
- attempt: guardrailOutputRetries + 1,
10952
- maxAttempts: maxGuardrailRetries + 1,
10953
- reason: feedbackMessage
11168
+ attempt,
11169
+ maxAttempts,
11170
+ output: content,
11171
+ reason: blockReason,
11172
+ defaultMessage: feedbackMessage
10954
11173
  });
10955
- guardrailOutputRetries++;
10956
- appendRetryMessages(
10957
- currentMessages,
10958
- content,
10959
- feedbackMessage,
10960
- response.providerMetadata
10961
- );
10962
- pendingRetryReason = "guardrail";
10963
- continue;
11174
+ if (decision.retry) {
11175
+ this.emitEvent({
11176
+ type: "pipeline",
11177
+ agent: agent2._name,
11178
+ status: "failed",
11179
+ stage: "guardrail",
11180
+ attempt,
11181
+ maxAttempts,
11182
+ reason: decision.message
11183
+ });
11184
+ guardrailOutputRetries++;
11185
+ appendRetryMessages(
11186
+ currentMessages,
11187
+ content,
11188
+ decision.message,
11189
+ response.providerMetadata
11190
+ );
11191
+ pendingRetryReason = "guardrail";
11192
+ continue;
11193
+ }
11194
+ this.currentSignal?.throwIfAborted();
11195
+ throw new GuardrailError("output", blockReason);
10964
11196
  }
10965
11197
  if (typeof onBlock === "function") {
10966
- return onBlock(outputResult.reason ?? "Output blocked by guardrail", {
11198
+ return onBlock(blockReason, {
10967
11199
  metadata: this.metadata
10968
11200
  });
10969
11201
  }
10970
- throw new GuardrailError("output", outputResult.reason ?? "Output blocked by guardrail");
11202
+ throw new GuardrailError("output", blockReason);
10971
11203
  }
10972
11204
  }
10973
11205
  let validated = void 0;
@@ -10987,7 +11219,7 @@ ${appendedSchemaText}`;
10987
11219
  schemaErr = err;
10988
11220
  schemaReason = err instanceof Error ? err.message : String(err);
10989
11221
  if (schemaRetries < maxSchemaRetries) {
10990
- schemaFeedback = `Your response was not valid JSON or did not match the required schema: ${schemaReason}. Please fix and try again.`;
11222
+ schemaFeedback = `Your response was not valid JSON or did not match the required schema: ${schemaReason}. Return a corrected response that matches the required schema.`;
10991
11223
  }
10992
11224
  }
10993
11225
  this.emitEvent({
@@ -11009,24 +11241,36 @@ ${appendedSchemaText}`;
11009
11241
  });
11010
11242
  if (!schemaValid) {
11011
11243
  if (schemaFeedback) {
11012
- this.emitEvent({
11013
- type: "pipeline",
11014
- agent: agent2._name,
11015
- status: "failed",
11244
+ const decision = await this.resolveRetryFeedback(options.retryFeedback, {
11016
11245
  stage: "schema",
11017
- attempt: schemaRetries + 1,
11018
- maxAttempts: (options.retries ?? 3) + 1,
11019
- reason: schemaFeedback
11246
+ attempt: schemaAttempt,
11247
+ maxAttempts: schemaMaxAttempts,
11248
+ output: content,
11249
+ reason: schemaReason ?? "Schema parse failed",
11250
+ ...schemaErr !== void 0 ? { error: schemaErr } : {},
11251
+ defaultMessage: schemaFeedback
11020
11252
  });
11021
- schemaRetries++;
11022
- appendRetryMessages(
11023
- currentMessages,
11024
- content,
11025
- schemaFeedback,
11026
- response.providerMetadata
11027
- );
11028
- pendingRetryReason = "schema";
11029
- continue;
11253
+ if (decision.retry) {
11254
+ this.emitEvent({
11255
+ type: "pipeline",
11256
+ agent: agent2._name,
11257
+ status: "failed",
11258
+ stage: "schema",
11259
+ attempt: schemaAttempt,
11260
+ maxAttempts: schemaMaxAttempts,
11261
+ reason: decision.message
11262
+ });
11263
+ schemaRetries++;
11264
+ appendRetryMessages(
11265
+ currentMessages,
11266
+ content,
11267
+ decision.message,
11268
+ response.providerMetadata
11269
+ );
11270
+ pendingRetryReason = "schema";
11271
+ continue;
11272
+ }
11273
+ this.currentSignal?.throwIfAborted();
11030
11274
  }
11031
11275
  const zodErr = schemaErr instanceof import_zod2.ZodError ? schemaErr : new import_zod2.ZodError([
11032
11276
  {
@@ -11040,12 +11284,14 @@ ${appendedSchemaText}`;
11040
11284
  }
11041
11285
  if (options?.schema && options.validate) {
11042
11286
  let validateResult;
11287
+ let validateErr;
11043
11288
  try {
11044
11289
  validateResult = await options.validate(validated, {
11045
11290
  metadata: this.metadata
11046
11291
  });
11047
11292
  } catch (err) {
11048
11293
  rethrowEventStreamOverflow(err);
11294
+ validateErr = err;
11049
11295
  const reason = err instanceof Error ? err.message : String(err);
11050
11296
  validateResult = { valid: false, reason: `Validator error: ${reason}` };
11051
11297
  }
@@ -11054,7 +11300,7 @@ ${appendedSchemaText}`;
11054
11300
  const validateMaxAttempts = maxValidateRetries + 1;
11055
11301
  let validateFeedback;
11056
11302
  if (!validateResult.valid && validateRetries < maxValidateRetries) {
11057
- validateFeedback = `Your response parsed correctly but failed validation: ${validateResult.reason ?? "Validation failed"}. Previous attempts are visible above. Please fix and try again.`;
11303
+ validateFeedback = `Your previous response failed validation: ${validateResult.reason ?? "Validation failed"}. Return a corrected response that satisfies the required schema and this rule.`;
11058
11304
  }
11059
11305
  this.emitEvent({
11060
11306
  type: "validate",
@@ -11075,24 +11321,37 @@ ${appendedSchemaText}`;
11075
11321
  });
11076
11322
  if (!validateResult.valid) {
11077
11323
  if (validateFeedback) {
11078
- this.emitEvent({
11079
- type: "pipeline",
11080
- agent: agent2._name,
11081
- status: "failed",
11324
+ const decision = await this.resolveRetryFeedback(options.retryFeedback, {
11082
11325
  stage: "validate",
11083
- attempt: validateRetries + 1,
11084
- maxAttempts: (options.validateRetries ?? 2) + 1,
11085
- reason: validateFeedback
11326
+ attempt: validateAttempt,
11327
+ maxAttempts: validateMaxAttempts,
11328
+ output: content,
11329
+ parsed: validated,
11330
+ reason: validateResult.reason ?? "Validation failed",
11331
+ ...validateErr !== void 0 ? { error: validateErr } : {},
11332
+ defaultMessage: validateFeedback
11086
11333
  });
11087
- validateRetries++;
11088
- appendRetryMessages(
11089
- currentMessages,
11090
- content,
11091
- validateFeedback,
11092
- response.providerMetadata
11093
- );
11094
- pendingRetryReason = "validate";
11095
- continue;
11334
+ if (decision.retry) {
11335
+ this.emitEvent({
11336
+ type: "pipeline",
11337
+ agent: agent2._name,
11338
+ status: "failed",
11339
+ stage: "validate",
11340
+ attempt: validateAttempt,
11341
+ maxAttempts: validateMaxAttempts,
11342
+ reason: decision.message
11343
+ });
11344
+ validateRetries++;
11345
+ appendRetryMessages(
11346
+ currentMessages,
11347
+ content,
11348
+ decision.message,
11349
+ response.providerMetadata
11350
+ );
11351
+ pendingRetryReason = "validate";
11352
+ continue;
11353
+ }
11354
+ this.currentSignal?.throwIfAborted();
11096
11355
  }
11097
11356
  throw new ValidationError(
11098
11357
  validated,
@@ -11114,6 +11373,31 @@ ${appendedSchemaText}`;
11114
11373
  }
11115
11374
  throw new MaxTurnsError("ctx.ask()", maxTurns);
11116
11375
  }
11376
+ /**
11377
+ * Resolve the corrective feedback text for one rejected attempt, giving an optional
11378
+ * caller-supplied `retryFeedback` hook the chance to replace it or to stop retrying.
11379
+ *
11380
+ * Called by all three output gates, only while a retry is still permitted (never on the
11381
+ * exhausting attempt) and only after the gate's own event has been emitted, so a hook that
11382
+ * throws or aborts can never erase the evidence that the gate rejected. A non-empty string
11383
+ * replaces `defaultMessage` verbatim; `undefined` / `''` keeps it; `{ retry: false }` asks
11384
+ * the gate to throw its typed terminal error. Hook exceptions propagate — a bug in caller
11385
+ * code is not a model failure and must not be retried into silence, and neither is a
11386
+ * return value outside the contract: an unrecognized shape throws instead of silently
11387
+ * degrading to the default text, which would hide the defect behind extra token spend.
11388
+ */
11389
+ async resolveRetryFeedback(hook, info) {
11390
+ if (!hook) return { retry: true, message: info.defaultMessage };
11391
+ const result = await hook(info, { metadata: this.metadata });
11392
+ if (result === void 0 || result === "") {
11393
+ return { retry: true, message: info.defaultMessage };
11394
+ }
11395
+ if (typeof result === "string") return { retry: true, message: result };
11396
+ if (isRetryAbort(result)) return { retry: false };
11397
+ throw new TypeError(
11398
+ `retryFeedback must return a string, undefined, or { retry: false }; received ${describeValue(result)}.`
11399
+ );
11400
+ }
11117
11401
  /**
11118
11402
  * Push the final assistant message into session history, preserving providerMetadata
11119
11403
  * (e.g., Gemini thought signatures needed for multi-turn reasoning context) and
@@ -11269,6 +11553,57 @@ ${appendedSchemaText}`;
11269
11553
  }
11270
11554
  }
11271
11555
  }
11556
+ /**
11557
+ * Emit a `provider_diagnostic` when the resolved provider reports that it
11558
+ * could not honor the unified `effort` verbatim (P3/R6/R7).
11559
+ *
11560
+ * The adapter only *resolves* — it returns the requested and effective levels
11561
+ * from the same pure resolver its request builder uses. The runtime decides
11562
+ * how to surface that, because only the runtime can see the event bus and
11563
+ * `AxlConfig.diagnostics.silent` (adapters receive `ProviderConfig` only).
11564
+ *
11565
+ * Called once per ask, so a tool loop with N provider round trips still emits
11566
+ * one event; the `console.warn` dedupes once per process per distinct clamp.
11567
+ * A throwing `effortResolution` is a provider bug and propagates.
11568
+ */
11569
+ emitProviderDiagnostics(agent2, options, ctx) {
11570
+ if (!ctx.provider.effortResolution) return;
11571
+ const resolution = ctx.provider.effortResolution({
11572
+ model: ctx.model,
11573
+ effort: options?.effort ?? agent2._config.effort,
11574
+ thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
11575
+ includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
11576
+ providerOptions: ctx.providerOptions
11577
+ });
11578
+ if (!resolution?.clamped) return;
11579
+ if (typeof resolution.effective !== "string" || resolution.effective.length === 0 || typeof resolution.requested !== "string" || resolution.requested.length === 0) {
11580
+ throw new TypeError(
11581
+ `Provider '${ctx.provider.name ?? "unknown"}' returned a malformed EffortResolution for model '${ctx.model}': 'requested' and 'effective' must be non-empty strings when 'clamped' is true. Received ${JSON.stringify(resolution)}.`
11582
+ );
11583
+ }
11584
+ const cause = resolution.cause ?? `effort '${resolution.requested}' is not supported as asked`;
11585
+ this.emitEvent({
11586
+ type: "provider_diagnostic",
11587
+ agent: agent2._name,
11588
+ data: {
11589
+ kind: "effort_clamped",
11590
+ ...ctx.provider.name ? { provider: ctx.provider.name } : {},
11591
+ model: ctx.model,
11592
+ requested: resolution.requested,
11593
+ effective: resolution.effective,
11594
+ cause
11595
+ }
11596
+ });
11597
+ warnDiagnosticOnce(
11598
+ // One warning per distinct clamp, not per ask: the same clamp across 500
11599
+ // asks is one process-level surprise, while a different requested/effective
11600
+ // pair is a genuinely new one.
11601
+ `effort_clamped\0${ctx.provider.name ?? ""}\0${ctx.model}\0${resolution.requested}\0${resolution.effective}`,
11602
+ `effort '${resolution.requested}' was clamped to '${resolution.effective}' on ${ctx.provider.name ? `${ctx.provider.name} ` : ""}${ctx.model} \u2014 ${cause}. A 'provider_diagnostic' event carries the same detail per ask.`,
11603
+ this.config.diagnostics?.silent,
11604
+ "docs/observability.md#provider-diagnostics"
11605
+ );
11606
+ }
11272
11607
  buildToolDefs(agent2, resolvedHandoffs) {
11273
11608
  const defs = [];
11274
11609
  if (agent2._config.tools) {
@@ -12549,7 +12884,8 @@ ${summaryResponse.content}`
12549
12884
  retries: options?.retries,
12550
12885
  metadata: options?.metadata,
12551
12886
  validate: options?.validate,
12552
- validateRetries: options?.validateRetries
12887
+ validateRetries: options?.validateRetries,
12888
+ retryFeedback: options?.retryFeedback
12553
12889
  });
12554
12890
  }
12555
12891
  const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
@@ -12597,7 +12933,8 @@ ${summaryResponse.content}`
12597
12933
  retries: options?.retries,
12598
12934
  metadata: options?.metadata,
12599
12935
  validate: options?.validate,
12600
- validateRetries: options?.validateRetries
12936
+ validateRetries: options?.validateRetries,
12937
+ retryFeedback: options?.retryFeedback
12601
12938
  });
12602
12939
  }
12603
12940
  /** Transcribe recorded audio through the dedicated transcription registry.