@axlsdk/axl 0.22.2 → 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/README.md +1 -1
- package/dist/index.cjs +365 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +164 -3
- package/dist/index.d.ts +164 -3
- package/dist/index.js +365 -136
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2118,17 +2118,22 @@ function resolveOpenAIReasoningEffort(model, resolved) {
|
|
|
2118
2118
|
}
|
|
2119
2119
|
return resolved.thinkingDisabled ? clampReasoningEffort(model, "none") : void 0;
|
|
2120
2120
|
}
|
|
2121
|
-
var warnedChatMaxDowngrades = /* @__PURE__ */ new Set();
|
|
2122
2121
|
function resolveOpenAIChatReasoningEffort(model, resolved) {
|
|
2123
2122
|
const effort = resolveOpenAIReasoningEffort(model, resolved);
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
return
|
|
2123
|
+
return effort === "max" ? "xhigh" : effort;
|
|
2124
|
+
}
|
|
2125
|
+
function resolveOpenAIEffortResolution(options, resolveWireEffort, endpoint) {
|
|
2126
|
+
const resolved = resolveThinkingOptions(options);
|
|
2127
|
+
if (resolved.effort === void 0 || resolved.hasBudgetOverride) return void 0;
|
|
2128
|
+
const model = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
|
|
2129
|
+
const effective = resolveWireEffort(model, resolved);
|
|
2130
|
+
if (effective === void 0 || effective === resolved.effort) return void 0;
|
|
2131
|
+
return {
|
|
2132
|
+
requested: resolved.effort,
|
|
2133
|
+
effective,
|
|
2134
|
+
clamped: true,
|
|
2135
|
+
cause: `${endpoint} does not accept reasoning effort '${resolved.effort}' for ${model}; using '${effective}'`
|
|
2136
|
+
};
|
|
2132
2137
|
}
|
|
2133
2138
|
var openaiReasoningEmit = (body, resolved, model) => {
|
|
2134
2139
|
const oSeries = isOSeriesModel(model);
|
|
@@ -2153,6 +2158,15 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
|
|
|
2153
2158
|
constructor(options = {}) {
|
|
2154
2159
|
super({ profile: OPENAI_PROFILE, ...options });
|
|
2155
2160
|
}
|
|
2161
|
+
/** Report a clamped `effort` — Chat Completions caps `'max'` at `'xhigh'` and
|
|
2162
|
+
* rejects `'none'` before gpt-5.1 (clamped to `'minimal'`). */
|
|
2163
|
+
effortResolution(options) {
|
|
2164
|
+
return resolveOpenAIEffortResolution(
|
|
2165
|
+
options,
|
|
2166
|
+
resolveOpenAIChatReasoningEffort,
|
|
2167
|
+
"OpenAI Chat Completions"
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2156
2170
|
computeCost(model, usage, _reportedCost, context) {
|
|
2157
2171
|
return usage ? estimateDirectOpenAICost(model, usage, { ...context, baseUrl: this.baseUrl }) : void 0;
|
|
2158
2172
|
}
|
|
@@ -2199,6 +2213,12 @@ var OpenAIResponsesProvider = class {
|
|
|
2199
2213
|
inputCapabilities(model) {
|
|
2200
2214
|
return model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
|
|
2201
2215
|
}
|
|
2216
|
+
/** Report a clamped `effort`. Responses accepts the native `'max'` tier that
|
|
2217
|
+
* Chat Completions caps, but still clamps `'none'` before gpt-5.1, `'xhigh'`
|
|
2218
|
+
* below gpt-5.2, and everything to `'high'` on gpt-5-pro. */
|
|
2219
|
+
effortResolution(options) {
|
|
2220
|
+
return resolveOpenAIEffortResolution(options, resolveOpenAIReasoningEffort, "OpenAI Responses");
|
|
2221
|
+
}
|
|
2202
2222
|
validateInput(request) {
|
|
2203
2223
|
const modelOverride = request.providerOptions?.model;
|
|
2204
2224
|
const effectiveModel = typeof modelOverride === "string" ? modelOverride : request.model;
|
|
@@ -2874,14 +2894,6 @@ function budgetToClaudeEffort(budget) {
|
|
|
2874
2894
|
if (budget <= THINKING_BUDGETS.xhigh) return "xhigh";
|
|
2875
2895
|
return "max";
|
|
2876
2896
|
}
|
|
2877
|
-
var warnedFableDisable = /* @__PURE__ */ new Set();
|
|
2878
|
-
function warnFableDisable(model) {
|
|
2879
|
-
if (warnedFableDisable.has(model)) return;
|
|
2880
|
-
warnedFableDisable.add(model);
|
|
2881
|
-
console.warn(
|
|
2882
|
-
`[axl] thinking cannot be disabled on Anthropic ${model}; using adaptive thinking at effort 'low'.`
|
|
2883
|
-
);
|
|
2884
|
-
}
|
|
2885
2897
|
function resolveClaudeThinking(model, resolved) {
|
|
2886
2898
|
const capability = resolveClaudeCapability(model);
|
|
2887
2899
|
if (!capability) return { stripTemperature: false };
|
|
@@ -2901,11 +2913,14 @@ function resolveClaudeThinking(model, resolved) {
|
|
|
2901
2913
|
}
|
|
2902
2914
|
if (resolved.thinkingDisabled) {
|
|
2903
2915
|
if (capability.thinking === "adaptive-always-on") {
|
|
2904
|
-
warnFableDisable(model);
|
|
2905
2916
|
return {
|
|
2906
2917
|
thinking: { type: "adaptive" },
|
|
2907
2918
|
outputConfig: { effort: "low" },
|
|
2908
|
-
stripTemperature: true
|
|
2919
|
+
stripTemperature: true,
|
|
2920
|
+
clamp: {
|
|
2921
|
+
effective: "low",
|
|
2922
|
+
cause: `thinking cannot be disabled on Anthropic ${model}; using adaptive thinking at effort 'low'`
|
|
2923
|
+
}
|
|
2909
2924
|
};
|
|
2910
2925
|
}
|
|
2911
2926
|
if (activeEffort && capability.disableAt && !isEffortAtOrBelow(activeEffort, capability.disableAt)) {
|
|
@@ -2967,7 +2982,16 @@ function resolveClaudeThinking(model, resolved) {
|
|
|
2967
2982
|
return {
|
|
2968
2983
|
thinking: { type: "enabled", budget_tokens: budget },
|
|
2969
2984
|
manualBudget: budget,
|
|
2970
|
-
stripTemperature: true
|
|
2985
|
+
stripTemperature: true,
|
|
2986
|
+
// Legacy models take `budget_tokens`, not an effort level, and have no
|
|
2987
|
+
// `effortLevels` — so `clampAnthropicEffort` folds 'max'/'xhigh' down to
|
|
2988
|
+
// the 'high' tier and the request silently carries that tier's budget.
|
|
2989
|
+
// Report it: an unreported budget-tier downgrade is exactly the silent
|
|
2990
|
+
// clamp this capability exists to expose.
|
|
2991
|
+
clamp: activeEffort === resolved.activeEffort ? void 0 : {
|
|
2992
|
+
effective: activeEffort,
|
|
2993
|
+
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}`
|
|
2994
|
+
}
|
|
2971
2995
|
};
|
|
2972
2996
|
}
|
|
2973
2997
|
return { stripTemperature: capability.stripTemperature ?? false };
|
|
@@ -3078,6 +3102,26 @@ function mergeAnthropicUsage(initial, update) {
|
|
|
3078
3102
|
}
|
|
3079
3103
|
var AnthropicProvider = class {
|
|
3080
3104
|
name = "anthropic";
|
|
3105
|
+
/** Report a clamped `effort`. Anthropic clamps to each model's supported
|
|
3106
|
+
* `output_config.effort` levels, and models that always think fall back to
|
|
3107
|
+
* adaptive `'low'` when asked to disable thinking. Only reported when the
|
|
3108
|
+
* effective level is what the request body actually carries; an explicit
|
|
3109
|
+
* `thinkingBudget` is documented to override `effort`, so it is not a clamp. */
|
|
3110
|
+
effortResolution(options) {
|
|
3111
|
+
const resolved = resolveThinkingOptions(options);
|
|
3112
|
+
if (resolved.effort === void 0 || resolved.hasBudgetOverride) return void 0;
|
|
3113
|
+
const model = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
|
|
3114
|
+
const config = resolveClaudeThinking(model, resolved);
|
|
3115
|
+
const wireEffort = typeof config.outputConfig?.effort === "string" ? config.outputConfig.effort : void 0;
|
|
3116
|
+
const effective = config.clamp?.effective ?? wireEffort;
|
|
3117
|
+
if (effective === void 0 || effective === resolved.effort) return void 0;
|
|
3118
|
+
return {
|
|
3119
|
+
requested: resolved.effort,
|
|
3120
|
+
effective,
|
|
3121
|
+
clamped: true,
|
|
3122
|
+
cause: config.clamp?.cause ?? `Anthropic ${model} does not support effort '${resolved.effort}'; using '${effective}'`
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3081
3125
|
inputCapabilities(model) {
|
|
3082
3126
|
return model.trim().length > 0 ? { image: { sources: ["url", "bytes", "base64", "provider-file"] } } : {};
|
|
3083
3127
|
}
|
|
@@ -3928,13 +3972,71 @@ function budgetToThinkingLevel(budgetTokens) {
|
|
|
3928
3972
|
function minThinkingLevel(model) {
|
|
3929
3973
|
return GEMINI_3X_MIN_THINKING_LEVEL.get(model) ?? "minimal";
|
|
3930
3974
|
}
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
if (
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3975
|
+
function resolveGeminiThinking(model, resolved) {
|
|
3976
|
+
const is3x = isGemini3x(model);
|
|
3977
|
+
if (resolved.thinkingDisabled) {
|
|
3978
|
+
const minLevel = minThinkingLevel(model);
|
|
3979
|
+
if (is3x) {
|
|
3980
|
+
return {
|
|
3981
|
+
interactionLevel: minLevel,
|
|
3982
|
+
thinkingLevel: minLevel,
|
|
3983
|
+
thinkingBudget: void 0,
|
|
3984
|
+
attachThoughts: false,
|
|
3985
|
+
// A `thinkingBudget: 0` disable has no unified effort to report against;
|
|
3986
|
+
// only an explicit `effort: 'none'` is a clamp of a requested value.
|
|
3987
|
+
clamp: resolved.effort === "none" ? {
|
|
3988
|
+
effective: minLevel,
|
|
3989
|
+
cause: `Gemini 3.x models cannot disable thinking; using the model minimum thinking level '${minLevel}'`
|
|
3990
|
+
} : void 0
|
|
3991
|
+
};
|
|
3992
|
+
}
|
|
3993
|
+
return {
|
|
3994
|
+
interactionLevel: "minimal",
|
|
3995
|
+
thinkingLevel: void 0,
|
|
3996
|
+
thinkingBudget: 0,
|
|
3997
|
+
attachThoughts: false
|
|
3998
|
+
};
|
|
3999
|
+
}
|
|
4000
|
+
if (resolved.hasBudgetOverride) {
|
|
4001
|
+
const budget = resolved.thinkingBudget;
|
|
4002
|
+
const level = budgetToThinkingLevel(budget);
|
|
4003
|
+
return {
|
|
4004
|
+
interactionLevel: level,
|
|
4005
|
+
thinkingLevel: is3x ? level : void 0,
|
|
4006
|
+
thinkingBudget: is3x ? void 0 : budget,
|
|
4007
|
+
attachThoughts: resolved.includeThoughts
|
|
4008
|
+
};
|
|
4009
|
+
}
|
|
4010
|
+
if (resolved.activeEffort) {
|
|
4011
|
+
const effort = resolved.activeEffort;
|
|
4012
|
+
const budget = THINKING_BUDGETS2[effort] ?? 5e3;
|
|
4013
|
+
if (is3x) {
|
|
4014
|
+
const level = THINKING_LEVELS[effort] ?? "medium";
|
|
4015
|
+
return {
|
|
4016
|
+
interactionLevel: level,
|
|
4017
|
+
thinkingLevel: level,
|
|
4018
|
+
thinkingBudget: void 0,
|
|
4019
|
+
attachThoughts: resolved.includeThoughts,
|
|
4020
|
+
clamp: level === effort ? void 0 : {
|
|
4021
|
+
effective: level,
|
|
4022
|
+
cause: `Gemini 3.x thinking levels cap at 'high'; effort '${effort}' maps to '${level}'`
|
|
4023
|
+
}
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
4026
|
+
return {
|
|
4027
|
+
interactionLevel: budgetToThinkingLevel(budget),
|
|
4028
|
+
thinkingLevel: void 0,
|
|
4029
|
+
// 2.5 Pro supports a higher max budget (32768) than other 2.5 models (24576).
|
|
4030
|
+
thinkingBudget: effort === "max" && model === "gemini-2.5-pro" ? 32768 : budget,
|
|
4031
|
+
attachThoughts: resolved.includeThoughts
|
|
4032
|
+
};
|
|
4033
|
+
}
|
|
4034
|
+
return {
|
|
4035
|
+
interactionLevel: void 0,
|
|
4036
|
+
thinkingLevel: void 0,
|
|
4037
|
+
thinkingBudget: void 0,
|
|
4038
|
+
attachThoughts: resolved.includeThoughts
|
|
4039
|
+
};
|
|
3938
4040
|
}
|
|
3939
4041
|
var GeminiProvider = class {
|
|
3940
4042
|
name = "google";
|
|
@@ -4003,6 +4105,21 @@ var GeminiProvider = class {
|
|
|
4003
4105
|
nativeStructuredOutputSupport() {
|
|
4004
4106
|
return "lossy";
|
|
4005
4107
|
}
|
|
4108
|
+
/** Report a clamped `effort` (R7a). Gemini 3.x cannot disable thinking and
|
|
4109
|
+
* caps at `'high'`, so `'none'`, `'xhigh'` and `'max'` are not honored
|
|
4110
|
+
* verbatim there. Everything else — including 2.x disabled thinking — is
|
|
4111
|
+
* sent as asked, so there is nothing to report. */
|
|
4112
|
+
effortResolution(options) {
|
|
4113
|
+
const resolved = resolveThinkingOptions(options);
|
|
4114
|
+
if (resolved.effort === void 0) return void 0;
|
|
4115
|
+
const clamp = resolveGeminiThinking(this.requestModel(options), resolved).clamp;
|
|
4116
|
+
return clamp ? {
|
|
4117
|
+
requested: resolved.effort,
|
|
4118
|
+
effective: clamp.effective,
|
|
4119
|
+
clamped: true,
|
|
4120
|
+
cause: clamp.cause
|
|
4121
|
+
} : void 0;
|
|
4122
|
+
}
|
|
4006
4123
|
baseUrl;
|
|
4007
4124
|
apiKeySource;
|
|
4008
4125
|
callCounter = 0;
|
|
@@ -4177,12 +4294,9 @@ var GeminiProvider = class {
|
|
|
4177
4294
|
generationConfig.tool_choice = typeof options.toolChoice === "string" ? options.toolChoice === "required" ? "any" : options.toolChoice : { allowed_tools: { mode: "any", tools: [options.toolChoice.function.name] } };
|
|
4178
4295
|
}
|
|
4179
4296
|
const thinking = resolveThinkingOptions(options);
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
generationConfig.thinking_level = budgetToThinkingLevel(thinking.thinkingBudget);
|
|
4184
|
-
} else if (thinking.activeEffort) {
|
|
4185
|
-
generationConfig.thinking_level = isGemini3x(model) ? THINKING_LEVELS[thinking.activeEffort] ?? "medium" : budgetToThinkingLevel(THINKING_BUDGETS2[thinking.activeEffort] ?? 5e3);
|
|
4297
|
+
const thinkingPlan = resolveGeminiThinking(model, thinking);
|
|
4298
|
+
if (thinkingPlan.interactionLevel !== void 0) {
|
|
4299
|
+
generationConfig.thinking_level = thinkingPlan.interactionLevel;
|
|
4186
4300
|
}
|
|
4187
4301
|
if (thinking.includeThoughts) generationConfig.thinking_summaries = "auto";
|
|
4188
4302
|
if (Object.keys(generationConfig).length) body.generation_config = generationConfig;
|
|
@@ -4618,50 +4732,17 @@ var GeminiProvider = class {
|
|
|
4618
4732
|
if (Object.keys(generationConfig).length > 0) {
|
|
4619
4733
|
body.generationConfig = generationConfig;
|
|
4620
4734
|
}
|
|
4621
|
-
const
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
}
|
|
4629
|
-
if (
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
warnGemini3xEffortNone(requestModel);
|
|
4633
|
-
}
|
|
4634
|
-
generationConfig.thinkingConfig = { thinkingLevel: minThinkingLevel(requestModel) };
|
|
4635
|
-
} else {
|
|
4636
|
-
generationConfig.thinkingConfig = { thinkingBudget: 0 };
|
|
4637
|
-
}
|
|
4638
|
-
if (!body.generationConfig) body.generationConfig = generationConfig;
|
|
4639
|
-
} else if (hasBudgetOverride) {
|
|
4640
|
-
const config = {};
|
|
4641
|
-
if (isGemini3x(requestModel)) {
|
|
4642
|
-
config.thinkingLevel = budgetToThinkingLevel(thinkingBudget);
|
|
4643
|
-
} else {
|
|
4644
|
-
config.thinkingBudget = thinkingBudget;
|
|
4645
|
-
}
|
|
4646
|
-
if (includeThoughts) config.includeThoughts = true;
|
|
4647
|
-
generationConfig.thinkingConfig = config;
|
|
4648
|
-
if (!body.generationConfig) body.generationConfig = generationConfig;
|
|
4649
|
-
} else if (activeEffort) {
|
|
4650
|
-
const config = {};
|
|
4651
|
-
if (isGemini3x(requestModel)) {
|
|
4652
|
-
config.thinkingLevel = THINKING_LEVELS[activeEffort] ?? "medium";
|
|
4653
|
-
} else {
|
|
4654
|
-
if (activeEffort === "max" && requestModel === "gemini-2.5-pro") {
|
|
4655
|
-
config.thinkingBudget = 32768;
|
|
4656
|
-
} else {
|
|
4657
|
-
config.thinkingBudget = THINKING_BUDGETS2[activeEffort] ?? 5e3;
|
|
4658
|
-
}
|
|
4659
|
-
}
|
|
4660
|
-
if (includeThoughts) config.includeThoughts = true;
|
|
4661
|
-
generationConfig.thinkingConfig = config;
|
|
4662
|
-
if (!body.generationConfig) body.generationConfig = generationConfig;
|
|
4663
|
-
} else if (includeThoughts) {
|
|
4664
|
-
generationConfig.thinkingConfig = { includeThoughts: true };
|
|
4735
|
+
const thinkingPlan = resolveGeminiThinking(requestModel, resolveThinkingOptions(options));
|
|
4736
|
+
const thinkingConfig = {};
|
|
4737
|
+
if (thinkingPlan.thinkingLevel !== void 0) {
|
|
4738
|
+
thinkingConfig.thinkingLevel = thinkingPlan.thinkingLevel;
|
|
4739
|
+
}
|
|
4740
|
+
if (thinkingPlan.thinkingBudget !== void 0) {
|
|
4741
|
+
thinkingConfig.thinkingBudget = thinkingPlan.thinkingBudget;
|
|
4742
|
+
}
|
|
4743
|
+
if (thinkingPlan.attachThoughts) thinkingConfig.includeThoughts = true;
|
|
4744
|
+
if (Object.keys(thinkingConfig).length > 0) {
|
|
4745
|
+
generationConfig.thinkingConfig = thinkingConfig;
|
|
4665
4746
|
if (!body.generationConfig) body.generationConfig = generationConfig;
|
|
4666
4747
|
}
|
|
4667
4748
|
if (options.toolChoice !== void 0) {
|
|
@@ -7296,6 +7377,9 @@ var AXL_EVENT_TYPES = [
|
|
|
7296
7377
|
// structured-output cliffs (oversized appended schema, dropped refinements,
|
|
7297
7378
|
// streaming disabled, zero-guidance `schemaPrompt:'none'`).
|
|
7298
7379
|
"schema_diagnostic",
|
|
7380
|
+
// Provider capability diagnostics — the resolved provider could not honor a
|
|
7381
|
+
// portable request knob verbatim (first kind: a clamped `effort`).
|
|
7382
|
+
"provider_diagnostic",
|
|
7299
7383
|
// Observability
|
|
7300
7384
|
"log",
|
|
7301
7385
|
"memory_remember",
|
|
@@ -7345,6 +7429,7 @@ var LIFECYCLE_TYPES = /* @__PURE__ */ new Set([
|
|
|
7345
7429
|
"pipeline",
|
|
7346
7430
|
"verify",
|
|
7347
7431
|
"schema_diagnostic",
|
|
7432
|
+
"provider_diagnostic",
|
|
7348
7433
|
"workflow_start",
|
|
7349
7434
|
"workflow_end",
|
|
7350
7435
|
"transcription_start",
|
|
@@ -8273,6 +8358,10 @@ var LEGACY_REDACTION_RULES = {
|
|
|
8273
8358
|
// which is user content or PII (field paths are schema-shape, preserved like
|
|
8274
8359
|
// `string_delta.path`). Passthrough, as an explicit reviewed decision.
|
|
8275
8360
|
schema_diagnostic: passthrough,
|
|
8361
|
+
// `provider_diagnostic` carries only provider capability metadata — a provider
|
|
8362
|
+
// name, a model id, and two effort level strings. No application data or user
|
|
8363
|
+
// content ever reaches it. Passthrough, as an explicit reviewed decision.
|
|
8364
|
+
provider_diagnostic: passthrough,
|
|
8276
8365
|
log: (e) => {
|
|
8277
8366
|
if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
|
|
8278
8367
|
return { ...e, data: walkObjectOneLevel(e.data, LOG_PRESERVE_KEYS) };
|
|
@@ -8403,6 +8492,7 @@ var COMMON_V2_REDACTION_RULES = {
|
|
|
8403
8492
|
string_delta: reuseLegacyRule("string_delta"),
|
|
8404
8493
|
verify: reuseLegacyRule("verify"),
|
|
8405
8494
|
schema_diagnostic: reuseLegacyRule("schema_diagnostic"),
|
|
8495
|
+
provider_diagnostic: reuseLegacyRule("provider_diagnostic"),
|
|
8406
8496
|
log: reuseLegacyRule("log"),
|
|
8407
8497
|
memory_remember: reuseLegacyRule("memory_remember"),
|
|
8408
8498
|
memory_recall: reuseLegacyRule("memory_recall"),
|
|
@@ -8498,13 +8588,14 @@ function detectDroppedRefinements(schema) {
|
|
|
8498
8588
|
var DOCS_URL = "docs/observability.md#schema-diagnostics";
|
|
8499
8589
|
var MAX_WARNED_KEYS = 2048;
|
|
8500
8590
|
var warnedDiagnosticKeys = /* @__PURE__ */ new Set();
|
|
8501
|
-
function
|
|
8591
|
+
function warnDiagnosticOnce(key, message, silent, docs = DOCS_URL) {
|
|
8502
8592
|
if (silent || process.env.AXL_DIAGNOSTICS_SILENT === "true") return;
|
|
8503
8593
|
if (warnedDiagnosticKeys.has(key)) return;
|
|
8504
8594
|
if (warnedDiagnosticKeys.size >= MAX_WARNED_KEYS) warnedDiagnosticKeys.clear();
|
|
8505
8595
|
warnedDiagnosticKeys.add(key);
|
|
8506
|
-
console.warn(`[axl] ${message} See ${
|
|
8596
|
+
console.warn(`[axl] ${message} See ${docs}.`);
|
|
8507
8597
|
}
|
|
8598
|
+
var warnSchemaDiagnosticOnce = warnDiagnosticOnce;
|
|
8508
8599
|
|
|
8509
8600
|
// src/config.ts
|
|
8510
8601
|
function defineConfig(config) {
|
|
@@ -9354,7 +9445,22 @@ function appendRetryMessages(messages, content, feedbackMessage, providerMetadat
|
|
|
9354
9445
|
content,
|
|
9355
9446
|
...providerMetadata ? { providerMetadata } : {}
|
|
9356
9447
|
});
|
|
9357
|
-
messages.push({ role: "
|
|
9448
|
+
messages.push({ role: "user", content: feedbackMessage });
|
|
9449
|
+
}
|
|
9450
|
+
function isRetryAbort(value) {
|
|
9451
|
+
return typeof value === "object" && value !== null && value.retry === false;
|
|
9452
|
+
}
|
|
9453
|
+
function describeValue(value) {
|
|
9454
|
+
if (value === null) return "null";
|
|
9455
|
+
if (Array.isArray(value)) return `an array of length ${value.length}`;
|
|
9456
|
+
if (typeof value === "object") {
|
|
9457
|
+
const keys = Object.keys(value);
|
|
9458
|
+
return keys.length > 0 ? `an object with keys [${keys.join(", ")}]` : "an empty object";
|
|
9459
|
+
}
|
|
9460
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
9461
|
+
return `the ${typeof value} ${JSON.stringify(value)}`;
|
|
9462
|
+
}
|
|
9463
|
+
return `a value of type ${typeof value}`;
|
|
9358
9464
|
}
|
|
9359
9465
|
function estimateMessagesTokens(messages) {
|
|
9360
9466
|
let total = 0;
|
|
@@ -10291,6 +10397,7 @@ ${appendedSchemaText}`;
|
|
|
10291
10397
|
provider,
|
|
10292
10398
|
model
|
|
10293
10399
|
});
|
|
10400
|
+
this.emitProviderDiagnostics(agent2, options, { provider, model, providerOptions });
|
|
10294
10401
|
if (handoffMessages && handoffMessages.length > 0) {
|
|
10295
10402
|
const handoffContext = handoffMessages.filter(
|
|
10296
10403
|
(m) => m.role === "assistant" || m.role === "tool"
|
|
@@ -10655,7 +10762,11 @@ ${appendedSchemaText}`;
|
|
|
10655
10762
|
retries: options.retries,
|
|
10656
10763
|
metadata: options.metadata,
|
|
10657
10764
|
validate: options.validate,
|
|
10658
|
-
validateRetries: options.validateRetries
|
|
10765
|
+
validateRetries: options.validateRetries,
|
|
10766
|
+
// This seam serves both callers of a handoff: a plain `ctx.ask()` on an
|
|
10767
|
+
// agent with `handoffs`, and the routed `ctx.delegate()` path, whose
|
|
10768
|
+
// terminal ask is the handoff target. The hook travels like `validate`.
|
|
10769
|
+
retryFeedback: options.retryFeedback
|
|
10659
10770
|
} : void 0;
|
|
10660
10771
|
const handoffFn = () => askStorage.run(targetFrame, async () => {
|
|
10661
10772
|
const delegateOriginal = agent2._delegateOriginalInput;
|
|
@@ -10804,9 +10915,10 @@ ${appendedSchemaText}`;
|
|
|
10804
10915
|
const attempt = guardrailOutputRetries + 1;
|
|
10805
10916
|
const maxAttempts = maxGuardrailRetries + 1;
|
|
10806
10917
|
const onBlock = guardrails.onBlock ?? "throw";
|
|
10918
|
+
const blockReason = outputResult.reason ?? "Output blocked by guardrail";
|
|
10807
10919
|
let feedbackMessage;
|
|
10808
10920
|
if (outputResult.block && onBlock === "retry" && guardrailOutputRetries < maxGuardrailRetries) {
|
|
10809
|
-
feedbackMessage = `Your previous response was blocked by a safety guardrail: ${
|
|
10921
|
+
feedbackMessage = `Your previous response was blocked by a safety guardrail: ${blockReason}. Please provide a different response that complies with the guidelines.`;
|
|
10810
10922
|
}
|
|
10811
10923
|
this.emitEvent({
|
|
10812
10924
|
type: "guardrail",
|
|
@@ -10829,31 +10941,43 @@ ${appendedSchemaText}`;
|
|
|
10829
10941
|
});
|
|
10830
10942
|
if (outputResult.block) {
|
|
10831
10943
|
if (feedbackMessage) {
|
|
10832
|
-
this.
|
|
10833
|
-
type: "pipeline",
|
|
10834
|
-
agent: agent2._name,
|
|
10835
|
-
status: "failed",
|
|
10944
|
+
const decision = await this.resolveRetryFeedback(options?.retryFeedback, {
|
|
10836
10945
|
stage: "guardrail",
|
|
10837
|
-
attempt
|
|
10838
|
-
maxAttempts
|
|
10839
|
-
|
|
10946
|
+
attempt,
|
|
10947
|
+
maxAttempts,
|
|
10948
|
+
output: content,
|
|
10949
|
+
reason: blockReason,
|
|
10950
|
+
defaultMessage: feedbackMessage
|
|
10840
10951
|
});
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10952
|
+
if (decision.retry) {
|
|
10953
|
+
this.emitEvent({
|
|
10954
|
+
type: "pipeline",
|
|
10955
|
+
agent: agent2._name,
|
|
10956
|
+
status: "failed",
|
|
10957
|
+
stage: "guardrail",
|
|
10958
|
+
attempt,
|
|
10959
|
+
maxAttempts,
|
|
10960
|
+
reason: decision.message
|
|
10961
|
+
});
|
|
10962
|
+
guardrailOutputRetries++;
|
|
10963
|
+
appendRetryMessages(
|
|
10964
|
+
currentMessages,
|
|
10965
|
+
content,
|
|
10966
|
+
decision.message,
|
|
10967
|
+
response.providerMetadata
|
|
10968
|
+
);
|
|
10969
|
+
pendingRetryReason = "guardrail";
|
|
10970
|
+
continue;
|
|
10971
|
+
}
|
|
10972
|
+
this.currentSignal?.throwIfAborted();
|
|
10973
|
+
throw new GuardrailError("output", blockReason);
|
|
10850
10974
|
}
|
|
10851
10975
|
if (typeof onBlock === "function") {
|
|
10852
|
-
return onBlock(
|
|
10976
|
+
return onBlock(blockReason, {
|
|
10853
10977
|
metadata: this.metadata
|
|
10854
10978
|
});
|
|
10855
10979
|
}
|
|
10856
|
-
throw new GuardrailError("output",
|
|
10980
|
+
throw new GuardrailError("output", blockReason);
|
|
10857
10981
|
}
|
|
10858
10982
|
}
|
|
10859
10983
|
let validated = void 0;
|
|
@@ -10873,7 +10997,7 @@ ${appendedSchemaText}`;
|
|
|
10873
10997
|
schemaErr = err;
|
|
10874
10998
|
schemaReason = err instanceof Error ? err.message : String(err);
|
|
10875
10999
|
if (schemaRetries < maxSchemaRetries) {
|
|
10876
|
-
schemaFeedback = `Your response was not valid JSON or did not match the required schema: ${schemaReason}.
|
|
11000
|
+
schemaFeedback = `Your response was not valid JSON or did not match the required schema: ${schemaReason}. Return a corrected response that matches the required schema.`;
|
|
10877
11001
|
}
|
|
10878
11002
|
}
|
|
10879
11003
|
this.emitEvent({
|
|
@@ -10895,24 +11019,36 @@ ${appendedSchemaText}`;
|
|
|
10895
11019
|
});
|
|
10896
11020
|
if (!schemaValid) {
|
|
10897
11021
|
if (schemaFeedback) {
|
|
10898
|
-
this.
|
|
10899
|
-
type: "pipeline",
|
|
10900
|
-
agent: agent2._name,
|
|
10901
|
-
status: "failed",
|
|
11022
|
+
const decision = await this.resolveRetryFeedback(options.retryFeedback, {
|
|
10902
11023
|
stage: "schema",
|
|
10903
|
-
attempt:
|
|
10904
|
-
maxAttempts:
|
|
10905
|
-
|
|
11024
|
+
attempt: schemaAttempt,
|
|
11025
|
+
maxAttempts: schemaMaxAttempts,
|
|
11026
|
+
output: content,
|
|
11027
|
+
reason: schemaReason ?? "Schema parse failed",
|
|
11028
|
+
...schemaErr !== void 0 ? { error: schemaErr } : {},
|
|
11029
|
+
defaultMessage: schemaFeedback
|
|
10906
11030
|
});
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
11031
|
+
if (decision.retry) {
|
|
11032
|
+
this.emitEvent({
|
|
11033
|
+
type: "pipeline",
|
|
11034
|
+
agent: agent2._name,
|
|
11035
|
+
status: "failed",
|
|
11036
|
+
stage: "schema",
|
|
11037
|
+
attempt: schemaAttempt,
|
|
11038
|
+
maxAttempts: schemaMaxAttempts,
|
|
11039
|
+
reason: decision.message
|
|
11040
|
+
});
|
|
11041
|
+
schemaRetries++;
|
|
11042
|
+
appendRetryMessages(
|
|
11043
|
+
currentMessages,
|
|
11044
|
+
content,
|
|
11045
|
+
decision.message,
|
|
11046
|
+
response.providerMetadata
|
|
11047
|
+
);
|
|
11048
|
+
pendingRetryReason = "schema";
|
|
11049
|
+
continue;
|
|
11050
|
+
}
|
|
11051
|
+
this.currentSignal?.throwIfAborted();
|
|
10916
11052
|
}
|
|
10917
11053
|
const zodErr = schemaErr instanceof ZodError2 ? schemaErr : new ZodError2([
|
|
10918
11054
|
{
|
|
@@ -10926,12 +11062,14 @@ ${appendedSchemaText}`;
|
|
|
10926
11062
|
}
|
|
10927
11063
|
if (options?.schema && options.validate) {
|
|
10928
11064
|
let validateResult;
|
|
11065
|
+
let validateErr;
|
|
10929
11066
|
try {
|
|
10930
11067
|
validateResult = await options.validate(validated, {
|
|
10931
11068
|
metadata: this.metadata
|
|
10932
11069
|
});
|
|
10933
11070
|
} catch (err) {
|
|
10934
11071
|
rethrowEventStreamOverflow(err);
|
|
11072
|
+
validateErr = err;
|
|
10935
11073
|
const reason = err instanceof Error ? err.message : String(err);
|
|
10936
11074
|
validateResult = { valid: false, reason: `Validator error: ${reason}` };
|
|
10937
11075
|
}
|
|
@@ -10940,7 +11078,7 @@ ${appendedSchemaText}`;
|
|
|
10940
11078
|
const validateMaxAttempts = maxValidateRetries + 1;
|
|
10941
11079
|
let validateFeedback;
|
|
10942
11080
|
if (!validateResult.valid && validateRetries < maxValidateRetries) {
|
|
10943
|
-
validateFeedback = `Your response
|
|
11081
|
+
validateFeedback = `Your previous response failed validation: ${validateResult.reason ?? "Validation failed"}. Return a corrected response that satisfies the required schema and this rule.`;
|
|
10944
11082
|
}
|
|
10945
11083
|
this.emitEvent({
|
|
10946
11084
|
type: "validate",
|
|
@@ -10961,24 +11099,37 @@ ${appendedSchemaText}`;
|
|
|
10961
11099
|
});
|
|
10962
11100
|
if (!validateResult.valid) {
|
|
10963
11101
|
if (validateFeedback) {
|
|
10964
|
-
this.
|
|
10965
|
-
type: "pipeline",
|
|
10966
|
-
agent: agent2._name,
|
|
10967
|
-
status: "failed",
|
|
11102
|
+
const decision = await this.resolveRetryFeedback(options.retryFeedback, {
|
|
10968
11103
|
stage: "validate",
|
|
10969
|
-
attempt:
|
|
10970
|
-
maxAttempts:
|
|
10971
|
-
|
|
11104
|
+
attempt: validateAttempt,
|
|
11105
|
+
maxAttempts: validateMaxAttempts,
|
|
11106
|
+
output: content,
|
|
11107
|
+
parsed: validated,
|
|
11108
|
+
reason: validateResult.reason ?? "Validation failed",
|
|
11109
|
+
...validateErr !== void 0 ? { error: validateErr } : {},
|
|
11110
|
+
defaultMessage: validateFeedback
|
|
10972
11111
|
});
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
|
|
10978
|
-
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
11112
|
+
if (decision.retry) {
|
|
11113
|
+
this.emitEvent({
|
|
11114
|
+
type: "pipeline",
|
|
11115
|
+
agent: agent2._name,
|
|
11116
|
+
status: "failed",
|
|
11117
|
+
stage: "validate",
|
|
11118
|
+
attempt: validateAttempt,
|
|
11119
|
+
maxAttempts: validateMaxAttempts,
|
|
11120
|
+
reason: decision.message
|
|
11121
|
+
});
|
|
11122
|
+
validateRetries++;
|
|
11123
|
+
appendRetryMessages(
|
|
11124
|
+
currentMessages,
|
|
11125
|
+
content,
|
|
11126
|
+
decision.message,
|
|
11127
|
+
response.providerMetadata
|
|
11128
|
+
);
|
|
11129
|
+
pendingRetryReason = "validate";
|
|
11130
|
+
continue;
|
|
11131
|
+
}
|
|
11132
|
+
this.currentSignal?.throwIfAborted();
|
|
10982
11133
|
}
|
|
10983
11134
|
throw new ValidationError(
|
|
10984
11135
|
validated,
|
|
@@ -11000,6 +11151,31 @@ ${appendedSchemaText}`;
|
|
|
11000
11151
|
}
|
|
11001
11152
|
throw new MaxTurnsError("ctx.ask()", maxTurns);
|
|
11002
11153
|
}
|
|
11154
|
+
/**
|
|
11155
|
+
* Resolve the corrective feedback text for one rejected attempt, giving an optional
|
|
11156
|
+
* caller-supplied `retryFeedback` hook the chance to replace it or to stop retrying.
|
|
11157
|
+
*
|
|
11158
|
+
* Called by all three output gates, only while a retry is still permitted (never on the
|
|
11159
|
+
* exhausting attempt) and only after the gate's own event has been emitted, so a hook that
|
|
11160
|
+
* throws or aborts can never erase the evidence that the gate rejected. A non-empty string
|
|
11161
|
+
* replaces `defaultMessage` verbatim; `undefined` / `''` keeps it; `{ retry: false }` asks
|
|
11162
|
+
* the gate to throw its typed terminal error. Hook exceptions propagate — a bug in caller
|
|
11163
|
+
* code is not a model failure and must not be retried into silence, and neither is a
|
|
11164
|
+
* return value outside the contract: an unrecognized shape throws instead of silently
|
|
11165
|
+
* degrading to the default text, which would hide the defect behind extra token spend.
|
|
11166
|
+
*/
|
|
11167
|
+
async resolveRetryFeedback(hook, info) {
|
|
11168
|
+
if (!hook) return { retry: true, message: info.defaultMessage };
|
|
11169
|
+
const result = await hook(info, { metadata: this.metadata });
|
|
11170
|
+
if (result === void 0 || result === "") {
|
|
11171
|
+
return { retry: true, message: info.defaultMessage };
|
|
11172
|
+
}
|
|
11173
|
+
if (typeof result === "string") return { retry: true, message: result };
|
|
11174
|
+
if (isRetryAbort(result)) return { retry: false };
|
|
11175
|
+
throw new TypeError(
|
|
11176
|
+
`retryFeedback must return a string, undefined, or { retry: false }; received ${describeValue(result)}.`
|
|
11177
|
+
);
|
|
11178
|
+
}
|
|
11003
11179
|
/**
|
|
11004
11180
|
* Push the final assistant message into session history, preserving providerMetadata
|
|
11005
11181
|
* (e.g., Gemini thought signatures needed for multi-turn reasoning context) and
|
|
@@ -11155,6 +11331,57 @@ ${appendedSchemaText}`;
|
|
|
11155
11331
|
}
|
|
11156
11332
|
}
|
|
11157
11333
|
}
|
|
11334
|
+
/**
|
|
11335
|
+
* Emit a `provider_diagnostic` when the resolved provider reports that it
|
|
11336
|
+
* could not honor the unified `effort` verbatim (P3/R6/R7).
|
|
11337
|
+
*
|
|
11338
|
+
* The adapter only *resolves* — it returns the requested and effective levels
|
|
11339
|
+
* from the same pure resolver its request builder uses. The runtime decides
|
|
11340
|
+
* how to surface that, because only the runtime can see the event bus and
|
|
11341
|
+
* `AxlConfig.diagnostics.silent` (adapters receive `ProviderConfig` only).
|
|
11342
|
+
*
|
|
11343
|
+
* Called once per ask, so a tool loop with N provider round trips still emits
|
|
11344
|
+
* one event; the `console.warn` dedupes once per process per distinct clamp.
|
|
11345
|
+
* A throwing `effortResolution` is a provider bug and propagates.
|
|
11346
|
+
*/
|
|
11347
|
+
emitProviderDiagnostics(agent2, options, ctx) {
|
|
11348
|
+
if (!ctx.provider.effortResolution) return;
|
|
11349
|
+
const resolution = ctx.provider.effortResolution({
|
|
11350
|
+
model: ctx.model,
|
|
11351
|
+
effort: options?.effort ?? agent2._config.effort,
|
|
11352
|
+
thinkingBudget: options?.thinkingBudget ?? agent2._config.thinkingBudget,
|
|
11353
|
+
includeThoughts: options?.includeThoughts ?? agent2._config.includeThoughts,
|
|
11354
|
+
providerOptions: ctx.providerOptions
|
|
11355
|
+
});
|
|
11356
|
+
if (!resolution?.clamped) return;
|
|
11357
|
+
if (typeof resolution.effective !== "string" || resolution.effective.length === 0 || typeof resolution.requested !== "string" || resolution.requested.length === 0) {
|
|
11358
|
+
throw new TypeError(
|
|
11359
|
+
`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)}.`
|
|
11360
|
+
);
|
|
11361
|
+
}
|
|
11362
|
+
const cause = resolution.cause ?? `effort '${resolution.requested}' is not supported as asked`;
|
|
11363
|
+
this.emitEvent({
|
|
11364
|
+
type: "provider_diagnostic",
|
|
11365
|
+
agent: agent2._name,
|
|
11366
|
+
data: {
|
|
11367
|
+
kind: "effort_clamped",
|
|
11368
|
+
...ctx.provider.name ? { provider: ctx.provider.name } : {},
|
|
11369
|
+
model: ctx.model,
|
|
11370
|
+
requested: resolution.requested,
|
|
11371
|
+
effective: resolution.effective,
|
|
11372
|
+
cause
|
|
11373
|
+
}
|
|
11374
|
+
});
|
|
11375
|
+
warnDiagnosticOnce(
|
|
11376
|
+
// One warning per distinct clamp, not per ask: the same clamp across 500
|
|
11377
|
+
// asks is one process-level surprise, while a different requested/effective
|
|
11378
|
+
// pair is a genuinely new one.
|
|
11379
|
+
`effort_clamped\0${ctx.provider.name ?? ""}\0${ctx.model}\0${resolution.requested}\0${resolution.effective}`,
|
|
11380
|
+
`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.`,
|
|
11381
|
+
this.config.diagnostics?.silent,
|
|
11382
|
+
"docs/observability.md#provider-diagnostics"
|
|
11383
|
+
);
|
|
11384
|
+
}
|
|
11158
11385
|
buildToolDefs(agent2, resolvedHandoffs) {
|
|
11159
11386
|
const defs = [];
|
|
11160
11387
|
if (agent2._config.tools) {
|
|
@@ -12435,7 +12662,8 @@ ${summaryResponse.content}`
|
|
|
12435
12662
|
retries: options?.retries,
|
|
12436
12663
|
metadata: options?.metadata,
|
|
12437
12664
|
validate: options?.validate,
|
|
12438
|
-
validateRetries: options?.validateRetries
|
|
12665
|
+
validateRetries: options?.validateRetries,
|
|
12666
|
+
retryFeedback: options?.retryFeedback
|
|
12439
12667
|
});
|
|
12440
12668
|
}
|
|
12441
12669
|
const resolveCtx = options?.metadata ? { metadata: { ...this.metadata, ...options.metadata } } : { metadata: this.metadata };
|
|
@@ -12483,7 +12711,8 @@ ${summaryResponse.content}`
|
|
|
12483
12711
|
retries: options?.retries,
|
|
12484
12712
|
metadata: options?.metadata,
|
|
12485
12713
|
validate: options?.validate,
|
|
12486
|
-
validateRetries: options?.validateRetries
|
|
12714
|
+
validateRetries: options?.validateRetries,
|
|
12715
|
+
retryFeedback: options?.retryFeedback
|
|
12487
12716
|
});
|
|
12488
12717
|
}
|
|
12489
12718
|
/** Transcribe recorded audio through the dedicated transcription registry.
|