@axlsdk/axl 0.25.0 → 0.25.1

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.js CHANGED
@@ -40,6 +40,23 @@ var UnsupportedModelInputError = class extends AxlError {
40
40
  this.source = options.source;
41
41
  }
42
42
  };
43
+ var UnsupportedModelOptionError = class extends AxlError {
44
+ provider;
45
+ model;
46
+ option;
47
+ remediation;
48
+ constructor(options) {
49
+ super(
50
+ "UNSUPPORTED_MODEL_OPTION",
51
+ `Provider '${options.provider}' model '${options.model}' does not support ${options.option}. ${options.remediation}`
52
+ );
53
+ this.name = "UnsupportedModelOptionError";
54
+ this.provider = options.provider;
55
+ this.model = options.model;
56
+ this.option = options.option;
57
+ this.remediation = options.remediation;
58
+ }
59
+ };
43
60
  var InvalidTranscriptionInputError = class extends AxlError {
44
61
  constructor(message) {
45
62
  super("INVALID_TRANSCRIPTION_INPUT", message);
@@ -331,7 +348,7 @@ var sharedGuard = typeof installedGuard === "object" && installedGuard !== null
331
348
  var buildPath = typeof __AXL_BUILD_PATH__ === "string" ? __AXL_BUILD_PATH__ : "<source>";
332
349
  var thisCopy = {
333
350
  path: buildPath.startsWith("file:") ? fileURLToPath(buildPath) : buildPath,
334
- version: true ? "0.25.0" : "unknown"
351
+ version: true ? "0.25.1" : "unknown"
335
352
  };
336
353
  function warnCrossCopy(other, joined) {
337
354
  if (other === thisCopy) return;
@@ -3204,8 +3221,12 @@ var OpenAICompatibleProvider = class {
3204
3221
  for (const key of forbidden) {
3205
3222
  if (!userKeys.includes(key)) delete body[key];
3206
3223
  }
3224
+ this.validateFinalRequestBody(body);
3207
3225
  return body;
3208
3226
  }
3227
+ /** Native subclasses may reject model-invalid fields after providerOptions merge. */
3228
+ validateFinalRequestBody(_body) {
3229
+ }
3209
3230
  formatMessage(msg, model) {
3210
3231
  const modalities = this.inputModalities(model);
3211
3232
  const out = {
@@ -3613,6 +3634,24 @@ var withLongContext = (short) => ({
3613
3634
  output: short.output * 1.5
3614
3635
  });
3615
3636
  var DIRECT_OPENAI_CATALOG = [
3637
+ {
3638
+ aliases: ["gpt-6-astra"],
3639
+ short: { input: 10 / M, cachedInput: 1 / M, cacheWrite: 12.5 / M, output: 50 / M },
3640
+ long: { input: 20 / M, cachedInput: 2 / M, cacheWrite: 25 / M, output: 75 / M },
3641
+ contextBoundary: LONG_CONTEXT_BOUNDARY
3642
+ },
3643
+ {
3644
+ aliases: ["gpt-6-sol"],
3645
+ short: { input: 2 / M, cachedInput: 0.2 / M, cacheWrite: 2.5 / M, output: 10 / M },
3646
+ long: { input: 4 / M, cachedInput: 0.4 / M, cacheWrite: 5 / M, output: 15 / M },
3647
+ contextBoundary: LONG_CONTEXT_BOUNDARY
3648
+ },
3649
+ {
3650
+ aliases: ["gpt-6-luna"],
3651
+ short: { input: 0.1 / M, cachedInput: 0.01 / M, cacheWrite: 0.125 / M, output: 0.5 / M },
3652
+ long: { input: 0.2 / M, cachedInput: 0.02 / M, cacheWrite: 0.25 / M, output: 0.75 / M },
3653
+ contextBoundary: LONG_CONTEXT_BOUNDARY
3654
+ },
3616
3655
  {
3617
3656
  aliases: ["gpt-5.6", "gpt-5.6-sol"],
3618
3657
  snapshotBase: "gpt-5.6-sol",
@@ -3943,7 +3982,7 @@ function estimateDirectOpenAICost(model, usage, context) {
3943
3982
  const audioOutput = audio_output_tokens ?? 0;
3944
3983
  if (![prompt_tokens, completion_tokens, cached, cacheWrite, audioInput, audioOutput].every(
3945
3984
  (count) => Number.isSafeInteger(count) && count >= 0
3946
- ) || cached + cacheWrite + audioInput > prompt_tokens || audioOutput > completion_tokens) {
3985
+ ) || resolveOpenAIModelDescriptor(model).strictUsageTotals && (!Number.isSafeInteger(usage.total_tokens) || usage.total_tokens !== prompt_tokens + completion_tokens) || cached + cacheWrite + audioInput > prompt_tokens || audioOutput > completion_tokens) {
3947
3986
  return void 0;
3948
3987
  }
3949
3988
  if (audioInput > 0 && entry.audio === void 0 || audioOutput > 0 && entry.audio?.output === void 0) {
@@ -3968,57 +4007,206 @@ function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens)
3968
4007
  cached_tokens: cachedTokens
3969
4008
  });
3970
4009
  }
3971
- function isOSeriesModel(model) {
3972
- return /^(o1|o3|o4-mini)/.test(model);
4010
+ var EFFORT_FALLBACK = {
4011
+ none: "minimal",
4012
+ minimal: "low",
4013
+ low: "medium",
4014
+ medium: "high",
4015
+ max: "xhigh",
4016
+ xhigh: "high"
4017
+ };
4018
+ var BASE_EFFORTS = ["minimal", "low", "medium", "high"];
4019
+ function bothEndpoints(value) {
4020
+ return { chat: value, responses: value };
3973
4021
  }
3974
- function supportsReasoningEffort(model) {
3975
- return isOSeriesModel(model) || /^gpt-5/.test(model);
4022
+ function gpt6(id, efforts, chatTools) {
4023
+ return {
4024
+ id,
4025
+ family: "gpt-6",
4026
+ reasoning: true,
4027
+ efforts: bothEndpoints(efforts),
4028
+ defaultEffort: "medium",
4029
+ // Same policy as GPT-5.x: a portable `temperature` is dropped while
4030
+ // reasoning is active (including the active default). A raw
4031
+ // `providerOptions` sampling field is rejected by the final-body check.
4032
+ sampling: bothEndpoints("unless-effort-none"),
4033
+ chatTools,
4034
+ validatesFinalBody: true,
4035
+ strictUsageTotals: true
4036
+ };
3976
4037
  }
3977
- var GPT_56_REASONING_MODELS = /* @__PURE__ */ new Set([
3978
- "gpt-5.6",
3979
- "gpt-5.6-sol",
3980
- "gpt-5.6-terra",
3981
- "gpt-5.6-luna"
3982
- ]);
3983
- function supportsMaxReasoningEffort(model) {
3984
- return GPT_56_REASONING_MODELS.has(model);
4038
+ function gpt56(id) {
4039
+ const chat = ["none", ...BASE_EFFORTS, "xhigh"];
4040
+ return {
4041
+ id,
4042
+ family: "gpt-5.6",
4043
+ reasoning: true,
4044
+ efforts: { chat, responses: [...chat, "max"] },
4045
+ // Responses rejects temperature even at the provider-default effort.
4046
+ sampling: { chat: "when-effort-sent", responses: "always" },
4047
+ chatTools: "allowed",
4048
+ validatesFinalBody: false,
4049
+ strictUsageTotals: false
4050
+ };
3985
4051
  }
3986
- function supportsReasoningNone(model) {
3987
- return /^gpt-5\.[1-9]/.test(model);
4052
+ var OPENAI_EXACT_MODELS = new Map(
4053
+ [
4054
+ gpt6("gpt-6-astra", ["low", "medium", "high", "xhigh", "max"], "never"),
4055
+ gpt6("gpt-6-sol", ["none", "low", "medium", "high", "xhigh", "max"], "effort-none-only"),
4056
+ gpt6("gpt-6-luna", ["none", "low", "medium", "high", "xhigh", "max"], "effort-none-only"),
4057
+ gpt56("gpt-5.6"),
4058
+ gpt56("gpt-5.6-sol"),
4059
+ gpt56("gpt-5.6-terra"),
4060
+ gpt56("gpt-5.6-luna")
4061
+ ].map((descriptor) => [descriptor.id, descriptor])
4062
+ );
4063
+ var O_SERIES_DESCRIPTOR = {
4064
+ id: "o-series",
4065
+ family: "o-series",
4066
+ reasoning: true,
4067
+ efforts: bothEndpoints(BASE_EFFORTS),
4068
+ defaultEffort: "medium",
4069
+ sampling: bothEndpoints("always"),
4070
+ chatTools: "allowed",
4071
+ validatesFinalBody: false,
4072
+ strictUsageTotals: false
4073
+ };
4074
+ var GPT_5_PRO_DESCRIPTOR = {
4075
+ id: "gpt-5-pro",
4076
+ family: "gpt-5-pro",
4077
+ reasoning: true,
4078
+ efforts: bothEndpoints(["high"]),
4079
+ defaultEffort: "high",
4080
+ sampling: bothEndpoints("when-effort-sent"),
4081
+ chatTools: "allowed",
4082
+ validatesFinalBody: false,
4083
+ strictUsageTotals: false
4084
+ };
4085
+ var DEFAULT_DESCRIPTOR = {
4086
+ id: "default",
4087
+ family: "default",
4088
+ reasoning: false,
4089
+ // Never emitted (the model is not reasoning-capable); keeps the clamp total.
4090
+ efforts: bothEndpoints(BASE_EFFORTS),
4091
+ sampling: bothEndpoints("never"),
4092
+ chatTools: "allowed",
4093
+ validatesFinalBody: false,
4094
+ strictUsageTotals: false
4095
+ };
4096
+ function gpt5FamilyDescriptor(model) {
4097
+ const none = /^gpt-5\.[1-9]/.test(model);
4098
+ const xhigh = /^gpt-5\.([2-9]|\d{2,})/.test(model);
4099
+ const efforts = [
4100
+ ...none ? ["none"] : [],
4101
+ ...BASE_EFFORTS,
4102
+ ...xhigh ? ["xhigh"] : []
4103
+ ];
4104
+ return {
4105
+ id: "gpt-5",
4106
+ family: "gpt-5",
4107
+ reasoning: true,
4108
+ efforts: bothEndpoints(efforts),
4109
+ ...none ? {} : { defaultEffort: "medium" },
4110
+ sampling: bothEndpoints("when-effort-sent"),
4111
+ chatTools: "allowed",
4112
+ validatesFinalBody: false,
4113
+ strictUsageTotals: false
4114
+ };
3988
4115
  }
3989
- function supportsXhigh(model) {
3990
- return /^gpt-5\.([2-9]|\d{2,})/.test(model);
4116
+ var OPENAI_FAMILY_FALLBACKS = [
4117
+ { match: /^(o1|o3|o4-mini)/, describe: () => O_SERIES_DESCRIPTOR },
4118
+ { match: /^gpt-5-pro/, describe: () => GPT_5_PRO_DESCRIPTOR },
4119
+ { match: /^gpt-5/, describe: gpt5FamilyDescriptor }
4120
+ ];
4121
+ function resolveOpenAIModelDescriptor(model) {
4122
+ const exact = OPENAI_EXACT_MODELS.get(model);
4123
+ if (exact) return exact;
4124
+ const family = OPENAI_FAMILY_FALLBACKS.find((entry) => entry.match.test(model));
4125
+ return family ? family.describe(model) : DEFAULT_DESCRIPTOR;
4126
+ }
4127
+ function clampToDescriptor(descriptor, endpoint, effort) {
4128
+ const accepted = descriptor.efforts[endpoint];
4129
+ let candidate = effort;
4130
+ while (candidate !== void 0 && !accepted.includes(candidate)) {
4131
+ candidate = EFFORT_FALLBACK[candidate];
4132
+ }
4133
+ if (candidate === void 0) {
4134
+ throw new Error(
4135
+ `OpenAI descriptor '${descriptor.id}' accepts no ${endpoint} effort reachable from '${effort}'`
4136
+ );
4137
+ }
4138
+ return candidate;
3991
4139
  }
3992
- function clampReasoningEffort(model, effort) {
3993
- if (model.startsWith("gpt-5-pro")) return "high";
3994
- if (effort === "none" && !supportsReasoningNone(model)) return "minimal";
3995
- if (effort === "max" && !supportsMaxReasoningEffort(model)) {
3996
- return clampReasoningEffort(model, "xhigh");
4140
+ function stripsPortableSampling(descriptor, endpoint, wireEffort) {
4141
+ switch (descriptor.sampling[endpoint]) {
4142
+ case "never":
4143
+ return false;
4144
+ case "always":
4145
+ return true;
4146
+ case "when-effort-sent":
4147
+ return wireEffort !== void 0;
4148
+ case "unless-effort-none":
4149
+ return (wireEffort ?? descriptor.defaultEffort) !== "none";
3997
4150
  }
3998
- if (effort === "xhigh" && !supportsXhigh(model)) return "high";
3999
- return effort;
4000
4151
  }
4001
- function effortToReasoningEffort(effort, model) {
4002
- return effort === "max" && model !== void 0 && supportsMaxReasoningEffort(model) ? "max" : effort === "max" ? "xhigh" : effort;
4152
+ function isOSeriesModel(model) {
4153
+ return resolveOpenAIModelDescriptor(model).family === "o-series";
4003
4154
  }
4004
4155
  function budgetToReasoningEffort(budget) {
4005
4156
  if (budget <= 1024) return "low";
4006
4157
  if (budget <= 8192) return "medium";
4007
4158
  return "high";
4008
4159
  }
4160
+ function resolveEndpointReasoningEffort(model, endpoint, resolved) {
4161
+ const descriptor = resolveOpenAIModelDescriptor(model);
4162
+ if (!descriptor.reasoning) return void 0;
4163
+ const requested = resolved.hasBudgetOverride ? budgetToReasoningEffort(resolved.thinkingBudget) : !resolved.thinkingDisabled && resolved.activeEffort ? resolved.activeEffort : resolved.thinkingDisabled ? "none" : void 0;
4164
+ return requested === void 0 ? void 0 : clampToDescriptor(descriptor, endpoint, requested);
4165
+ }
4009
4166
  function resolveOpenAIReasoningEffort(model, resolved) {
4010
- if (!supportsReasoningEffort(model)) return void 0;
4011
- if (resolved.hasBudgetOverride) {
4012
- return clampReasoningEffort(model, budgetToReasoningEffort(resolved.thinkingBudget));
4013
- }
4014
- if (!resolved.thinkingDisabled && resolved.activeEffort) {
4015
- return clampReasoningEffort(model, effortToReasoningEffort(resolved.activeEffort, model));
4016
- }
4017
- return resolved.thinkingDisabled ? clampReasoningEffort(model, "none") : void 0;
4167
+ return resolveEndpointReasoningEffort(model, "responses", resolved);
4018
4168
  }
4019
4169
  function resolveOpenAIChatReasoningEffort(model, resolved) {
4020
- const effort = resolveOpenAIReasoningEffort(model, resolved);
4021
- return effort === "max" ? "xhigh" : effort;
4170
+ return resolveEndpointReasoningEffort(model, "chat", resolved);
4171
+ }
4172
+ function validateOpenAIFinalRequestBody(body, endpoint) {
4173
+ const model = body.model;
4174
+ if (typeof model !== "string") return;
4175
+ const descriptor = resolveOpenAIModelDescriptor(model);
4176
+ if (!descriptor.validatesFinalBody) return;
4177
+ const provider = endpoint === "chat" ? "openai" : "openai-responses";
4178
+ const fail = (option, remediation) => {
4179
+ throw new UnsupportedModelOptionError({ provider, model, option, remediation });
4180
+ };
4181
+ const rawEffort = endpoint === "chat" ? body.reasoning_effort : body.reasoning !== null && typeof body.reasoning === "object" ? body.reasoning.effort : void 0;
4182
+ const accepted = descriptor.efforts[endpoint];
4183
+ if (rawEffort !== void 0 && !accepted.includes(rawEffort)) {
4184
+ fail("reasoning effort", `Use one of: ${accepted.join(", ")}.`);
4185
+ }
4186
+ const activeReasoning = (rawEffort ?? descriptor.defaultEffort) !== "none";
4187
+ if (endpoint === "chat" && descriptor.chatTools !== "allowed") {
4188
+ const toolChoice = body.tool_choice;
4189
+ const toolIntent = Array.isArray(body.tools) && body.tools.length > 0 || toolChoice !== void 0 && toolChoice !== null && toolChoice !== "none" && toolChoice !== "auto";
4190
+ if (toolIntent && (descriptor.chatTools === "never" || activeReasoning)) {
4191
+ fail(
4192
+ "Chat Completions tool calling",
4193
+ descriptor.chatTools === "never" ? `Use openai-responses:${model} for tools.` : `Use openai-responses:${model} for reasoning with tools, or set effective reasoning_effort to 'none'.`
4194
+ );
4195
+ }
4196
+ }
4197
+ if (activeReasoning) {
4198
+ const forbidden = endpoint === "chat" ? ["temperature", "top_p", "top_logprobs", "logprobs"] : ["temperature", "top_p", "top_logprobs"];
4199
+ for (const key of forbidden) {
4200
+ if (body[key] !== void 0)
4201
+ fail(key, `Remove ${key} or set reasoning effort to 'none' on Sol or Luna.`);
4202
+ }
4203
+ if (endpoint === "responses" && Array.isArray(body.include) && body.include.includes("message.output_text.logprobs")) {
4204
+ fail(
4205
+ "message.output_text.logprobs",
4206
+ "Remove it from include or set reasoning effort to none on Sol or Luna."
4207
+ );
4208
+ }
4209
+ }
4022
4210
  }
4023
4211
  function resolveOpenAIEffortResolution(options, resolveWireEffort, endpoint) {
4024
4212
  const resolved = resolveThinkingOptions(options);
@@ -4034,11 +4222,15 @@ function resolveOpenAIEffortResolution(options, resolveWireEffort, endpoint) {
4034
4222
  };
4035
4223
  }
4036
4224
  var openaiReasoningEmit = (body, resolved, model) => {
4037
- const oSeries = isOSeriesModel(model);
4038
- const reasoningCapable = supportsReasoningEffort(model);
4039
4225
  const wireEffort = resolveOpenAIChatReasoningEffort(model, resolved);
4040
4226
  if (wireEffort) body.reasoning_effort = wireEffort;
4041
- return { stripTemperature: oSeries || reasoningCapable && wireEffort !== void 0 };
4227
+ return {
4228
+ stripTemperature: stripsPortableSampling(
4229
+ resolveOpenAIModelDescriptor(model),
4230
+ "chat",
4231
+ wireEffort
4232
+ )
4233
+ };
4042
4234
  };
4043
4235
  var OPENAI_PROFILE = {
4044
4236
  name: "openai",
@@ -4073,6 +4265,9 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
4073
4265
  "OpenAI Chat Completions"
4074
4266
  );
4075
4267
  }
4268
+ validateFinalRequestBody(body) {
4269
+ validateOpenAIFinalRequestBody(body, "chat");
4270
+ }
4076
4271
  computeCost(model, usage, _reportedCost, context) {
4077
4272
  return usage ? estimateDirectOpenAICost(model, usage, { ...context, baseUrl: this.baseUrl }) : void 0;
4078
4273
  }
@@ -4345,12 +4540,12 @@ var OpenAIResponsesProvider = class {
4345
4540
  // ---------------------------------------------------------------------------
4346
4541
  buildRequestBody(messages, options, stream) {
4347
4542
  const effectiveModel = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
4348
- const oSeries = isOSeriesModel(effectiveModel);
4349
- const reasoningCapable = supportsReasoningEffort(effectiveModel);
4543
+ const descriptor = resolveOpenAIModelDescriptor(effectiveModel);
4544
+ const reasoningCapable = descriptor.reasoning;
4350
4545
  const resolved = resolveThinkingOptions(options);
4351
4546
  const { includeThoughts } = resolved;
4352
4547
  const wireEffort = resolveOpenAIReasoningEffort(effectiveModel, resolved);
4353
- const stripTemp = oSeries || supportsMaxReasoningEffort(effectiveModel) || reasoningCapable && wireEffort !== void 0;
4548
+ const stripTemp = stripsPortableSampling(descriptor, "responses", wireEffort);
4354
4549
  const systemMessages = messages.filter((m) => m.role === "system");
4355
4550
  const nonSystemMessages = messages.filter((m) => m.role !== "system");
4356
4551
  const body = {
@@ -4399,6 +4594,7 @@ var OpenAIResponsesProvider = class {
4399
4594
  if (options.providerOptions) {
4400
4595
  Object.assign(body, options.providerOptions);
4401
4596
  }
4597
+ validateOpenAIFinalRequestBody(body, "responses");
4402
4598
  return body;
4403
4599
  }
4404
4600
  requestModel(body, fallback) {
@@ -4713,6 +4909,36 @@ function anthropicImageBlocks(parts, model) {
4713
4909
  }
4714
4910
  var ANTHROPIC_API_VERSION = "2023-06-01";
4715
4911
  var ANTHROPIC_FILES_BETA = "files-api-2025-04-14";
4912
+ var ANTHROPIC_THINKING_BINDING_BETA = "thinking-binding-controls-2026-08-01";
4913
+ function isRecord(value) {
4914
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4915
+ }
4916
+ function hasReplayedThinking(body) {
4917
+ return Array.isArray(body.messages) && body.messages.some(
4918
+ (message) => isRecord(message) && Array.isArray(message.content) && message.content.some(
4919
+ (block) => isRecord(block) && (block.type === "thinking" || block.type === "redacted_thinking")
4920
+ )
4921
+ );
4922
+ }
4923
+ function hasFinalFileImage(body) {
4924
+ return Array.isArray(body.messages) && body.messages.some(
4925
+ (message) => isRecord(message) && Array.isArray(message.content) && message.content.some(
4926
+ (block) => isRecord(block) && block.type === "image" && isRecord(block.source) && block.source.type === "file"
4927
+ )
4928
+ );
4929
+ }
4930
+ function normalizeThinkingDrops(value) {
4931
+ if (!Array.isArray(value)) return void 0;
4932
+ const reasons = {};
4933
+ let droppedBlocks = 0;
4934
+ for (const entry of value) {
4935
+ if (!isRecord(entry) || entry.type !== "thinking_dropped") continue;
4936
+ const reason = entry.reason === "prefix_binding_mismatch" || entry.reason === "model_binding_mismatch" || entry.reason === "organization_binding_mismatch" || entry.reason === "end_user_binding_mismatch" ? entry.reason : "other";
4937
+ reasons[reason] = (reasons[reason] ?? 0) + 1;
4938
+ droppedBlocks++;
4939
+ }
4940
+ return droppedBlocks > 0 ? { droppedBlocks, reasons } : void 0;
4941
+ }
4716
4942
  function hasAnthropicProviderFileImage(messages) {
4717
4943
  return messages.some(
4718
4944
  (message) => Array.isArray(message.content) && message.content.some(
@@ -4721,11 +4947,19 @@ function hasAnthropicProviderFileImage(messages) {
4721
4947
  );
4722
4948
  }
4723
4949
  var CLAUDE_CAPABILITIES = {
4950
+ "claude-opus-5-5": {
4951
+ thinking: "adaptive-always-on",
4952
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
4953
+ manualBudget: false,
4954
+ stripTemperature: true,
4955
+ thinkingBinding: true
4956
+ },
4724
4957
  "claude-fable-5-1": {
4725
4958
  thinking: "adaptive-always-on",
4726
4959
  effortLevels: ["low", "medium", "high", "xhigh", "max"],
4727
4960
  manualBudget: false,
4728
- stripTemperature: true
4961
+ stripTemperature: true,
4962
+ thinkingBinding: true
4729
4963
  },
4730
4964
  "claude-fable-5": {
4731
4965
  thinking: "adaptive-always-on",
@@ -4819,6 +5053,7 @@ function resolveClaudeCapability(model) {
4819
5053
  }
4820
5054
  var DEFAULT_CACHE_READ_MULTIPLIER = 0.1;
4821
5055
  var ANTHROPIC_RATES = {
5056
+ "claude-opus-5-5": { input: 4e-6, output: 2e-5, cacheReadMultiplier: 0.05 },
4822
5057
  "claude-fable-5-1": { input: 1e-5, output: 5e-5, cacheReadMultiplier: 0.025 },
4823
5058
  "claude-fable-5": { input: 1e-5, output: 5e-5 },
4824
5059
  // Limited availability (Glasswing). Priced from the public pricing table; no
@@ -5253,11 +5488,12 @@ var AnthropicProvider = class {
5253
5488
  // chat - non-streaming completion
5254
5489
  // ---------------------------------------------------------------------------
5255
5490
  async chat(messages, options) {
5491
+ const body = this.buildRequestBody(messages, options, false);
5256
5492
  const headers = this.buildHeaders(
5257
5493
  await this.resolveKey(),
5258
- hasAnthropicProviderFileImage(messages)
5494
+ hasAnthropicProviderFileImage(messages) || hasFinalFileImage(body),
5495
+ this.requiresBindingBeta(body)
5259
5496
  );
5260
- const body = this.buildRequestBody(messages, options, false);
5261
5497
  const pricingContext = pricingContextFromBody(body);
5262
5498
  const recorder = new CallTimingRecorder(options.requestLifecycle);
5263
5499
  const governor = this.governorFor(pricingContext.model ?? options.model);
@@ -5295,11 +5531,12 @@ var AnthropicProvider = class {
5295
5531
  // stream - SSE streaming completion
5296
5532
  // ---------------------------------------------------------------------------
5297
5533
  async *stream(messages, options) {
5534
+ const body = this.buildRequestBody(messages, options, true);
5298
5535
  const headers = this.buildHeaders(
5299
5536
  await this.resolveKey(),
5300
- hasAnthropicProviderFileImage(messages)
5537
+ hasAnthropicProviderFileImage(messages) || hasFinalFileImage(body),
5538
+ this.requiresBindingBeta(body)
5301
5539
  );
5302
- const body = this.buildRequestBody(messages, options, true);
5303
5540
  const pricingContext = pricingContextFromBody(body);
5304
5541
  const recorder = new CallTimingRecorder(options.requestLifecycle);
5305
5542
  const governor = this.governorFor(pricingContext.model ?? options.model);
@@ -5341,12 +5578,61 @@ var AnthropicProvider = class {
5341
5578
  // ---------------------------------------------------------------------------
5342
5579
  // Internal: request building
5343
5580
  // ---------------------------------------------------------------------------
5344
- buildHeaders(apiKey, requiresFilesBeta = false) {
5581
+ buildHeaders(apiKey, requiresFilesBeta = false, requiresBindingBeta = false) {
5582
+ const betas = [
5583
+ ...requiresFilesBeta ? [ANTHROPIC_FILES_BETA] : [],
5584
+ ...requiresBindingBeta ? [ANTHROPIC_THINKING_BINDING_BETA] : []
5585
+ ];
5345
5586
  return {
5346
5587
  "Content-Type": "application/json",
5347
5588
  "x-api-key": apiKey,
5348
5589
  "anthropic-version": ANTHROPIC_API_VERSION,
5349
- ...requiresFilesBeta ? { "anthropic-beta": ANTHROPIC_FILES_BETA } : {}
5590
+ ...betas.length > 0 ? { "anthropic-beta": betas.join(",") } : {}
5591
+ };
5592
+ }
5593
+ requiresBindingBeta(body) {
5594
+ const thinking = body.thinking;
5595
+ return resolveClaudeCapability(String(body.model))?.thinkingBinding === true && (hasReplayedThinking(body) || isRecord(thinking) && thinking.block_binding !== void 0);
5596
+ }
5597
+ validateAndBindFinalBody(body) {
5598
+ const model = typeof body.model === "string" ? body.model : "";
5599
+ if (resolveClaudeCapability(model)?.thinkingBinding !== true) return;
5600
+ const reject = (option, remediation) => {
5601
+ throw new UnsupportedModelOptionError({ provider: this.name, model, option, remediation });
5602
+ };
5603
+ const toolChoice = body.tool_choice;
5604
+ if (isRecord(toolChoice) && (toolChoice.type === "any" || toolChoice.type === "tool")) {
5605
+ reject("forced tool choice", "Use toolChoice: 'auto' or 'none' on this model.");
5606
+ }
5607
+ if (body.thinking !== void 0 && (!isRecord(body.thinking) || body.thinking.type !== "adaptive" || body.thinking.budget_tokens !== void 0)) {
5608
+ reject(
5609
+ "manual or disabled thinking",
5610
+ "Use thinking: { type: 'adaptive' } and output_config.effort."
5611
+ );
5612
+ }
5613
+ const outputConfig = body.output_config;
5614
+ if (outputConfig !== void 0 && (!isRecord(outputConfig) || outputConfig.effort !== void 0 && !["low", "medium", "high", "xhigh", "max"].includes(String(outputConfig.effort)))) {
5615
+ reject("output_config.effort", "Use low, medium, high, xhigh, or max effort.");
5616
+ }
5617
+ if (body.temperature !== void 0 && body.temperature !== 1) {
5618
+ reject("temperature", "Omit temperature or use the default value 1.");
5619
+ }
5620
+ if (body.top_p !== void 0 && (typeof body.top_p !== "number" || body.top_p < 0.99 || body.top_p > 1)) {
5621
+ reject("top_p", "Omit top_p or use a value from 0.99 to 1.");
5622
+ }
5623
+ if (body.top_k !== void 0) {
5624
+ reject("top_k", "Omit top_k on this model.");
5625
+ }
5626
+ const replayed = hasReplayedThinking(body);
5627
+ if (!replayed) return;
5628
+ const thinking = isRecord(body.thinking) ? body.thinking : { type: "adaptive" };
5629
+ const binding = thinking.block_binding;
5630
+ if (binding !== void 0 && (!isRecord(binding) || binding.prefix_mismatch_behavior !== "error" && binding.prefix_mismatch_behavior !== "drop_block")) {
5631
+ reject("thinking.block_binding", "Use prefix_mismatch_behavior: 'error' or 'drop_block'.");
5632
+ }
5633
+ body.thinking = {
5634
+ ...thinking,
5635
+ block_binding: binding ?? { prefix_mismatch_behavior: "drop_block" }
5350
5636
  };
5351
5637
  }
5352
5638
  /** Extract a human-readable message from an API error response body. */
@@ -5425,6 +5711,7 @@ ${jsonInstruction}` : jsonInstruction;
5425
5711
  if (options.providerOptions) {
5426
5712
  Object.assign(body, options.providerOptions);
5427
5713
  }
5714
+ this.validateAndBindFinalBody(body);
5428
5715
  return body;
5429
5716
  }
5430
5717
  /**
@@ -5581,6 +5868,7 @@ ${jsonInstruction}` : jsonInstruction;
5581
5868
  }
5582
5869
  }
5583
5870
  const normalized = json.usage ? normalizeAnthropicUsage(json.usage) : void 0;
5871
+ const reasoningContextReset = normalizeThinkingDrops(json.input_transformations);
5584
5872
  const effectiveModel = typeof json.model === "string" ? json.model : pricingContext.model;
5585
5873
  if (hasFallbackBoundary) {
5586
5874
  throw new Error(
@@ -5595,7 +5883,8 @@ ${jsonInstruction}` : jsonInstruction;
5595
5883
  usage: normalized?.usage,
5596
5884
  cost,
5597
5885
  costProvenance: tableEstimate(cost),
5598
- providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
5886
+ providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary ? { anthropicThinkingBlocks: thinkingBlocks } : void 0,
5887
+ ...reasoningContextReset ? { diagnostics: { reasoningContextReset } } : {}
5599
5888
  };
5600
5889
  }
5601
5890
  // ---------------------------------------------------------------------------
@@ -5617,6 +5906,7 @@ ${jsonInstruction}` : jsonInstruction;
5617
5906
  let hasFallbackBoundary = false;
5618
5907
  let hasFallbackIterationSignal = false;
5619
5908
  let refused = false;
5909
+ let reasoningContextReset;
5620
5910
  const finalizeUsage = () => {
5621
5911
  const normalized = rawUsage ? normalizeAnthropicUsage(rawUsage) : void 0;
5622
5912
  usage = normalized?.usage;
@@ -5629,7 +5919,8 @@ ${jsonInstruction}` : jsonInstruction;
5629
5919
  usage,
5630
5920
  cost,
5631
5921
  costProvenance: tableEstimate(cost),
5632
- providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
5922
+ providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0,
5923
+ ...reasoningContextReset ? { diagnostics: { reasoningContextReset } } : {}
5633
5924
  };
5634
5925
  };
5635
5926
  try {
@@ -5722,6 +6013,7 @@ ${jsonInstruction}` : jsonInstruction;
5722
6013
  rawUsage = mergeAnthropicUsage(rawUsage, event.message.usage);
5723
6014
  }
5724
6015
  if (typeof event.message?.model === "string") effectiveModel = event.message.model;
6016
+ reasoningContextReset = normalizeThinkingDrops(event.message?.input_transformations);
5725
6017
  const messageFallback = hasFallbackIteration(event.message?.usage?.iterations);
5726
6018
  if (isAnthropicResponseModifier(
5727
6019
  "inference_geo",
@@ -5738,6 +6030,10 @@ ${jsonInstruction}` : jsonInstruction;
5738
6030
  break;
5739
6031
  }
5740
6032
  case "message_delta": {
6033
+ const finalTransformations = event.input_transformations ?? event.delta?.input_transformations;
6034
+ if (finalTransformations !== void 0) {
6035
+ reasoningContextReset = normalizeThinkingDrops(finalTransformations);
6036
+ }
5741
6037
  if (event.usage) {
5742
6038
  rawUsage = mergeAnthropicUsage(rawUsage, event.usage);
5743
6039
  finalizeUsage();
@@ -5966,7 +6262,31 @@ var GEMINI_PRICING = {
5966
6262
  },
5967
6263
  "gemini-3.8-flash": { input: 75e-8, cached: 75e-9, output: 375e-8 }
5968
6264
  };
6265
+ var GEMINI_FLASH_SUCCESSOR_PRICING = {
6266
+ ...GEMINI_PRICING,
6267
+ "gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 },
6268
+ "gemini-3.7-flash": {
6269
+ input: 15e-7,
6270
+ cached: 15e-8,
6271
+ output: 75e-7,
6272
+ audioInput: 15e-7
6273
+ },
6274
+ "gemini-3.8-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
6275
+ };
6276
+ var GEMINI_FLASH_SUCCESSOR_START_MS = Date.UTC(2027, 0, 1);
6277
+ function geminiRatesAtDispatch(at) {
6278
+ return at < GEMINI_FLASH_SUCCESSOR_START_MS ? GEMINI_PRICING : GEMINI_FLASH_SUCCESSOR_PRICING;
6279
+ }
5969
6280
  var CANONICAL_GEMINI_BASE_URL = GEMINI_DEFAULT_BASE_URL;
6281
+ function geminiPricingTimingObserver(context, recorder) {
6282
+ return {
6283
+ ...recorder.observer,
6284
+ onDispatch: (attempt, at) => {
6285
+ context.rates = geminiRatesAtDispatch(at);
6286
+ recorder.observer.onDispatch?.(attempt, at);
6287
+ }
6288
+ };
6289
+ }
5970
6290
  function isValidTokenCount2(value) {
5971
6291
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
5972
6292
  }
@@ -6047,8 +6367,8 @@ function geminiInteractionErrorStatus(code) {
6047
6367
  function isEligibleGeminiPricing(context, hasDefinitiveStandardResponseTier, hasInvalidResponseTier) {
6048
6368
  return context.eligibleRequest && isStandardGeminiRequestTier(context.serviceTier) && hasDefinitiveStandardResponseTier && !hasInvalidResponseTier;
6049
6369
  }
6050
- function estimateGeminiCost(model, usage) {
6051
- const pricing = GEMINI_PRICING[model];
6370
+ function estimateGeminiCost(model, usage, rates) {
6371
+ const pricing = rates && Object.hasOwn(rates, model) ? rates[model] : void 0;
6052
6372
  const cached = usage.cachedTokens ?? 0;
6053
6373
  if (!pricing || !isValidTokenCount2(usage.inputTokens) || !isValidTokenCount2(usage.outputTokens) || !isValidTokenCount2(cached) || cached > usage.inputTokens) {
6054
6374
  return void 0;
@@ -6376,7 +6696,7 @@ var GeminiProvider = class {
6376
6696
  {
6377
6697
  governor: this.governorFor(pricingContext.model),
6378
6698
  provider: this.name,
6379
- timing: recorder.observer,
6699
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6380
6700
  admission: options.dispatchAdmission
6381
6701
  }
6382
6702
  );
@@ -6419,7 +6739,7 @@ var GeminiProvider = class {
6419
6739
  {
6420
6740
  governor: this.governorFor(pricingContext.model),
6421
6741
  provider: this.name,
6422
- timing: recorder.observer,
6742
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6423
6743
  admission: options.dispatchAdmission
6424
6744
  }
6425
6745
  );
@@ -6457,7 +6777,7 @@ var GeminiProvider = class {
6457
6777
  {
6458
6778
  governor: this.governorFor(pricingContext.model),
6459
6779
  provider: this.name,
6460
- timing: recorder.observer,
6780
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6461
6781
  admission: options.dispatchAdmission
6462
6782
  }
6463
6783
  );
@@ -6493,7 +6813,7 @@ var GeminiProvider = class {
6493
6813
  {
6494
6814
  governor: this.governorFor(pricingContext.model),
6495
6815
  provider: this.name,
6496
- timing: recorder.observer,
6816
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6497
6817
  admission: options.dispatchAdmission
6498
6818
  }
6499
6819
  );
@@ -6712,7 +7032,7 @@ var GeminiProvider = class {
6712
7032
  )) {
6713
7033
  return void 0;
6714
7034
  }
6715
- return estimateGeminiCost(effectiveModel, normalized.pricingUsage);
7035
+ return estimateGeminiCost(effectiveModel, normalized.pricingUsage, pricingContext.rates);
6716
7036
  }
6717
7037
  /**
6718
7038
  * Whether every model-produced step is text. A non-text reply part (image,
@@ -7297,7 +7617,7 @@ var GeminiProvider = class {
7297
7617
  pricingContext,
7298
7618
  isDefinitiveStandardGeminiResponseTier(json.usageMetadata?.serviceTier),
7299
7619
  json.usageMetadata?.serviceTier !== void 0 && !isDefinitiveStandardGeminiResponseTier(json.usageMetadata.serviceTier)
7300
- ) ? estimateGeminiCost(effectiveModel, normalized.pricingUsage) : void 0;
7620
+ ) ? estimateGeminiCost(effectiveModel, normalized.pricingUsage, pricingContext.rates) : void 0;
7301
7621
  const rawParts = candidate?.content?.parts;
7302
7622
  const providerMetadata = rawParts ? { geminiParts: rawParts } : void 0;
7303
7623
  return {
@@ -7388,7 +7708,7 @@ var GeminiProvider = class {
7388
7708
  pricingContext,
7389
7709
  hasDefinitiveStandardResponseTier,
7390
7710
  hasInvalidResponseTier
7391
- ) ? estimateGeminiCost(effectiveModel, normalizedUsage.pricingUsage) : void 0;
7711
+ ) ? estimateGeminiCost(effectiveModel, normalizedUsage.pricingUsage, pricingContext.rates) : void 0;
7392
7712
  yield {
7393
7713
  type: "done",
7394
7714
  usage: normalizedUsage?.usage,
@@ -9301,6 +9621,95 @@ function sessionHistoryForAsk(history, normalizedInput) {
9301
9621
  return snapshot;
9302
9622
  }
9303
9623
 
9624
+ // src/compaction.ts
9625
+ import { createHash } from "crypto";
9626
+ var SUMMARY_SYSTEM_PROMPT = "Summarize the following conversation concisely, preserving key facts, decisions, and context needed for continuing the conversation.";
9627
+ var SUMMARY_MAX_TOKENS = 1024;
9628
+ function buildSummaryPrompt(messages, previousSummary) {
9629
+ return [
9630
+ ...previousSummary ? [`Previous conversation summary: ${previousSummary}`] : [],
9631
+ ...messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`)
9632
+ ].join("\n");
9633
+ }
9634
+ function requestSummary(provider, options) {
9635
+ return provider.chat(
9636
+ [
9637
+ { role: "system", content: SUMMARY_SYSTEM_PROMPT },
9638
+ { role: "user", content: options.prompt }
9639
+ ],
9640
+ {
9641
+ model: options.model,
9642
+ maxTokens: SUMMARY_MAX_TOKENS,
9643
+ ...options.signal ? { signal: options.signal } : {}
9644
+ }
9645
+ );
9646
+ }
9647
+ function summaryContextMessage(summary) {
9648
+ return {
9649
+ role: "system",
9650
+ origin: "runtime",
9651
+ content: `Summary of earlier conversation:
9652
+ ${summary}`
9653
+ };
9654
+ }
9655
+ function withoutAnthropicThinking(message) {
9656
+ const metadata = message.providerMetadata;
9657
+ if (!metadata || !Object.hasOwn(metadata, "anthropicThinkingBlocks")) return message;
9658
+ const retainedMetadata = { ...metadata };
9659
+ delete retainedMetadata.anthropicThinkingBlocks;
9660
+ return {
9661
+ ...message,
9662
+ ...Object.keys(retainedMetadata).length > 0 ? { providerMetadata: retainedMetadata } : { providerMetadata: void 0 }
9663
+ };
9664
+ }
9665
+ function anthropicThinkingBlockCount(message) {
9666
+ const blocks = message.providerMetadata?.anthropicThinkingBlocks;
9667
+ if (!Array.isArray(blocks)) return 0;
9668
+ return blocks.filter(
9669
+ (block) => block !== null && typeof block === "object" && (block.type === "thinking" || block.type === "redacted_thinking")
9670
+ ).length;
9671
+ }
9672
+ function askSummaryMetaKey(agentName) {
9673
+ return `askSummary:${agentName}`;
9674
+ }
9675
+ function isCount(value) {
9676
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
9677
+ }
9678
+ function parseAskSummaryRecord(value) {
9679
+ if (!value || typeof value !== "object") return void 0;
9680
+ const record = value;
9681
+ if (typeof record.summary !== "string" || !isCount(record.coveredCount) || typeof record.prefixHash !== "string" || typeof record.summaryModelUri !== "string" || !isCount(record.invalidatedThinkingThrough) || record.sourceSummary !== void 0 && typeof record.sourceSummary !== "string") {
9682
+ return void 0;
9683
+ }
9684
+ return {
9685
+ summary: record.summary,
9686
+ coveredCount: record.coveredCount,
9687
+ prefixHash: record.prefixHash,
9688
+ ...record.sourceSummary !== void 0 ? { sourceSummary: record.sourceSummary } : {},
9689
+ summaryModelUri: record.summaryModelUri,
9690
+ invalidatedThinkingThrough: record.invalidatedThinkingThrough
9691
+ };
9692
+ }
9693
+ function isPlainObject(value) {
9694
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
9695
+ const proto = Object.getPrototypeOf(value);
9696
+ return proto === Object.prototype || proto === null;
9697
+ }
9698
+ function summaryPrefixHash(history, coveredCount) {
9699
+ let canonical;
9700
+ try {
9701
+ canonical = JSON.stringify(
9702
+ history.slice(0, coveredCount),
9703
+ (_key, value) => isPlainObject(value) ? Object.fromEntries(
9704
+ Object.keys(value).sort().map((key) => [key, value[key]])
9705
+ ) : value
9706
+ );
9707
+ } catch {
9708
+ return void 0;
9709
+ }
9710
+ return createHash("sha256").update(canonical).digest("hex");
9711
+ }
9712
+
9304
9713
  // src/partial-json.ts
9305
9714
  var MAX_DEPTH = 256;
9306
9715
  function parsePartialJson(input) {
@@ -10937,9 +11346,9 @@ var LEGACY_REDACTION_RULES = {
10937
11346
  // which is user content or PII (field paths are schema-shape, preserved like
10938
11347
  // `string_delta.path`). Passthrough, as an explicit reviewed decision.
10939
11348
  schema_diagnostic: passthrough,
10940
- // `provider_diagnostic` carries only provider capability metadata — a provider
10941
- // name, a model id, and two effort level strings. No application data or user
10942
- // content ever reaches it. Passthrough, as an explicit reviewed decision.
11349
+ // `provider_diagnostic` carries only provider/model metadata, effort levels,
11350
+ // and bounded dropped-thinking reason counts. Raw transformation paths and
11351
+ // signed thinking never enter this event. Passthrough is reviewed here.
10943
11352
  provider_diagnostic: passthrough,
10944
11353
  log: (e) => {
10945
11354
  if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
@@ -12944,7 +13353,14 @@ var WorkflowContext = class _WorkflowContext {
12944
13353
  * suppresses late branch events after bounded finalization. */
12945
13354
  workflowLifecycleState;
12946
13355
  signal;
12947
- summaryCache;
13356
+ /** Durable summary of messages removed by Session.history.maxMessages. */
13357
+ sessionSummary;
13358
+ /** Per-agent `maxContext` summaries, keyed by agent name. `null` records a
13359
+ * store lookup that found nothing. Mirrored to session metadata
13360
+ * (`askSummary:<agent>`) when this execution belongs to a session, so a
13361
+ * later execution can reuse the same exact-prefix boundary. Never the
13362
+ * durable `sessionSummary`. */
13363
+ askSummaries = /* @__PURE__ */ new Map();
12948
13364
  /** Consecutive provider calls with promptCache on that wrote to the cache
12949
13365
  * without reading from it (prefix changes per call), and consecutive calls
12950
13366
  * with no cache activity at all (prefix below the model's minimum, which
@@ -13120,7 +13536,7 @@ var WorkflowContext = class _WorkflowContext {
13120
13536
  endEmitted: false
13121
13537
  };
13122
13538
  if (init.metadata?.summaryCache) {
13123
- this.summaryCache = init.metadata.summaryCache;
13539
+ this.sessionSummary = init.metadata.summaryCache;
13124
13540
  }
13125
13541
  }
13126
13542
  /**
@@ -13745,6 +14161,7 @@ var WorkflowContext = class _WorkflowContext {
13745
14161
  if (systemPrompt) {
13746
14162
  messages.push({ role: "system", content: systemPrompt });
13747
14163
  }
14164
+ const cachedSummaryMessage = this.sessionSummary ? summaryContextMessage(this.sessionSummary) : void 0;
13748
14165
  const maxContext = agent2._config.maxContext;
13749
14166
  if (maxContext && sessionHistory.length > 0) {
13750
14167
  const reserveTokens = this.config.contextManagement?.reserveTokens ?? 2e3;
@@ -13769,22 +14186,44 @@ var WorkflowContext = class _WorkflowContext {
13769
14186
  }
13770
14187
  });
13771
14188
  }
13772
- if (historyEstimate.tokens > availableForHistory) {
13773
- const summarizedMessages = await this.summarizeHistory(
14189
+ const summaryTokens = cachedSummaryMessage ? estimateMessagesTokens([cachedSummaryMessage]).tokens : 0;
14190
+ if (historyEstimate.tokens + summaryTokens > availableForHistory) {
14191
+ const projection = await this.summarizeHistory(
14192
+ agent2._name,
14193
+ modelUri,
13774
14194
  provider,
13775
14195
  model,
13776
14196
  sessionHistory,
13777
14197
  availableForHistory
13778
14198
  );
13779
- for (const msg of summarizedMessages) {
14199
+ if (this.budgetContext?.exceeded && this.budgetContext.policy !== "warn") {
14200
+ const { limit, totalCost: spent, policy } = this.budgetContext;
14201
+ throw new BudgetExceededError(limit, spent, policy);
14202
+ }
14203
+ if (projection.removedThinkingBlocks > 0) {
14204
+ this.emitEvent({
14205
+ type: "provider_diagnostic",
14206
+ agent: agent2._name,
14207
+ data: {
14208
+ kind: "reasoning_context_reset",
14209
+ ...provider.name ? { provider: provider.name } : {},
14210
+ model: typeof providerOptions?.model === "string" ? providerOptions.model : model,
14211
+ droppedBlocks: projection.removedThinkingBlocks,
14212
+ reasons: { client_prefix_rewrite: projection.removedThinkingBlocks }
14213
+ }
14214
+ });
14215
+ }
14216
+ for (const msg of projection.messages) {
13780
14217
  messages.push(msg);
13781
14218
  }
13782
14219
  } else {
14220
+ if (cachedSummaryMessage) messages.push(cachedSummaryMessage);
13783
14221
  for (const msg of sessionHistory) {
13784
14222
  messages.push(msg);
13785
14223
  }
13786
14224
  }
13787
14225
  } else {
14226
+ if (cachedSummaryMessage) messages.push(cachedSummaryMessage);
13788
14227
  for (const msg of sessionHistory) {
13789
14228
  messages.push(msg);
13790
14229
  }
@@ -13885,6 +14324,7 @@ ${appendedSchemaText}`;
13885
14324
  }
13886
14325
  const startTime = Date.now();
13887
14326
  const askClock = currentAskClock();
14327
+ const pausedAtStartMs = askClock ? pausedAskClockMs(askClock, startTime) : 0;
13888
14328
  const timingTotals = { turns: 0, queuedMs: 0, retryMs: 0, wireMs: 0 };
13889
14329
  const currentMessages = [...messages];
13890
14330
  let turns = 0;
@@ -13902,7 +14342,7 @@ ${appendedSchemaText}`;
13902
14342
  this.currentSignal?.throwIfAborted();
13903
14343
  const now = Date.now();
13904
14344
  const elapsedMs = now - startTime;
13905
- const pausedMs = askClock ? pausedAskClockMs(askClock, now) : 0;
14345
+ const pausedMs = askClock ? Math.max(0, pausedAskClockMs(askClock, now) - pausedAtStartMs) : 0;
13906
14346
  const chargedMs = Math.max(0, elapsedMs - Math.min(pausedMs, Math.max(0, elapsedMs)));
13907
14347
  if (chargedMs > timeoutMs) {
13908
14348
  throw new TimeoutError(
@@ -14157,6 +14597,7 @@ ${appendedSchemaText}`;
14157
14597
  usage: chunk.usage,
14158
14598
  cost: chunk.cost,
14159
14599
  providerMetadata: chunk.providerMetadata,
14600
+ diagnostics: chunk.diagnostics,
14160
14601
  timing: chunk.timing
14161
14602
  };
14162
14603
  }
@@ -14274,6 +14715,34 @@ ${appendedSchemaText}`;
14274
14715
  timingTotals.retryMs += response.timing.retryMs;
14275
14716
  timingTotals.wireMs += response.timing.wireMs;
14276
14717
  }
14718
+ const reasoningReset = response.diagnostics?.reasoningContextReset;
14719
+ if (reasoningReset && Number.isSafeInteger(reasoningReset.droppedBlocks) && reasoningReset.droppedBlocks > 0) {
14720
+ const reasons = {};
14721
+ for (const reason of [
14722
+ "prefix_binding_mismatch",
14723
+ "model_binding_mismatch",
14724
+ "organization_binding_mismatch",
14725
+ "end_user_binding_mismatch",
14726
+ "client_prefix_rewrite",
14727
+ "other"
14728
+ ]) {
14729
+ const count = reasoningReset.reasons?.[reason];
14730
+ if (typeof count === "number" && Number.isSafeInteger(count) && count > 0) {
14731
+ reasons[reason] = count;
14732
+ }
14733
+ }
14734
+ this.emitEvent({
14735
+ type: "provider_diagnostic",
14736
+ agent: agent2._name,
14737
+ data: {
14738
+ kind: "reasoning_context_reset",
14739
+ ...provider.name ? { provider: provider.name } : {},
14740
+ model: typeof providerOptions?.model === "string" ? providerOptions.model : model,
14741
+ droppedBlocks: reasoningReset.droppedBlocks,
14742
+ reasons
14743
+ }
14744
+ });
14745
+ }
14277
14746
  this.emitEvent({
14278
14747
  type: "agent_call_end",
14279
14748
  agent: agent2._name,
@@ -15016,31 +15485,65 @@ ${appendedSchemaText}`;
15016
15485
  }
15017
15486
  return defs;
15018
15487
  }
15488
+ /** Session id for session-scoped ask-summary persistence, when this
15489
+ * execution belongs to a session and has a store to persist into. */
15490
+ askSummarySessionId() {
15491
+ const sessionId = this.metadata?.sessionId;
15492
+ if (this.metadata?.sessionPersist === false) return void 0;
15493
+ return this.stateStore && typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
15494
+ }
15495
+ /** This agent's ask summary: the in-memory copy first, else the session's
15496
+ * persisted record (read once per execution). */
15497
+ async loadAskSummary(agentName) {
15498
+ if (this.askSummaries.has(agentName)) return this.askSummaries.get(agentName) ?? void 0;
15499
+ const sessionId = this.askSummarySessionId();
15500
+ const stored = sessionId ? parseAskSummaryRecord(
15501
+ await this.stateStore.getSessionMeta(sessionId, askSummaryMetaKey(agentName))
15502
+ ) : void 0;
15503
+ this.askSummaries.set(agentName, stored ?? null);
15504
+ return stored;
15505
+ }
15506
+ async saveAskSummary(agentName, record) {
15507
+ this.askSummaries.set(agentName, record);
15508
+ const sessionId = this.askSummarySessionId();
15509
+ if (!sessionId) return;
15510
+ const key = askSummaryMetaKey(agentName);
15511
+ try {
15512
+ await this.stateStore.saveSessionMeta(sessionId, key, record);
15513
+ } catch (error) {
15514
+ this.emitEvent({
15515
+ type: "log",
15516
+ agent: agentName,
15517
+ data: {
15518
+ warning: `Could not persist ask summary '${key}' for session '${sessionId}'; later executions will regenerate it. ` + (error instanceof Error ? error.message : String(error))
15519
+ }
15520
+ });
15521
+ }
15522
+ }
15019
15523
  /**
15020
- * Summarize old messages to fit within context window.
15021
- * Keeps recent messages intact, summarizes older ones.
15524
+ * Project `history` into `availableTokens` for one agent's request: a summary
15525
+ * of an exact covered prefix followed by every later message. Reuses the
15526
+ * agent's cached boundary (in memory or persisted for the session) while the
15527
+ * covered prefix, durable session summary, and summary model are unchanged
15528
+ * and the whole tail fits; otherwise generates a new summary. Also reports how
15529
+ * many Anthropic thinking blocks the projection removed from carried turns.
15022
15530
  */
15023
- async summarizeHistory(provider, model, history, availableTokens) {
15024
- if (this.summaryCache) {
15025
- const summaryMsg2 = {
15026
- role: "system",
15027
- origin: "runtime",
15028
- content: `Summary of earlier conversation:
15029
- ${this.summaryCache}`
15030
- };
15031
- const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg2.content)) + 4;
15032
- const remaining = availableTokens - summaryTokens;
15033
- let recentTokens2 = 0;
15034
- let splitIdx2 = history.length;
15035
- for (let i = history.length - 1; i >= 0; i--) {
15036
- const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
15037
- if (recentTokens2 + msgTokens > remaining) break;
15038
- recentTokens2 += msgTokens;
15039
- splitIdx2 = i;
15040
- }
15041
- splitIdx2 = nextUserTurn(history, splitIdx2) ?? history.length;
15042
- if (splitIdx2 < history.length) {
15043
- return [summaryMsg2, ...history.slice(splitIdx2)];
15531
+ async summarizeHistory(agentName, modelUri, provider, model, history, availableTokens) {
15532
+ const summaryModelUri = this.config.contextManagement?.summaryModel ?? modelUri;
15533
+ const cache = await this.loadAskSummary(agentName);
15534
+ if (cache && cache.sourceSummary === this.sessionSummary && cache.summaryModelUri === summaryModelUri && cache.coveredCount <= history.length && cache.prefixHash === summaryPrefixHash(history, cache.coveredCount)) {
15535
+ const summaryMsg = summaryContextMessage(cache.summary);
15536
+ const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg.content)) + 4;
15537
+ const tail2 = history.slice(cache.coveredCount);
15538
+ const tailTokens = estimateMessagesTokens(tail2).tokens;
15539
+ if (summaryTokens + tailTokens <= availableTokens && (tail2.length === 0 || tail2[0].role === "user")) {
15540
+ let removedThinkingBlocks = 0;
15541
+ const projected = tail2.map((message, index) => {
15542
+ if (cache.coveredCount + index >= cache.invalidatedThinkingThrough) return message;
15543
+ removedThinkingBlocks += anthropicThinkingBlockCount(message);
15544
+ return withoutAnthropicThinking(message);
15545
+ });
15546
+ return { messages: [summaryMsg, ...projected], removedThinkingBlocks };
15044
15547
  }
15045
15548
  }
15046
15549
  let recentTokens = 0;
@@ -15065,42 +15568,103 @@ ${this.summaryCache}`
15065
15568
  } else {
15066
15569
  splitIdx = history.length;
15067
15570
  }
15068
- if (splitIdx === 0) return history;
15571
+ if (splitIdx === 0 && this.sessionSummary) splitIdx = history.length;
15572
+ if (splitIdx === 0) return { messages: history, removedThinkingBlocks: 0 };
15069
15573
  const oldMessages = history.slice(0, splitIdx);
15070
- const summaryModelUri = this.config.contextManagement?.summaryModel;
15574
+ const configuredSummaryModelUri = this.config.contextManagement?.summaryModel;
15071
15575
  let summaryProvider;
15072
15576
  let summaryModel;
15073
- if (summaryModelUri) {
15074
- const resolved = this.resolveProviderUri(summaryModelUri);
15577
+ if (configuredSummaryModelUri) {
15578
+ const resolved = this.resolveProviderUri(configuredSummaryModelUri);
15075
15579
  summaryProvider = resolved.provider;
15076
15580
  summaryModel = resolved.model;
15077
15581
  } else {
15078
15582
  summaryProvider = provider;
15079
15583
  summaryModel = model;
15080
15584
  }
15081
- const oldContent = oldMessages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n");
15082
- const summaryResponse = await summaryProvider.chat(
15083
- [
15084
- {
15085
- role: "system",
15086
- content: "Summarize the following conversation concisely, preserving key facts, decisions, and context needed for continuing the conversation."
15087
- },
15088
- { role: "user", content: oldContent }
15585
+ const summaryPrompt = buildSummaryPrompt(oldMessages, this.sessionSummary);
15586
+ const summaryStart = Date.now();
15587
+ this.emitEvent({
15588
+ type: "agent_call_start",
15589
+ agent: agentName,
15590
+ model: summaryModelUri,
15591
+ turn: 0,
15592
+ data: {
15593
+ purpose: "summary",
15594
+ prompt: summaryPrompt,
15595
+ system: SUMMARY_SYSTEM_PROMPT,
15596
+ params: { maxTokens: SUMMARY_MAX_TOKENS },
15597
+ turn: 0
15598
+ }
15599
+ });
15600
+ let summaryResponse;
15601
+ try {
15602
+ this.currentSignal?.throwIfAborted();
15603
+ summaryResponse = await requestSummary(summaryProvider, {
15604
+ model: summaryModel,
15605
+ prompt: summaryPrompt,
15606
+ signal: this.currentSignal
15607
+ });
15608
+ } catch (error) {
15609
+ const providerError = error instanceof ProviderError ? error : void 0;
15610
+ this.emitEvent({
15611
+ type: "agent_call_end",
15612
+ agent: agentName,
15613
+ model: summaryModelUri,
15614
+ duration: Date.now() - summaryStart,
15615
+ ...providerError?.timing ? { timing: providerError.timing } : {},
15616
+ data: {
15617
+ purpose: "summary",
15618
+ response: "",
15619
+ turn: 0,
15620
+ // Provider errors may echo the summarized history, so the event
15621
+ // uses a fixed projection even when trace redaction is disabled.
15622
+ error: "Context-management summary call failed",
15623
+ ...providerError ? { status: providerError.status, retryable: providerError.retryable } : {}
15624
+ }
15625
+ });
15626
+ throw error;
15627
+ }
15628
+ if (isUsableCost2(summaryResponse.cost)) this._accumulateBudgetCost(summaryResponse.cost);
15629
+ this.emitEvent({
15630
+ type: "agent_call_end",
15631
+ agent: agentName,
15632
+ model: summaryModelUri,
15633
+ cost: summaryResponse.cost,
15634
+ ...!isUsableCost2(summaryResponse.cost) ? { unpriced: true } : {},
15635
+ tokens: summaryResponse.usage ? {
15636
+ input: summaryResponse.usage.prompt_tokens,
15637
+ output: summaryResponse.usage.completion_tokens,
15638
+ reasoning: summaryResponse.usage.reasoning_tokens,
15639
+ cached: summaryResponse.usage.cached_tokens,
15640
+ cacheWrite: summaryResponse.usage.cache_write_tokens
15641
+ } : void 0,
15642
+ duration: Date.now() - summaryStart,
15643
+ ...summaryResponse.timing ? { timing: summaryResponse.timing } : {},
15644
+ data: { purpose: "summary", response: summaryResponse.content, turn: 0 }
15645
+ });
15646
+ const prefixHash = summaryPrefixHash(history, splitIdx);
15647
+ if (prefixHash) {
15648
+ await this.saveAskSummary(agentName, {
15649
+ summary: summaryResponse.content,
15650
+ coveredCount: splitIdx,
15651
+ prefixHash,
15652
+ ...this.sessionSummary !== void 0 ? { sourceSummary: this.sessionSummary } : {},
15653
+ summaryModelUri,
15654
+ invalidatedThinkingThrough: history.length
15655
+ });
15656
+ }
15657
+ const tail = history.slice(splitIdx);
15658
+ return {
15659
+ messages: [
15660
+ summaryContextMessage(summaryResponse.content),
15661
+ ...tail.map(withoutAnthropicThinking)
15089
15662
  ],
15090
- { model: summaryModel, maxTokens: 1024, signal: this.currentSignal }
15091
- );
15092
- this.summaryCache = summaryResponse.content;
15093
- const sessionId = this.metadata?.sessionId;
15094
- if (sessionId && this.stateStore) {
15095
- await this.stateStore.saveSessionMeta(sessionId, "summaryCache", this.summaryCache);
15096
- }
15097
- const summaryMsg = {
15098
- role: "system",
15099
- origin: "runtime",
15100
- content: `Summary of earlier conversation:
15101
- ${summaryResponse.content}`
15663
+ removedThinkingBlocks: tail.reduce(
15664
+ (count, message) => count + anthropicThinkingBlockCount(message),
15665
+ 0
15666
+ )
15102
15667
  };
15103
- return [summaryMsg, ...history.slice(splitIdx)];
15104
15668
  }
15105
15669
  // ── ctx.checkpoint() ────────────────────────────────────────────────
15106
15670
  /**
@@ -17509,17 +18073,17 @@ var Session = class _Session {
17509
18073
  throw new Error("SessionOptions.history.summaryModel is required when summarize is true");
17510
18074
  }
17511
18075
  const messagesToDrop = history.slice(0, history.length - maxMessages);
17512
- const toSummarize = cachedSummary ? [
17513
- { role: "system", content: `Previous conversation summary: ${cachedSummary}` },
17514
- ...messagesToDrop
17515
- ] : messagesToDrop;
17516
- const summary = await this.runtime.summarizeMessages(toSummarize, summaryModel);
18076
+ const summary = await this.runtime.summarizeMessages(
18077
+ messagesToDrop,
18078
+ summaryModel,
18079
+ cachedSummary ? { previousSummary: cachedSummary } : void 0
18080
+ );
17517
18081
  await this.store.saveSessionMeta(this.sessionId, "summaryCache", summary);
17518
18082
  cachedSummary = summary;
17519
18083
  }
17520
18084
  const trimmed = history.slice(-maxMessages);
17521
18085
  history.length = 0;
17522
- history.push(...trimmed);
18086
+ history.push(...trimmed.map(withoutAnthropicThinking));
17523
18087
  }
17524
18088
  const userMessage = { role: "user", content: preparedInput.content };
17525
18089
  history.push(userMessage);
@@ -17529,7 +18093,10 @@ var Session = class _Session {
17529
18093
  const metadata = {
17530
18094
  sessionId: this.sessionId,
17531
18095
  sessionHistory: history,
17532
- ...cachedSummary ? { summaryCache: cachedSummary } : {}
18096
+ ...cachedSummary ? { summaryCache: cachedSummary } : {},
18097
+ // An opted-out session must leave nothing at rest, including the
18098
+ // agent-scoped ask-summary records the runtime would otherwise cache.
18099
+ ...this.options.persist === false ? { sessionPersist: false } : {}
17533
18100
  };
17534
18101
  return { history, metadata };
17535
18102
  }
@@ -20611,22 +21178,17 @@ var AxlRuntime = class extends EventEmitter2 {
20611
21178
  /**
20612
21179
  * Summarize a list of chat messages into a concise summary string.
20613
21180
  * Used by Session to summarize dropped messages when history.summarize is enabled.
21181
+ * `previousSummary` is folded in so a rolling summary never loses what it
21182
+ * alone still carries. Shares its prompt and provider call with the
21183
+ * `AgentConfig.maxContext` summarizer (see `compaction.ts`). It runs outside
21184
+ * any workflow execution, so it emits no ask-scoped events.
20614
21185
  */
20615
- async summarizeMessages(messages, modelUri) {
21186
+ async summarizeMessages(messages, modelUri, options) {
20616
21187
  const { provider, model } = this.resolveProvider(modelUri);
20617
- const response = await provider.chat(
20618
- [
20619
- {
20620
- role: "system",
20621
- content: "Summarize the following conversation concisely, preserving key facts, decisions, and context needed for continuing the conversation."
20622
- },
20623
- {
20624
- role: "user",
20625
- content: messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n")
20626
- }
20627
- ],
20628
- { model, maxTokens: 1024 }
20629
- );
21188
+ const response = await requestSummary(provider, {
21189
+ model,
21190
+ prompt: buildSummaryPrompt(messages, options?.previousSummary)
21191
+ });
20630
21192
  return response.content;
20631
21193
  }
20632
21194
  /** Get the state store (for testing and advanced use cases). */
@@ -22056,6 +22618,7 @@ export {
22056
22618
  TranscriptionProviderRegistry,
22057
22619
  UnsupportedEventSchemaVersionError,
22058
22620
  UnsupportedModelInputError,
22621
+ UnsupportedModelOptionError,
22059
22622
  UnsupportedTranscriptionInputError,
22060
22623
  VLLM_PROFILE,
22061
22624
  ValidationError,