@axlsdk/axl 0.25.0 → 0.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.2" : "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,47 @@ 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 isFinalThinkingAdaptive(capability, body) {
4917
+ const thinking = body.thinking;
4918
+ if (thinking === void 0) {
4919
+ return capability.thinking === "adaptive-default-on" || capability.thinking === "adaptive-always-on";
4920
+ }
4921
+ return isRecord(thinking) && thinking.type === "adaptive";
4922
+ }
4923
+ var NATIVE_CLAUDE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
4924
+ function isNativeClaudeEffort(value) {
4925
+ return NATIVE_CLAUDE_EFFORTS.includes(value);
4926
+ }
4927
+ function hasReplayedThinking(body) {
4928
+ return Array.isArray(body.messages) && body.messages.some(
4929
+ (message) => isRecord(message) && Array.isArray(message.content) && message.content.some(
4930
+ (block) => isRecord(block) && (block.type === "thinking" || block.type === "redacted_thinking")
4931
+ )
4932
+ );
4933
+ }
4934
+ function hasFinalFileImage(body) {
4935
+ return Array.isArray(body.messages) && body.messages.some(
4936
+ (message) => isRecord(message) && Array.isArray(message.content) && message.content.some(
4937
+ (block) => isRecord(block) && block.type === "image" && isRecord(block.source) && block.source.type === "file"
4938
+ )
4939
+ );
4940
+ }
4941
+ function normalizeThinkingDrops(value) {
4942
+ if (!Array.isArray(value)) return void 0;
4943
+ const reasons = {};
4944
+ let droppedBlocks = 0;
4945
+ for (const entry of value) {
4946
+ if (!isRecord(entry) || entry.type !== "thinking_dropped") continue;
4947
+ 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";
4948
+ reasons[reason] = (reasons[reason] ?? 0) + 1;
4949
+ droppedBlocks++;
4950
+ }
4951
+ return droppedBlocks > 0 ? { droppedBlocks, reasons } : void 0;
4952
+ }
4716
4953
  function hasAnthropicProviderFileImage(messages) {
4717
4954
  return messages.some(
4718
4955
  (message) => Array.isArray(message.content) && message.content.some(
@@ -4721,11 +4958,51 @@ function hasAnthropicProviderFileImage(messages) {
4721
4958
  );
4722
4959
  }
4723
4960
  var CLAUDE_CAPABILITIES = {
4961
+ // Thinking off is `between_tools` (progress updates between tool calls);
4962
+ // `disabled` is a provider 400. Forced tool choice is a 400.
4963
+ "claude-sonnet-5-5": {
4964
+ thinking: "adaptive-default-on",
4965
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
4966
+ manualBudget: false,
4967
+ stripTemperature: true,
4968
+ disableAt: "high",
4969
+ offShape: "between_tools",
4970
+ forcedToolChoice: "rejected",
4971
+ defaultOnlySampling: true,
4972
+ strictThinking: true,
4973
+ thinkingBinding: true
4974
+ },
4975
+ // Thinking off is `disabled`, only at effort high or below. Forced tool
4976
+ // choice is accepted (the provider skips thinking for that turn).
4977
+ "claude-haiku-5-5": {
4978
+ thinking: "adaptive-default-on",
4979
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
4980
+ manualBudget: false,
4981
+ stripTemperature: true,
4982
+ disableAt: "high",
4983
+ defaultOnlySampling: true,
4984
+ strictThinking: true,
4985
+ thinkingBinding: true
4986
+ },
4987
+ "claude-opus-5-5": {
4988
+ thinking: "adaptive-always-on",
4989
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
4990
+ manualBudget: false,
4991
+ stripTemperature: true,
4992
+ forcedToolChoice: "rejected",
4993
+ defaultOnlySampling: true,
4994
+ strictThinking: true,
4995
+ thinkingBinding: true
4996
+ },
4724
4997
  "claude-fable-5-1": {
4725
4998
  thinking: "adaptive-always-on",
4726
4999
  effortLevels: ["low", "medium", "high", "xhigh", "max"],
4727
5000
  manualBudget: false,
4728
- stripTemperature: true
5001
+ stripTemperature: true,
5002
+ forcedToolChoice: "rejected",
5003
+ defaultOnlySampling: true,
5004
+ strictThinking: true,
5005
+ thinkingBinding: true
4729
5006
  },
4730
5007
  "claude-fable-5": {
4731
5008
  thinking: "adaptive-always-on",
@@ -4819,6 +5096,16 @@ function resolveClaudeCapability(model) {
4819
5096
  }
4820
5097
  var DEFAULT_CACHE_READ_MULTIPLIER = 0.1;
4821
5098
  var ANTHROPIC_RATES = {
5099
+ // Sonnet 5.5 cache reads are 0.05x ($0.10/MTok): the pricing table, its
5100
+ // footnote, the models overview, and the prompt-caching guide agree
5101
+ // (re-checked 2026-10-07).
5102
+ "claude-sonnet-5-5": { input: 2e-6, output: 1e-5, cacheReadMultiplier: 0.05 },
5103
+ "claude-haiku-5-5": {
5104
+ input: 1e-7,
5105
+ output: 5e-7,
5106
+ longPrompt: { aboveTokens: 1e5, card: { input: 5e-7, output: 25e-7 } }
5107
+ },
5108
+ "claude-opus-5-5": { input: 4e-6, output: 2e-5, cacheReadMultiplier: 0.05 },
4822
5109
  "claude-fable-5-1": { input: 1e-5, output: 5e-5, cacheReadMultiplier: 0.025 },
4823
5110
  "claude-fable-5": { input: 1e-5, output: 5e-5 },
4824
5111
  // Limited availability (Glasswing). Priced from the public pricing table; no
@@ -4880,14 +5167,16 @@ function estimateAnthropicCost(model, usage) {
4880
5167
  return void 0;
4881
5168
  }
4882
5169
  const cacheWriteTotal = safeTokenSum(cacheWrite5m, cacheWrite1h);
4883
- if (cacheWriteTotal === void 0 || safeTokenSum(usage.inputTokens, cacheRead, cacheWriteTotal) === void 0) {
5170
+ const promptTokens = cacheWriteTotal === void 0 ? void 0 : safeTokenSum(usage.inputTokens, cacheRead, cacheWriteTotal);
5171
+ if (cacheWriteTotal === void 0 || promptTokens === void 0) {
4884
5172
  return void 0;
4885
5173
  }
4886
5174
  if (usage.aggregateCacheWriteTokens !== void 0 && (!isValidTokenCount(usage.aggregateCacheWriteTokens) || usage.aggregateCacheWriteTokens !== cacheWriteTotal)) {
4887
5175
  return void 0;
4888
5176
  }
4889
- const { input, output } = rate;
4890
- const cacheReadRate = input * (rate.cacheReadMultiplier ?? DEFAULT_CACHE_READ_MULTIPLIER);
5177
+ const card = rate.longPrompt && promptTokens > rate.longPrompt.aboveTokens ? rate.longPrompt.card : rate;
5178
+ const { input, output } = card;
5179
+ const cacheReadRate = input * (card.cacheReadMultiplier ?? DEFAULT_CACHE_READ_MULTIPLIER);
4891
5180
  return usage.inputTokens * input + cacheRead * cacheReadRate + cacheWrite5m * input * 1.25 + cacheWrite1h * input * 2 + usage.outputTokens * output;
4892
5181
  }
4893
5182
  var THINKING_BUDGETS = {
@@ -4949,7 +5238,7 @@ function resolveClaudeThinking(model, resolved) {
4949
5238
  };
4950
5239
  }
4951
5240
  return {
4952
- thinking: { type: "disabled" },
5241
+ thinking: { type: capability.offShape ?? "disabled" },
4953
5242
  outputConfig: activeEffort ? { effort: activeEffort } : void 0,
4954
5243
  stripTemperature: true
4955
5244
  };
@@ -5253,11 +5542,12 @@ var AnthropicProvider = class {
5253
5542
  // chat - non-streaming completion
5254
5543
  // ---------------------------------------------------------------------------
5255
5544
  async chat(messages, options) {
5545
+ const body = this.buildRequestBody(messages, options, false);
5256
5546
  const headers = this.buildHeaders(
5257
5547
  await this.resolveKey(),
5258
- hasAnthropicProviderFileImage(messages)
5548
+ hasAnthropicProviderFileImage(messages) || hasFinalFileImage(body),
5549
+ this.requiresBindingBeta(body)
5259
5550
  );
5260
- const body = this.buildRequestBody(messages, options, false);
5261
5551
  const pricingContext = pricingContextFromBody(body);
5262
5552
  const recorder = new CallTimingRecorder(options.requestLifecycle);
5263
5553
  const governor = this.governorFor(pricingContext.model ?? options.model);
@@ -5295,11 +5585,12 @@ var AnthropicProvider = class {
5295
5585
  // stream - SSE streaming completion
5296
5586
  // ---------------------------------------------------------------------------
5297
5587
  async *stream(messages, options) {
5588
+ const body = this.buildRequestBody(messages, options, true);
5298
5589
  const headers = this.buildHeaders(
5299
5590
  await this.resolveKey(),
5300
- hasAnthropicProviderFileImage(messages)
5591
+ hasAnthropicProviderFileImage(messages) || hasFinalFileImage(body),
5592
+ this.requiresBindingBeta(body)
5301
5593
  );
5302
- const body = this.buildRequestBody(messages, options, true);
5303
5594
  const pricingContext = pricingContextFromBody(body);
5304
5595
  const recorder = new CallTimingRecorder(options.requestLifecycle);
5305
5596
  const governor = this.governorFor(pricingContext.model ?? options.model);
@@ -5341,14 +5632,119 @@ var AnthropicProvider = class {
5341
5632
  // ---------------------------------------------------------------------------
5342
5633
  // Internal: request building
5343
5634
  // ---------------------------------------------------------------------------
5344
- buildHeaders(apiKey, requiresFilesBeta = false) {
5635
+ buildHeaders(apiKey, requiresFilesBeta = false, requiresBindingBeta = false) {
5636
+ const betas = [
5637
+ ...requiresFilesBeta ? [ANTHROPIC_FILES_BETA] : [],
5638
+ ...requiresBindingBeta ? [ANTHROPIC_THINKING_BINDING_BETA] : []
5639
+ ];
5345
5640
  return {
5346
5641
  "Content-Type": "application/json",
5347
5642
  "x-api-key": apiKey,
5348
5643
  "anthropic-version": ANTHROPIC_API_VERSION,
5349
- ...requiresFilesBeta ? { "anthropic-beta": ANTHROPIC_FILES_BETA } : {}
5644
+ ...betas.length > 0 ? { "anthropic-beta": betas.join(",") } : {}
5350
5645
  };
5351
5646
  }
5647
+ /**
5648
+ * Whether the final body needs the binding beta header. Runs after
5649
+ * `validateAndBindFinalBody`, which has already injected `block_binding`
5650
+ * wherever it applies, so the header follows the final body rather than
5651
+ * re-deriving the injection rule.
5652
+ */
5653
+ requiresBindingBeta(body) {
5654
+ const capability = resolveClaudeCapability(String(body.model));
5655
+ const thinking = body.thinking;
5656
+ return capability?.thinkingBinding === true && isRecord(thinking) && thinking.type === "adaptive" && thinking.block_binding !== void 0;
5657
+ }
5658
+ /**
5659
+ * Validate the final body (after `providerOptions`) against the effective
5660
+ * model's capability, then inject `block_binding` where it applies. Every
5661
+ * rule is gated on its own capability field and is a no-op when unset, so a
5662
+ * model only gets the guards its provider contract calls for.
5663
+ */
5664
+ validateAndBindFinalBody(body) {
5665
+ const model = typeof body.model === "string" ? body.model : "";
5666
+ const capability = resolveClaudeCapability(model);
5667
+ if (!capability) return;
5668
+ const reject = (option, remediation) => {
5669
+ throw new UnsupportedModelOptionError({ provider: this.name, model, option, remediation });
5670
+ };
5671
+ if (capability.forcedToolChoice === "rejected") {
5672
+ const toolChoice = body.tool_choice;
5673
+ if (isRecord(toolChoice) && (toolChoice.type === "any" || toolChoice.type === "tool")) {
5674
+ reject("forced tool choice", "Use toolChoice: 'auto' or 'none' on this model.");
5675
+ }
5676
+ }
5677
+ if (capability.strictThinking) {
5678
+ const offShape = capability.thinking === "adaptive-always-on" ? void 0 : capability.offShape ?? "disabled";
5679
+ const thinking = body.thinking;
5680
+ const outputConfig = body.output_config;
5681
+ const effort = isRecord(outputConfig) ? outputConfig.effort : void 0;
5682
+ if (thinking !== void 0 && offShape === void 0) {
5683
+ if (!isRecord(thinking) || thinking.type !== "adaptive" || thinking.budget_tokens !== void 0) {
5684
+ reject(
5685
+ "manual or disabled thinking",
5686
+ "Use thinking: { type: 'adaptive' } and output_config.effort."
5687
+ );
5688
+ }
5689
+ } else if (thinking !== void 0) {
5690
+ if (!isRecord(thinking) || thinking.type !== "adaptive" && thinking.type !== offShape) {
5691
+ reject(
5692
+ "thinking.type",
5693
+ `Use thinking: { type: 'adaptive' }, or { type: '${offShape}' } to turn thinking off.`
5694
+ );
5695
+ }
5696
+ const record = thinking;
5697
+ if (record.type === "adaptive" && record.budget_tokens !== void 0) {
5698
+ reject("thinking.budget_tokens", "Use output_config.effort instead of budget_tokens.");
5699
+ }
5700
+ if (record.type === offShape) {
5701
+ const extra = Object.keys(record).filter((key) => key !== "type");
5702
+ if (extra.length > 0) {
5703
+ reject(
5704
+ `thinking.${offShape}.${extra[0]}`,
5705
+ `Send thinking: { type: '${offShape}' } with no other fields.`
5706
+ );
5707
+ }
5708
+ }
5709
+ }
5710
+ if (outputConfig !== void 0 && (!isRecord(outputConfig) || effort !== void 0 && !isNativeClaudeEffort(effort))) {
5711
+ reject("output_config.effort", "Use low, medium, high, xhigh, or max effort.");
5712
+ }
5713
+ if (offShape !== void 0 && isRecord(thinking) && thinking.type === offShape && effort !== void 0 && capability.disableAt && !isEffortAtOrBelow(effort, capability.disableAt)) {
5714
+ reject(
5715
+ "output_config.effort",
5716
+ `Thinking can be turned off only at effort '${capability.disableAt}' or below; lower the effort or use adaptive thinking.`
5717
+ );
5718
+ }
5719
+ }
5720
+ if (capability.defaultOnlySampling) {
5721
+ if (body.temperature !== void 0 && body.temperature !== 1) {
5722
+ reject("temperature", "Omit temperature or use the default value 1.");
5723
+ }
5724
+ if (body.top_p !== void 0 && body.top_p !== 0.99) {
5725
+ reject("top_p", "Omit top_p or use the default value 0.99.");
5726
+ }
5727
+ if (body.temperature !== void 0 && body.top_p !== void 0) {
5728
+ reject("temperature and top_p", "Send at most one of temperature and top_p.");
5729
+ }
5730
+ if (body.top_k !== void 0) {
5731
+ reject("top_k", "Omit top_k on this model.");
5732
+ }
5733
+ }
5734
+ if (capability.thinkingBinding === true && isFinalThinkingAdaptive(capability, body)) {
5735
+ const thinking = isRecord(body.thinking) ? body.thinking : { type: "adaptive" };
5736
+ const binding = thinking.block_binding;
5737
+ if (binding !== void 0 && (!isRecord(binding) || binding.prefix_mismatch_behavior !== "error" && binding.prefix_mismatch_behavior !== "drop_block")) {
5738
+ reject("thinking.block_binding", "Use prefix_mismatch_behavior: 'error' or 'drop_block'.");
5739
+ }
5740
+ if (hasReplayedThinking(body)) {
5741
+ body.thinking = {
5742
+ ...thinking,
5743
+ block_binding: binding ?? { prefix_mismatch_behavior: "drop_block" }
5744
+ };
5745
+ }
5746
+ }
5747
+ }
5352
5748
  /** Extract a human-readable message from an API error response body. */
5353
5749
  extractErrorMessage(body, status) {
5354
5750
  try {
@@ -5425,6 +5821,7 @@ ${jsonInstruction}` : jsonInstruction;
5425
5821
  if (options.providerOptions) {
5426
5822
  Object.assign(body, options.providerOptions);
5427
5823
  }
5824
+ this.validateAndBindFinalBody(body);
5428
5825
  return body;
5429
5826
  }
5430
5827
  /**
@@ -5581,6 +5978,7 @@ ${jsonInstruction}` : jsonInstruction;
5581
5978
  }
5582
5979
  }
5583
5980
  const normalized = json.usage ? normalizeAnthropicUsage(json.usage) : void 0;
5981
+ const reasoningContextReset = normalizeThinkingDrops(json.input_transformations);
5584
5982
  const effectiveModel = typeof json.model === "string" ? json.model : pricingContext.model;
5585
5983
  if (hasFallbackBoundary) {
5586
5984
  throw new Error(
@@ -5595,7 +5993,8 @@ ${jsonInstruction}` : jsonInstruction;
5595
5993
  usage: normalized?.usage,
5596
5994
  cost,
5597
5995
  costProvenance: tableEstimate(cost),
5598
- providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
5996
+ providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary ? { anthropicThinkingBlocks: thinkingBlocks } : void 0,
5997
+ ...reasoningContextReset ? { diagnostics: { reasoningContextReset } } : {}
5599
5998
  };
5600
5999
  }
5601
6000
  // ---------------------------------------------------------------------------
@@ -5617,6 +6016,7 @@ ${jsonInstruction}` : jsonInstruction;
5617
6016
  let hasFallbackBoundary = false;
5618
6017
  let hasFallbackIterationSignal = false;
5619
6018
  let refused = false;
6019
+ let reasoningContextReset;
5620
6020
  const finalizeUsage = () => {
5621
6021
  const normalized = rawUsage ? normalizeAnthropicUsage(rawUsage) : void 0;
5622
6022
  usage = normalized?.usage;
@@ -5629,7 +6029,8 @@ ${jsonInstruction}` : jsonInstruction;
5629
6029
  usage,
5630
6030
  cost,
5631
6031
  costProvenance: tableEstimate(cost),
5632
- providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
6032
+ providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0,
6033
+ ...reasoningContextReset ? { diagnostics: { reasoningContextReset } } : {}
5633
6034
  };
5634
6035
  };
5635
6036
  try {
@@ -5722,6 +6123,7 @@ ${jsonInstruction}` : jsonInstruction;
5722
6123
  rawUsage = mergeAnthropicUsage(rawUsage, event.message.usage);
5723
6124
  }
5724
6125
  if (typeof event.message?.model === "string") effectiveModel = event.message.model;
6126
+ reasoningContextReset = normalizeThinkingDrops(event.message?.input_transformations);
5725
6127
  const messageFallback = hasFallbackIteration(event.message?.usage?.iterations);
5726
6128
  if (isAnthropicResponseModifier(
5727
6129
  "inference_geo",
@@ -5738,6 +6140,10 @@ ${jsonInstruction}` : jsonInstruction;
5738
6140
  break;
5739
6141
  }
5740
6142
  case "message_delta": {
6143
+ const finalTransformations = event.input_transformations ?? event.delta?.input_transformations;
6144
+ if (finalTransformations !== void 0) {
6145
+ reasoningContextReset = normalizeThinkingDrops(finalTransformations);
6146
+ }
5741
6147
  if (event.usage) {
5742
6148
  rawUsage = mergeAnthropicUsage(rawUsage, event.usage);
5743
6149
  finalizeUsage();
@@ -5966,7 +6372,31 @@ var GEMINI_PRICING = {
5966
6372
  },
5967
6373
  "gemini-3.8-flash": { input: 75e-8, cached: 75e-9, output: 375e-8 }
5968
6374
  };
6375
+ var GEMINI_FLASH_SUCCESSOR_PRICING = {
6376
+ ...GEMINI_PRICING,
6377
+ "gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 },
6378
+ "gemini-3.7-flash": {
6379
+ input: 15e-7,
6380
+ cached: 15e-8,
6381
+ output: 75e-7,
6382
+ audioInput: 15e-7
6383
+ },
6384
+ "gemini-3.8-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
6385
+ };
6386
+ var GEMINI_FLASH_SUCCESSOR_START_MS = Date.UTC(2027, 0, 1);
6387
+ function geminiRatesAtDispatch(at) {
6388
+ return at < GEMINI_FLASH_SUCCESSOR_START_MS ? GEMINI_PRICING : GEMINI_FLASH_SUCCESSOR_PRICING;
6389
+ }
5969
6390
  var CANONICAL_GEMINI_BASE_URL = GEMINI_DEFAULT_BASE_URL;
6391
+ function geminiPricingTimingObserver(context, recorder) {
6392
+ return {
6393
+ ...recorder.observer,
6394
+ onDispatch: (attempt, at) => {
6395
+ context.rates = geminiRatesAtDispatch(at);
6396
+ recorder.observer.onDispatch?.(attempt, at);
6397
+ }
6398
+ };
6399
+ }
5970
6400
  function isValidTokenCount2(value) {
5971
6401
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
5972
6402
  }
@@ -6047,8 +6477,8 @@ function geminiInteractionErrorStatus(code) {
6047
6477
  function isEligibleGeminiPricing(context, hasDefinitiveStandardResponseTier, hasInvalidResponseTier) {
6048
6478
  return context.eligibleRequest && isStandardGeminiRequestTier(context.serviceTier) && hasDefinitiveStandardResponseTier && !hasInvalidResponseTier;
6049
6479
  }
6050
- function estimateGeminiCost(model, usage) {
6051
- const pricing = GEMINI_PRICING[model];
6480
+ function estimateGeminiCost(model, usage, rates) {
6481
+ const pricing = rates && Object.hasOwn(rates, model) ? rates[model] : void 0;
6052
6482
  const cached = usage.cachedTokens ?? 0;
6053
6483
  if (!pricing || !isValidTokenCount2(usage.inputTokens) || !isValidTokenCount2(usage.outputTokens) || !isValidTokenCount2(cached) || cached > usage.inputTokens) {
6054
6484
  return void 0;
@@ -6376,7 +6806,7 @@ var GeminiProvider = class {
6376
6806
  {
6377
6807
  governor: this.governorFor(pricingContext.model),
6378
6808
  provider: this.name,
6379
- timing: recorder.observer,
6809
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6380
6810
  admission: options.dispatchAdmission
6381
6811
  }
6382
6812
  );
@@ -6419,7 +6849,7 @@ var GeminiProvider = class {
6419
6849
  {
6420
6850
  governor: this.governorFor(pricingContext.model),
6421
6851
  provider: this.name,
6422
- timing: recorder.observer,
6852
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6423
6853
  admission: options.dispatchAdmission
6424
6854
  }
6425
6855
  );
@@ -6457,7 +6887,7 @@ var GeminiProvider = class {
6457
6887
  {
6458
6888
  governor: this.governorFor(pricingContext.model),
6459
6889
  provider: this.name,
6460
- timing: recorder.observer,
6890
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6461
6891
  admission: options.dispatchAdmission
6462
6892
  }
6463
6893
  );
@@ -6493,7 +6923,7 @@ var GeminiProvider = class {
6493
6923
  {
6494
6924
  governor: this.governorFor(pricingContext.model),
6495
6925
  provider: this.name,
6496
- timing: recorder.observer,
6926
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6497
6927
  admission: options.dispatchAdmission
6498
6928
  }
6499
6929
  );
@@ -6712,7 +7142,7 @@ var GeminiProvider = class {
6712
7142
  )) {
6713
7143
  return void 0;
6714
7144
  }
6715
- return estimateGeminiCost(effectiveModel, normalized.pricingUsage);
7145
+ return estimateGeminiCost(effectiveModel, normalized.pricingUsage, pricingContext.rates);
6716
7146
  }
6717
7147
  /**
6718
7148
  * Whether every model-produced step is text. A non-text reply part (image,
@@ -7297,7 +7727,7 @@ var GeminiProvider = class {
7297
7727
  pricingContext,
7298
7728
  isDefinitiveStandardGeminiResponseTier(json.usageMetadata?.serviceTier),
7299
7729
  json.usageMetadata?.serviceTier !== void 0 && !isDefinitiveStandardGeminiResponseTier(json.usageMetadata.serviceTier)
7300
- ) ? estimateGeminiCost(effectiveModel, normalized.pricingUsage) : void 0;
7730
+ ) ? estimateGeminiCost(effectiveModel, normalized.pricingUsage, pricingContext.rates) : void 0;
7301
7731
  const rawParts = candidate?.content?.parts;
7302
7732
  const providerMetadata = rawParts ? { geminiParts: rawParts } : void 0;
7303
7733
  return {
@@ -7388,7 +7818,7 @@ var GeminiProvider = class {
7388
7818
  pricingContext,
7389
7819
  hasDefinitiveStandardResponseTier,
7390
7820
  hasInvalidResponseTier
7391
- ) ? estimateGeminiCost(effectiveModel, normalizedUsage.pricingUsage) : void 0;
7821
+ ) ? estimateGeminiCost(effectiveModel, normalizedUsage.pricingUsage, pricingContext.rates) : void 0;
7392
7822
  yield {
7393
7823
  type: "done",
7394
7824
  usage: normalizedUsage?.usage,
@@ -9301,6 +9731,95 @@ function sessionHistoryForAsk(history, normalizedInput) {
9301
9731
  return snapshot;
9302
9732
  }
9303
9733
 
9734
+ // src/compaction.ts
9735
+ import { createHash } from "crypto";
9736
+ var SUMMARY_SYSTEM_PROMPT = "Summarize the following conversation concisely, preserving key facts, decisions, and context needed for continuing the conversation.";
9737
+ var SUMMARY_MAX_TOKENS = 1024;
9738
+ function buildSummaryPrompt(messages, previousSummary) {
9739
+ return [
9740
+ ...previousSummary ? [`Previous conversation summary: ${previousSummary}`] : [],
9741
+ ...messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`)
9742
+ ].join("\n");
9743
+ }
9744
+ function requestSummary(provider, options) {
9745
+ return provider.chat(
9746
+ [
9747
+ { role: "system", content: SUMMARY_SYSTEM_PROMPT },
9748
+ { role: "user", content: options.prompt }
9749
+ ],
9750
+ {
9751
+ model: options.model,
9752
+ maxTokens: SUMMARY_MAX_TOKENS,
9753
+ ...options.signal ? { signal: options.signal } : {}
9754
+ }
9755
+ );
9756
+ }
9757
+ function summaryContextMessage(summary) {
9758
+ return {
9759
+ role: "system",
9760
+ origin: "runtime",
9761
+ content: `Summary of earlier conversation:
9762
+ ${summary}`
9763
+ };
9764
+ }
9765
+ function withoutAnthropicThinking(message) {
9766
+ const metadata = message.providerMetadata;
9767
+ if (!metadata || !Object.hasOwn(metadata, "anthropicThinkingBlocks")) return message;
9768
+ const retainedMetadata = { ...metadata };
9769
+ delete retainedMetadata.anthropicThinkingBlocks;
9770
+ return {
9771
+ ...message,
9772
+ ...Object.keys(retainedMetadata).length > 0 ? { providerMetadata: retainedMetadata } : { providerMetadata: void 0 }
9773
+ };
9774
+ }
9775
+ function anthropicThinkingBlockCount(message) {
9776
+ const blocks = message.providerMetadata?.anthropicThinkingBlocks;
9777
+ if (!Array.isArray(blocks)) return 0;
9778
+ return blocks.filter(
9779
+ (block) => block !== null && typeof block === "object" && (block.type === "thinking" || block.type === "redacted_thinking")
9780
+ ).length;
9781
+ }
9782
+ function askSummaryMetaKey(agentName) {
9783
+ return `askSummary:${agentName}`;
9784
+ }
9785
+ function isCount(value) {
9786
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
9787
+ }
9788
+ function parseAskSummaryRecord(value) {
9789
+ if (!value || typeof value !== "object") return void 0;
9790
+ const record = value;
9791
+ 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") {
9792
+ return void 0;
9793
+ }
9794
+ return {
9795
+ summary: record.summary,
9796
+ coveredCount: record.coveredCount,
9797
+ prefixHash: record.prefixHash,
9798
+ ...record.sourceSummary !== void 0 ? { sourceSummary: record.sourceSummary } : {},
9799
+ summaryModelUri: record.summaryModelUri,
9800
+ invalidatedThinkingThrough: record.invalidatedThinkingThrough
9801
+ };
9802
+ }
9803
+ function isPlainObject(value) {
9804
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
9805
+ const proto = Object.getPrototypeOf(value);
9806
+ return proto === Object.prototype || proto === null;
9807
+ }
9808
+ function summaryPrefixHash(history, coveredCount) {
9809
+ let canonical;
9810
+ try {
9811
+ canonical = JSON.stringify(
9812
+ history.slice(0, coveredCount),
9813
+ (_key, value) => isPlainObject(value) ? Object.fromEntries(
9814
+ Object.keys(value).sort().map((key) => [key, value[key]])
9815
+ ) : value
9816
+ );
9817
+ } catch {
9818
+ return void 0;
9819
+ }
9820
+ return createHash("sha256").update(canonical).digest("hex");
9821
+ }
9822
+
9304
9823
  // src/partial-json.ts
9305
9824
  var MAX_DEPTH = 256;
9306
9825
  function parsePartialJson(input) {
@@ -10937,9 +11456,9 @@ var LEGACY_REDACTION_RULES = {
10937
11456
  // which is user content or PII (field paths are schema-shape, preserved like
10938
11457
  // `string_delta.path`). Passthrough, as an explicit reviewed decision.
10939
11458
  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.
11459
+ // `provider_diagnostic` carries only provider/model metadata, effort levels,
11460
+ // and bounded dropped-thinking reason counts. Raw transformation paths and
11461
+ // signed thinking never enter this event. Passthrough is reviewed here.
10943
11462
  provider_diagnostic: passthrough,
10944
11463
  log: (e) => {
10945
11464
  if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
@@ -12944,7 +13463,14 @@ var WorkflowContext = class _WorkflowContext {
12944
13463
  * suppresses late branch events after bounded finalization. */
12945
13464
  workflowLifecycleState;
12946
13465
  signal;
12947
- summaryCache;
13466
+ /** Durable summary of messages removed by Session.history.maxMessages. */
13467
+ sessionSummary;
13468
+ /** Per-agent `maxContext` summaries, keyed by agent name. `null` records a
13469
+ * store lookup that found nothing. Mirrored to session metadata
13470
+ * (`askSummary:<agent>`) when this execution belongs to a session, so a
13471
+ * later execution can reuse the same exact-prefix boundary. Never the
13472
+ * durable `sessionSummary`. */
13473
+ askSummaries = /* @__PURE__ */ new Map();
12948
13474
  /** Consecutive provider calls with promptCache on that wrote to the cache
12949
13475
  * without reading from it (prefix changes per call), and consecutive calls
12950
13476
  * with no cache activity at all (prefix below the model's minimum, which
@@ -13120,7 +13646,7 @@ var WorkflowContext = class _WorkflowContext {
13120
13646
  endEmitted: false
13121
13647
  };
13122
13648
  if (init.metadata?.summaryCache) {
13123
- this.summaryCache = init.metadata.summaryCache;
13649
+ this.sessionSummary = init.metadata.summaryCache;
13124
13650
  }
13125
13651
  }
13126
13652
  /**
@@ -13745,6 +14271,7 @@ var WorkflowContext = class _WorkflowContext {
13745
14271
  if (systemPrompt) {
13746
14272
  messages.push({ role: "system", content: systemPrompt });
13747
14273
  }
14274
+ const cachedSummaryMessage = this.sessionSummary ? summaryContextMessage(this.sessionSummary) : void 0;
13748
14275
  const maxContext = agent2._config.maxContext;
13749
14276
  if (maxContext && sessionHistory.length > 0) {
13750
14277
  const reserveTokens = this.config.contextManagement?.reserveTokens ?? 2e3;
@@ -13769,22 +14296,44 @@ var WorkflowContext = class _WorkflowContext {
13769
14296
  }
13770
14297
  });
13771
14298
  }
13772
- if (historyEstimate.tokens > availableForHistory) {
13773
- const summarizedMessages = await this.summarizeHistory(
14299
+ const summaryTokens = cachedSummaryMessage ? estimateMessagesTokens([cachedSummaryMessage]).tokens : 0;
14300
+ if (historyEstimate.tokens + summaryTokens > availableForHistory) {
14301
+ const projection = await this.summarizeHistory(
14302
+ agent2._name,
14303
+ modelUri,
13774
14304
  provider,
13775
14305
  model,
13776
14306
  sessionHistory,
13777
14307
  availableForHistory
13778
14308
  );
13779
- for (const msg of summarizedMessages) {
14309
+ if (this.budgetContext?.exceeded && this.budgetContext.policy !== "warn") {
14310
+ const { limit, totalCost: spent, policy } = this.budgetContext;
14311
+ throw new BudgetExceededError(limit, spent, policy);
14312
+ }
14313
+ if (projection.removedThinkingBlocks > 0) {
14314
+ this.emitEvent({
14315
+ type: "provider_diagnostic",
14316
+ agent: agent2._name,
14317
+ data: {
14318
+ kind: "reasoning_context_reset",
14319
+ ...provider.name ? { provider: provider.name } : {},
14320
+ model: typeof providerOptions?.model === "string" ? providerOptions.model : model,
14321
+ droppedBlocks: projection.removedThinkingBlocks,
14322
+ reasons: { client_prefix_rewrite: projection.removedThinkingBlocks }
14323
+ }
14324
+ });
14325
+ }
14326
+ for (const msg of projection.messages) {
13780
14327
  messages.push(msg);
13781
14328
  }
13782
14329
  } else {
14330
+ if (cachedSummaryMessage) messages.push(cachedSummaryMessage);
13783
14331
  for (const msg of sessionHistory) {
13784
14332
  messages.push(msg);
13785
14333
  }
13786
14334
  }
13787
14335
  } else {
14336
+ if (cachedSummaryMessage) messages.push(cachedSummaryMessage);
13788
14337
  for (const msg of sessionHistory) {
13789
14338
  messages.push(msg);
13790
14339
  }
@@ -13885,6 +14434,7 @@ ${appendedSchemaText}`;
13885
14434
  }
13886
14435
  const startTime = Date.now();
13887
14436
  const askClock = currentAskClock();
14437
+ const pausedAtStartMs = askClock ? pausedAskClockMs(askClock, startTime) : 0;
13888
14438
  const timingTotals = { turns: 0, queuedMs: 0, retryMs: 0, wireMs: 0 };
13889
14439
  const currentMessages = [...messages];
13890
14440
  let turns = 0;
@@ -13902,7 +14452,7 @@ ${appendedSchemaText}`;
13902
14452
  this.currentSignal?.throwIfAborted();
13903
14453
  const now = Date.now();
13904
14454
  const elapsedMs = now - startTime;
13905
- const pausedMs = askClock ? pausedAskClockMs(askClock, now) : 0;
14455
+ const pausedMs = askClock ? Math.max(0, pausedAskClockMs(askClock, now) - pausedAtStartMs) : 0;
13906
14456
  const chargedMs = Math.max(0, elapsedMs - Math.min(pausedMs, Math.max(0, elapsedMs)));
13907
14457
  if (chargedMs > timeoutMs) {
13908
14458
  throw new TimeoutError(
@@ -14157,6 +14707,7 @@ ${appendedSchemaText}`;
14157
14707
  usage: chunk.usage,
14158
14708
  cost: chunk.cost,
14159
14709
  providerMetadata: chunk.providerMetadata,
14710
+ diagnostics: chunk.diagnostics,
14160
14711
  timing: chunk.timing
14161
14712
  };
14162
14713
  }
@@ -14274,6 +14825,34 @@ ${appendedSchemaText}`;
14274
14825
  timingTotals.retryMs += response.timing.retryMs;
14275
14826
  timingTotals.wireMs += response.timing.wireMs;
14276
14827
  }
14828
+ const reasoningReset = response.diagnostics?.reasoningContextReset;
14829
+ if (reasoningReset && Number.isSafeInteger(reasoningReset.droppedBlocks) && reasoningReset.droppedBlocks > 0) {
14830
+ const reasons = {};
14831
+ for (const reason of [
14832
+ "prefix_binding_mismatch",
14833
+ "model_binding_mismatch",
14834
+ "organization_binding_mismatch",
14835
+ "end_user_binding_mismatch",
14836
+ "client_prefix_rewrite",
14837
+ "other"
14838
+ ]) {
14839
+ const count = reasoningReset.reasons?.[reason];
14840
+ if (typeof count === "number" && Number.isSafeInteger(count) && count > 0) {
14841
+ reasons[reason] = count;
14842
+ }
14843
+ }
14844
+ this.emitEvent({
14845
+ type: "provider_diagnostic",
14846
+ agent: agent2._name,
14847
+ data: {
14848
+ kind: "reasoning_context_reset",
14849
+ ...provider.name ? { provider: provider.name } : {},
14850
+ model: typeof providerOptions?.model === "string" ? providerOptions.model : model,
14851
+ droppedBlocks: reasoningReset.droppedBlocks,
14852
+ reasons
14853
+ }
14854
+ });
14855
+ }
14277
14856
  this.emitEvent({
14278
14857
  type: "agent_call_end",
14279
14858
  agent: agent2._name,
@@ -15016,31 +15595,65 @@ ${appendedSchemaText}`;
15016
15595
  }
15017
15596
  return defs;
15018
15597
  }
15598
+ /** Session id for session-scoped ask-summary persistence, when this
15599
+ * execution belongs to a session and has a store to persist into. */
15600
+ askSummarySessionId() {
15601
+ const sessionId = this.metadata?.sessionId;
15602
+ if (this.metadata?.sessionPersist === false) return void 0;
15603
+ return this.stateStore && typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
15604
+ }
15605
+ /** This agent's ask summary: the in-memory copy first, else the session's
15606
+ * persisted record (read once per execution). */
15607
+ async loadAskSummary(agentName) {
15608
+ if (this.askSummaries.has(agentName)) return this.askSummaries.get(agentName) ?? void 0;
15609
+ const sessionId = this.askSummarySessionId();
15610
+ const stored = sessionId ? parseAskSummaryRecord(
15611
+ await this.stateStore.getSessionMeta(sessionId, askSummaryMetaKey(agentName))
15612
+ ) : void 0;
15613
+ this.askSummaries.set(agentName, stored ?? null);
15614
+ return stored;
15615
+ }
15616
+ async saveAskSummary(agentName, record) {
15617
+ this.askSummaries.set(agentName, record);
15618
+ const sessionId = this.askSummarySessionId();
15619
+ if (!sessionId) return;
15620
+ const key = askSummaryMetaKey(agentName);
15621
+ try {
15622
+ await this.stateStore.saveSessionMeta(sessionId, key, record);
15623
+ } catch (error) {
15624
+ this.emitEvent({
15625
+ type: "log",
15626
+ agent: agentName,
15627
+ data: {
15628
+ warning: `Could not persist ask summary '${key}' for session '${sessionId}'; later executions will regenerate it. ` + (error instanceof Error ? error.message : String(error))
15629
+ }
15630
+ });
15631
+ }
15632
+ }
15019
15633
  /**
15020
- * Summarize old messages to fit within context window.
15021
- * Keeps recent messages intact, summarizes older ones.
15634
+ * Project `history` into `availableTokens` for one agent's request: a summary
15635
+ * of an exact covered prefix followed by every later message. Reuses the
15636
+ * agent's cached boundary (in memory or persisted for the session) while the
15637
+ * covered prefix, durable session summary, and summary model are unchanged
15638
+ * and the whole tail fits; otherwise generates a new summary. Also reports how
15639
+ * many Anthropic thinking blocks the projection removed from carried turns.
15022
15640
  */
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)];
15641
+ async summarizeHistory(agentName, modelUri, provider, model, history, availableTokens) {
15642
+ const summaryModelUri = this.config.contextManagement?.summaryModel ?? modelUri;
15643
+ const cache = await this.loadAskSummary(agentName);
15644
+ if (cache && cache.sourceSummary === this.sessionSummary && cache.summaryModelUri === summaryModelUri && cache.coveredCount <= history.length && cache.prefixHash === summaryPrefixHash(history, cache.coveredCount)) {
15645
+ const summaryMsg = summaryContextMessage(cache.summary);
15646
+ const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg.content)) + 4;
15647
+ const tail2 = history.slice(cache.coveredCount);
15648
+ const tailTokens = estimateMessagesTokens(tail2).tokens;
15649
+ if (summaryTokens + tailTokens <= availableTokens && (tail2.length === 0 || tail2[0].role === "user")) {
15650
+ let removedThinkingBlocks = 0;
15651
+ const projected = tail2.map((message, index) => {
15652
+ if (cache.coveredCount + index >= cache.invalidatedThinkingThrough) return message;
15653
+ removedThinkingBlocks += anthropicThinkingBlockCount(message);
15654
+ return withoutAnthropicThinking(message);
15655
+ });
15656
+ return { messages: [summaryMsg, ...projected], removedThinkingBlocks };
15044
15657
  }
15045
15658
  }
15046
15659
  let recentTokens = 0;
@@ -15065,42 +15678,103 @@ ${this.summaryCache}`
15065
15678
  } else {
15066
15679
  splitIdx = history.length;
15067
15680
  }
15068
- if (splitIdx === 0) return history;
15681
+ if (splitIdx === 0 && this.sessionSummary) splitIdx = history.length;
15682
+ if (splitIdx === 0) return { messages: history, removedThinkingBlocks: 0 };
15069
15683
  const oldMessages = history.slice(0, splitIdx);
15070
- const summaryModelUri = this.config.contextManagement?.summaryModel;
15684
+ const configuredSummaryModelUri = this.config.contextManagement?.summaryModel;
15071
15685
  let summaryProvider;
15072
15686
  let summaryModel;
15073
- if (summaryModelUri) {
15074
- const resolved = this.resolveProviderUri(summaryModelUri);
15687
+ if (configuredSummaryModelUri) {
15688
+ const resolved = this.resolveProviderUri(configuredSummaryModelUri);
15075
15689
  summaryProvider = resolved.provider;
15076
15690
  summaryModel = resolved.model;
15077
15691
  } else {
15078
15692
  summaryProvider = provider;
15079
15693
  summaryModel = model;
15080
15694
  }
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 }
15695
+ const summaryPrompt = buildSummaryPrompt(oldMessages, this.sessionSummary);
15696
+ const summaryStart = Date.now();
15697
+ this.emitEvent({
15698
+ type: "agent_call_start",
15699
+ agent: agentName,
15700
+ model: summaryModelUri,
15701
+ turn: 0,
15702
+ data: {
15703
+ purpose: "summary",
15704
+ prompt: summaryPrompt,
15705
+ system: SUMMARY_SYSTEM_PROMPT,
15706
+ params: { maxTokens: SUMMARY_MAX_TOKENS },
15707
+ turn: 0
15708
+ }
15709
+ });
15710
+ let summaryResponse;
15711
+ try {
15712
+ this.currentSignal?.throwIfAborted();
15713
+ summaryResponse = await requestSummary(summaryProvider, {
15714
+ model: summaryModel,
15715
+ prompt: summaryPrompt,
15716
+ signal: this.currentSignal
15717
+ });
15718
+ } catch (error) {
15719
+ const providerError = error instanceof ProviderError ? error : void 0;
15720
+ this.emitEvent({
15721
+ type: "agent_call_end",
15722
+ agent: agentName,
15723
+ model: summaryModelUri,
15724
+ duration: Date.now() - summaryStart,
15725
+ ...providerError?.timing ? { timing: providerError.timing } : {},
15726
+ data: {
15727
+ purpose: "summary",
15728
+ response: "",
15729
+ turn: 0,
15730
+ // Provider errors may echo the summarized history, so the event
15731
+ // uses a fixed projection even when trace redaction is disabled.
15732
+ error: "Context-management summary call failed",
15733
+ ...providerError ? { status: providerError.status, retryable: providerError.retryable } : {}
15734
+ }
15735
+ });
15736
+ throw error;
15737
+ }
15738
+ if (isUsableCost2(summaryResponse.cost)) this._accumulateBudgetCost(summaryResponse.cost);
15739
+ this.emitEvent({
15740
+ type: "agent_call_end",
15741
+ agent: agentName,
15742
+ model: summaryModelUri,
15743
+ cost: summaryResponse.cost,
15744
+ ...!isUsableCost2(summaryResponse.cost) ? { unpriced: true } : {},
15745
+ tokens: summaryResponse.usage ? {
15746
+ input: summaryResponse.usage.prompt_tokens,
15747
+ output: summaryResponse.usage.completion_tokens,
15748
+ reasoning: summaryResponse.usage.reasoning_tokens,
15749
+ cached: summaryResponse.usage.cached_tokens,
15750
+ cacheWrite: summaryResponse.usage.cache_write_tokens
15751
+ } : void 0,
15752
+ duration: Date.now() - summaryStart,
15753
+ ...summaryResponse.timing ? { timing: summaryResponse.timing } : {},
15754
+ data: { purpose: "summary", response: summaryResponse.content, turn: 0 }
15755
+ });
15756
+ const prefixHash = summaryPrefixHash(history, splitIdx);
15757
+ if (prefixHash) {
15758
+ await this.saveAskSummary(agentName, {
15759
+ summary: summaryResponse.content,
15760
+ coveredCount: splitIdx,
15761
+ prefixHash,
15762
+ ...this.sessionSummary !== void 0 ? { sourceSummary: this.sessionSummary } : {},
15763
+ summaryModelUri,
15764
+ invalidatedThinkingThrough: history.length
15765
+ });
15766
+ }
15767
+ const tail = history.slice(splitIdx);
15768
+ return {
15769
+ messages: [
15770
+ summaryContextMessage(summaryResponse.content),
15771
+ ...tail.map(withoutAnthropicThinking)
15089
15772
  ],
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}`
15773
+ removedThinkingBlocks: tail.reduce(
15774
+ (count, message) => count + anthropicThinkingBlockCount(message),
15775
+ 0
15776
+ )
15102
15777
  };
15103
- return [summaryMsg, ...history.slice(splitIdx)];
15104
15778
  }
15105
15779
  // ── ctx.checkpoint() ────────────────────────────────────────────────
15106
15780
  /**
@@ -17509,17 +18183,17 @@ var Session = class _Session {
17509
18183
  throw new Error("SessionOptions.history.summaryModel is required when summarize is true");
17510
18184
  }
17511
18185
  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);
18186
+ const summary = await this.runtime.summarizeMessages(
18187
+ messagesToDrop,
18188
+ summaryModel,
18189
+ cachedSummary ? { previousSummary: cachedSummary } : void 0
18190
+ );
17517
18191
  await this.store.saveSessionMeta(this.sessionId, "summaryCache", summary);
17518
18192
  cachedSummary = summary;
17519
18193
  }
17520
18194
  const trimmed = history.slice(-maxMessages);
17521
18195
  history.length = 0;
17522
- history.push(...trimmed);
18196
+ history.push(...trimmed.map(withoutAnthropicThinking));
17523
18197
  }
17524
18198
  const userMessage = { role: "user", content: preparedInput.content };
17525
18199
  history.push(userMessage);
@@ -17529,7 +18203,10 @@ var Session = class _Session {
17529
18203
  const metadata = {
17530
18204
  sessionId: this.sessionId,
17531
18205
  sessionHistory: history,
17532
- ...cachedSummary ? { summaryCache: cachedSummary } : {}
18206
+ ...cachedSummary ? { summaryCache: cachedSummary } : {},
18207
+ // An opted-out session must leave nothing at rest, including the
18208
+ // agent-scoped ask-summary records the runtime would otherwise cache.
18209
+ ...this.options.persist === false ? { sessionPersist: false } : {}
17533
18210
  };
17534
18211
  return { history, metadata };
17535
18212
  }
@@ -20611,22 +21288,17 @@ var AxlRuntime = class extends EventEmitter2 {
20611
21288
  /**
20612
21289
  * Summarize a list of chat messages into a concise summary string.
20613
21290
  * Used by Session to summarize dropped messages when history.summarize is enabled.
21291
+ * `previousSummary` is folded in so a rolling summary never loses what it
21292
+ * alone still carries. Shares its prompt and provider call with the
21293
+ * `AgentConfig.maxContext` summarizer (see `compaction.ts`). It runs outside
21294
+ * any workflow execution, so it emits no ask-scoped events.
20614
21295
  */
20615
- async summarizeMessages(messages, modelUri) {
21296
+ async summarizeMessages(messages, modelUri, options) {
20616
21297
  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
- );
21298
+ const response = await requestSummary(provider, {
21299
+ model,
21300
+ prompt: buildSummaryPrompt(messages, options?.previousSummary)
21301
+ });
20630
21302
  return response.content;
20631
21303
  }
20632
21304
  /** Get the state store (for testing and advanced use cases). */
@@ -22056,6 +22728,7 @@ export {
22056
22728
  TranscriptionProviderRegistry,
22057
22729
  UnsupportedEventSchemaVersionError,
22058
22730
  UnsupportedModelInputError,
22731
+ UnsupportedModelOptionError,
22059
22732
  UnsupportedTranscriptionInputError,
22060
22733
  VLLM_PROFILE,
22061
22734
  ValidationError,