@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.cjs CHANGED
@@ -184,6 +184,7 @@ __export(index_exports, {
184
184
  TranscriptionProviderRegistry: () => TranscriptionProviderRegistry,
185
185
  UnsupportedEventSchemaVersionError: () => UnsupportedEventSchemaVersionError,
186
186
  UnsupportedModelInputError: () => UnsupportedModelInputError,
187
+ UnsupportedModelOptionError: () => UnsupportedModelOptionError,
187
188
  UnsupportedTranscriptionInputError: () => UnsupportedTranscriptionInputError,
188
189
  VLLM_PROFILE: () => VLLM_PROFILE,
189
190
  ValidationError: () => ValidationError,
@@ -272,6 +273,23 @@ var UnsupportedModelInputError = class extends AxlError {
272
273
  this.source = options.source;
273
274
  }
274
275
  };
276
+ var UnsupportedModelOptionError = class extends AxlError {
277
+ provider;
278
+ model;
279
+ option;
280
+ remediation;
281
+ constructor(options) {
282
+ super(
283
+ "UNSUPPORTED_MODEL_OPTION",
284
+ `Provider '${options.provider}' model '${options.model}' does not support ${options.option}. ${options.remediation}`
285
+ );
286
+ this.name = "UnsupportedModelOptionError";
287
+ this.provider = options.provider;
288
+ this.model = options.model;
289
+ this.option = options.option;
290
+ this.remediation = options.remediation;
291
+ }
292
+ };
275
293
  var InvalidTranscriptionInputError = class extends AxlError {
276
294
  constructor(message) {
277
295
  super("INVALID_TRANSCRIPTION_INPUT", message);
@@ -563,7 +581,7 @@ var sharedGuard = typeof installedGuard === "object" && installedGuard !== null
563
581
  var buildPath = typeof __AXL_BUILD_PATH__ === "string" ? __AXL_BUILD_PATH__ : "<source>";
564
582
  var thisCopy = {
565
583
  path: buildPath.startsWith("file:") ? (0, import_node_url.fileURLToPath)(buildPath) : buildPath,
566
- version: true ? "0.25.0" : "unknown"
584
+ version: true ? "0.25.2" : "unknown"
567
585
  };
568
586
  function warnCrossCopy(other, joined) {
569
587
  if (other === thisCopy) return;
@@ -3436,8 +3454,12 @@ var OpenAICompatibleProvider = class {
3436
3454
  for (const key of forbidden) {
3437
3455
  if (!userKeys.includes(key)) delete body[key];
3438
3456
  }
3457
+ this.validateFinalRequestBody(body);
3439
3458
  return body;
3440
3459
  }
3460
+ /** Native subclasses may reject model-invalid fields after providerOptions merge. */
3461
+ validateFinalRequestBody(_body) {
3462
+ }
3441
3463
  formatMessage(msg, model) {
3442
3464
  const modalities = this.inputModalities(model);
3443
3465
  const out = {
@@ -3845,6 +3867,24 @@ var withLongContext = (short) => ({
3845
3867
  output: short.output * 1.5
3846
3868
  });
3847
3869
  var DIRECT_OPENAI_CATALOG = [
3870
+ {
3871
+ aliases: ["gpt-6-astra"],
3872
+ short: { input: 10 / M, cachedInput: 1 / M, cacheWrite: 12.5 / M, output: 50 / M },
3873
+ long: { input: 20 / M, cachedInput: 2 / M, cacheWrite: 25 / M, output: 75 / M },
3874
+ contextBoundary: LONG_CONTEXT_BOUNDARY
3875
+ },
3876
+ {
3877
+ aliases: ["gpt-6-sol"],
3878
+ short: { input: 2 / M, cachedInput: 0.2 / M, cacheWrite: 2.5 / M, output: 10 / M },
3879
+ long: { input: 4 / M, cachedInput: 0.4 / M, cacheWrite: 5 / M, output: 15 / M },
3880
+ contextBoundary: LONG_CONTEXT_BOUNDARY
3881
+ },
3882
+ {
3883
+ aliases: ["gpt-6-luna"],
3884
+ short: { input: 0.1 / M, cachedInput: 0.01 / M, cacheWrite: 0.125 / M, output: 0.5 / M },
3885
+ long: { input: 0.2 / M, cachedInput: 0.02 / M, cacheWrite: 0.25 / M, output: 0.75 / M },
3886
+ contextBoundary: LONG_CONTEXT_BOUNDARY
3887
+ },
3848
3888
  {
3849
3889
  aliases: ["gpt-5.6", "gpt-5.6-sol"],
3850
3890
  snapshotBase: "gpt-5.6-sol",
@@ -4175,7 +4215,7 @@ function estimateDirectOpenAICost(model, usage, context) {
4175
4215
  const audioOutput = audio_output_tokens ?? 0;
4176
4216
  if (![prompt_tokens, completion_tokens, cached, cacheWrite, audioInput, audioOutput].every(
4177
4217
  (count) => Number.isSafeInteger(count) && count >= 0
4178
- ) || cached + cacheWrite + audioInput > prompt_tokens || audioOutput > completion_tokens) {
4218
+ ) || resolveOpenAIModelDescriptor(model).strictUsageTotals && (!Number.isSafeInteger(usage.total_tokens) || usage.total_tokens !== prompt_tokens + completion_tokens) || cached + cacheWrite + audioInput > prompt_tokens || audioOutput > completion_tokens) {
4179
4219
  return void 0;
4180
4220
  }
4181
4221
  if (audioInput > 0 && entry.audio === void 0 || audioOutput > 0 && entry.audio?.output === void 0) {
@@ -4200,57 +4240,206 @@ function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens)
4200
4240
  cached_tokens: cachedTokens
4201
4241
  });
4202
4242
  }
4203
- function isOSeriesModel(model) {
4204
- return /^(o1|o3|o4-mini)/.test(model);
4243
+ var EFFORT_FALLBACK = {
4244
+ none: "minimal",
4245
+ minimal: "low",
4246
+ low: "medium",
4247
+ medium: "high",
4248
+ max: "xhigh",
4249
+ xhigh: "high"
4250
+ };
4251
+ var BASE_EFFORTS = ["minimal", "low", "medium", "high"];
4252
+ function bothEndpoints(value) {
4253
+ return { chat: value, responses: value };
4205
4254
  }
4206
- function supportsReasoningEffort(model) {
4207
- return isOSeriesModel(model) || /^gpt-5/.test(model);
4255
+ function gpt6(id, efforts, chatTools) {
4256
+ return {
4257
+ id,
4258
+ family: "gpt-6",
4259
+ reasoning: true,
4260
+ efforts: bothEndpoints(efforts),
4261
+ defaultEffort: "medium",
4262
+ // Same policy as GPT-5.x: a portable `temperature` is dropped while
4263
+ // reasoning is active (including the active default). A raw
4264
+ // `providerOptions` sampling field is rejected by the final-body check.
4265
+ sampling: bothEndpoints("unless-effort-none"),
4266
+ chatTools,
4267
+ validatesFinalBody: true,
4268
+ strictUsageTotals: true
4269
+ };
4208
4270
  }
4209
- var GPT_56_REASONING_MODELS = /* @__PURE__ */ new Set([
4210
- "gpt-5.6",
4211
- "gpt-5.6-sol",
4212
- "gpt-5.6-terra",
4213
- "gpt-5.6-luna"
4214
- ]);
4215
- function supportsMaxReasoningEffort(model) {
4216
- return GPT_56_REASONING_MODELS.has(model);
4271
+ function gpt56(id) {
4272
+ const chat = ["none", ...BASE_EFFORTS, "xhigh"];
4273
+ return {
4274
+ id,
4275
+ family: "gpt-5.6",
4276
+ reasoning: true,
4277
+ efforts: { chat, responses: [...chat, "max"] },
4278
+ // Responses rejects temperature even at the provider-default effort.
4279
+ sampling: { chat: "when-effort-sent", responses: "always" },
4280
+ chatTools: "allowed",
4281
+ validatesFinalBody: false,
4282
+ strictUsageTotals: false
4283
+ };
4217
4284
  }
4218
- function supportsReasoningNone(model) {
4219
- return /^gpt-5\.[1-9]/.test(model);
4285
+ var OPENAI_EXACT_MODELS = new Map(
4286
+ [
4287
+ gpt6("gpt-6-astra", ["low", "medium", "high", "xhigh", "max"], "never"),
4288
+ gpt6("gpt-6-sol", ["none", "low", "medium", "high", "xhigh", "max"], "effort-none-only"),
4289
+ gpt6("gpt-6-luna", ["none", "low", "medium", "high", "xhigh", "max"], "effort-none-only"),
4290
+ gpt56("gpt-5.6"),
4291
+ gpt56("gpt-5.6-sol"),
4292
+ gpt56("gpt-5.6-terra"),
4293
+ gpt56("gpt-5.6-luna")
4294
+ ].map((descriptor) => [descriptor.id, descriptor])
4295
+ );
4296
+ var O_SERIES_DESCRIPTOR = {
4297
+ id: "o-series",
4298
+ family: "o-series",
4299
+ reasoning: true,
4300
+ efforts: bothEndpoints(BASE_EFFORTS),
4301
+ defaultEffort: "medium",
4302
+ sampling: bothEndpoints("always"),
4303
+ chatTools: "allowed",
4304
+ validatesFinalBody: false,
4305
+ strictUsageTotals: false
4306
+ };
4307
+ var GPT_5_PRO_DESCRIPTOR = {
4308
+ id: "gpt-5-pro",
4309
+ family: "gpt-5-pro",
4310
+ reasoning: true,
4311
+ efforts: bothEndpoints(["high"]),
4312
+ defaultEffort: "high",
4313
+ sampling: bothEndpoints("when-effort-sent"),
4314
+ chatTools: "allowed",
4315
+ validatesFinalBody: false,
4316
+ strictUsageTotals: false
4317
+ };
4318
+ var DEFAULT_DESCRIPTOR = {
4319
+ id: "default",
4320
+ family: "default",
4321
+ reasoning: false,
4322
+ // Never emitted (the model is not reasoning-capable); keeps the clamp total.
4323
+ efforts: bothEndpoints(BASE_EFFORTS),
4324
+ sampling: bothEndpoints("never"),
4325
+ chatTools: "allowed",
4326
+ validatesFinalBody: false,
4327
+ strictUsageTotals: false
4328
+ };
4329
+ function gpt5FamilyDescriptor(model) {
4330
+ const none = /^gpt-5\.[1-9]/.test(model);
4331
+ const xhigh = /^gpt-5\.([2-9]|\d{2,})/.test(model);
4332
+ const efforts = [
4333
+ ...none ? ["none"] : [],
4334
+ ...BASE_EFFORTS,
4335
+ ...xhigh ? ["xhigh"] : []
4336
+ ];
4337
+ return {
4338
+ id: "gpt-5",
4339
+ family: "gpt-5",
4340
+ reasoning: true,
4341
+ efforts: bothEndpoints(efforts),
4342
+ ...none ? {} : { defaultEffort: "medium" },
4343
+ sampling: bothEndpoints("when-effort-sent"),
4344
+ chatTools: "allowed",
4345
+ validatesFinalBody: false,
4346
+ strictUsageTotals: false
4347
+ };
4220
4348
  }
4221
- function supportsXhigh(model) {
4222
- return /^gpt-5\.([2-9]|\d{2,})/.test(model);
4349
+ var OPENAI_FAMILY_FALLBACKS = [
4350
+ { match: /^(o1|o3|o4-mini)/, describe: () => O_SERIES_DESCRIPTOR },
4351
+ { match: /^gpt-5-pro/, describe: () => GPT_5_PRO_DESCRIPTOR },
4352
+ { match: /^gpt-5/, describe: gpt5FamilyDescriptor }
4353
+ ];
4354
+ function resolveOpenAIModelDescriptor(model) {
4355
+ const exact = OPENAI_EXACT_MODELS.get(model);
4356
+ if (exact) return exact;
4357
+ const family = OPENAI_FAMILY_FALLBACKS.find((entry) => entry.match.test(model));
4358
+ return family ? family.describe(model) : DEFAULT_DESCRIPTOR;
4359
+ }
4360
+ function clampToDescriptor(descriptor, endpoint, effort) {
4361
+ const accepted = descriptor.efforts[endpoint];
4362
+ let candidate = effort;
4363
+ while (candidate !== void 0 && !accepted.includes(candidate)) {
4364
+ candidate = EFFORT_FALLBACK[candidate];
4365
+ }
4366
+ if (candidate === void 0) {
4367
+ throw new Error(
4368
+ `OpenAI descriptor '${descriptor.id}' accepts no ${endpoint} effort reachable from '${effort}'`
4369
+ );
4370
+ }
4371
+ return candidate;
4223
4372
  }
4224
- function clampReasoningEffort(model, effort) {
4225
- if (model.startsWith("gpt-5-pro")) return "high";
4226
- if (effort === "none" && !supportsReasoningNone(model)) return "minimal";
4227
- if (effort === "max" && !supportsMaxReasoningEffort(model)) {
4228
- return clampReasoningEffort(model, "xhigh");
4373
+ function stripsPortableSampling(descriptor, endpoint, wireEffort) {
4374
+ switch (descriptor.sampling[endpoint]) {
4375
+ case "never":
4376
+ return false;
4377
+ case "always":
4378
+ return true;
4379
+ case "when-effort-sent":
4380
+ return wireEffort !== void 0;
4381
+ case "unless-effort-none":
4382
+ return (wireEffort ?? descriptor.defaultEffort) !== "none";
4229
4383
  }
4230
- if (effort === "xhigh" && !supportsXhigh(model)) return "high";
4231
- return effort;
4232
4384
  }
4233
- function effortToReasoningEffort(effort, model) {
4234
- return effort === "max" && model !== void 0 && supportsMaxReasoningEffort(model) ? "max" : effort === "max" ? "xhigh" : effort;
4385
+ function isOSeriesModel(model) {
4386
+ return resolveOpenAIModelDescriptor(model).family === "o-series";
4235
4387
  }
4236
4388
  function budgetToReasoningEffort(budget) {
4237
4389
  if (budget <= 1024) return "low";
4238
4390
  if (budget <= 8192) return "medium";
4239
4391
  return "high";
4240
4392
  }
4393
+ function resolveEndpointReasoningEffort(model, endpoint, resolved) {
4394
+ const descriptor = resolveOpenAIModelDescriptor(model);
4395
+ if (!descriptor.reasoning) return void 0;
4396
+ const requested = resolved.hasBudgetOverride ? budgetToReasoningEffort(resolved.thinkingBudget) : !resolved.thinkingDisabled && resolved.activeEffort ? resolved.activeEffort : resolved.thinkingDisabled ? "none" : void 0;
4397
+ return requested === void 0 ? void 0 : clampToDescriptor(descriptor, endpoint, requested);
4398
+ }
4241
4399
  function resolveOpenAIReasoningEffort(model, resolved) {
4242
- if (!supportsReasoningEffort(model)) return void 0;
4243
- if (resolved.hasBudgetOverride) {
4244
- return clampReasoningEffort(model, budgetToReasoningEffort(resolved.thinkingBudget));
4245
- }
4246
- if (!resolved.thinkingDisabled && resolved.activeEffort) {
4247
- return clampReasoningEffort(model, effortToReasoningEffort(resolved.activeEffort, model));
4248
- }
4249
- return resolved.thinkingDisabled ? clampReasoningEffort(model, "none") : void 0;
4400
+ return resolveEndpointReasoningEffort(model, "responses", resolved);
4250
4401
  }
4251
4402
  function resolveOpenAIChatReasoningEffort(model, resolved) {
4252
- const effort = resolveOpenAIReasoningEffort(model, resolved);
4253
- return effort === "max" ? "xhigh" : effort;
4403
+ return resolveEndpointReasoningEffort(model, "chat", resolved);
4404
+ }
4405
+ function validateOpenAIFinalRequestBody(body, endpoint) {
4406
+ const model = body.model;
4407
+ if (typeof model !== "string") return;
4408
+ const descriptor = resolveOpenAIModelDescriptor(model);
4409
+ if (!descriptor.validatesFinalBody) return;
4410
+ const provider = endpoint === "chat" ? "openai" : "openai-responses";
4411
+ const fail = (option, remediation) => {
4412
+ throw new UnsupportedModelOptionError({ provider, model, option, remediation });
4413
+ };
4414
+ const rawEffort = endpoint === "chat" ? body.reasoning_effort : body.reasoning !== null && typeof body.reasoning === "object" ? body.reasoning.effort : void 0;
4415
+ const accepted = descriptor.efforts[endpoint];
4416
+ if (rawEffort !== void 0 && !accepted.includes(rawEffort)) {
4417
+ fail("reasoning effort", `Use one of: ${accepted.join(", ")}.`);
4418
+ }
4419
+ const activeReasoning = (rawEffort ?? descriptor.defaultEffort) !== "none";
4420
+ if (endpoint === "chat" && descriptor.chatTools !== "allowed") {
4421
+ const toolChoice = body.tool_choice;
4422
+ const toolIntent = Array.isArray(body.tools) && body.tools.length > 0 || toolChoice !== void 0 && toolChoice !== null && toolChoice !== "none" && toolChoice !== "auto";
4423
+ if (toolIntent && (descriptor.chatTools === "never" || activeReasoning)) {
4424
+ fail(
4425
+ "Chat Completions tool calling",
4426
+ descriptor.chatTools === "never" ? `Use openai-responses:${model} for tools.` : `Use openai-responses:${model} for reasoning with tools, or set effective reasoning_effort to 'none'.`
4427
+ );
4428
+ }
4429
+ }
4430
+ if (activeReasoning) {
4431
+ const forbidden = endpoint === "chat" ? ["temperature", "top_p", "top_logprobs", "logprobs"] : ["temperature", "top_p", "top_logprobs"];
4432
+ for (const key of forbidden) {
4433
+ if (body[key] !== void 0)
4434
+ fail(key, `Remove ${key} or set reasoning effort to 'none' on Sol or Luna.`);
4435
+ }
4436
+ if (endpoint === "responses" && Array.isArray(body.include) && body.include.includes("message.output_text.logprobs")) {
4437
+ fail(
4438
+ "message.output_text.logprobs",
4439
+ "Remove it from include or set reasoning effort to none on Sol or Luna."
4440
+ );
4441
+ }
4442
+ }
4254
4443
  }
4255
4444
  function resolveOpenAIEffortResolution(options, resolveWireEffort, endpoint) {
4256
4445
  const resolved = resolveThinkingOptions(options);
@@ -4266,11 +4455,15 @@ function resolveOpenAIEffortResolution(options, resolveWireEffort, endpoint) {
4266
4455
  };
4267
4456
  }
4268
4457
  var openaiReasoningEmit = (body, resolved, model) => {
4269
- const oSeries = isOSeriesModel(model);
4270
- const reasoningCapable = supportsReasoningEffort(model);
4271
4458
  const wireEffort = resolveOpenAIChatReasoningEffort(model, resolved);
4272
4459
  if (wireEffort) body.reasoning_effort = wireEffort;
4273
- return { stripTemperature: oSeries || reasoningCapable && wireEffort !== void 0 };
4460
+ return {
4461
+ stripTemperature: stripsPortableSampling(
4462
+ resolveOpenAIModelDescriptor(model),
4463
+ "chat",
4464
+ wireEffort
4465
+ )
4466
+ };
4274
4467
  };
4275
4468
  var OPENAI_PROFILE = {
4276
4469
  name: "openai",
@@ -4305,6 +4498,9 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
4305
4498
  "OpenAI Chat Completions"
4306
4499
  );
4307
4500
  }
4501
+ validateFinalRequestBody(body) {
4502
+ validateOpenAIFinalRequestBody(body, "chat");
4503
+ }
4308
4504
  computeCost(model, usage, _reportedCost, context) {
4309
4505
  return usage ? estimateDirectOpenAICost(model, usage, { ...context, baseUrl: this.baseUrl }) : void 0;
4310
4506
  }
@@ -4577,12 +4773,12 @@ var OpenAIResponsesProvider = class {
4577
4773
  // ---------------------------------------------------------------------------
4578
4774
  buildRequestBody(messages, options, stream) {
4579
4775
  const effectiveModel = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
4580
- const oSeries = isOSeriesModel(effectiveModel);
4581
- const reasoningCapable = supportsReasoningEffort(effectiveModel);
4776
+ const descriptor = resolveOpenAIModelDescriptor(effectiveModel);
4777
+ const reasoningCapable = descriptor.reasoning;
4582
4778
  const resolved = resolveThinkingOptions(options);
4583
4779
  const { includeThoughts } = resolved;
4584
4780
  const wireEffort = resolveOpenAIReasoningEffort(effectiveModel, resolved);
4585
- const stripTemp = oSeries || supportsMaxReasoningEffort(effectiveModel) || reasoningCapable && wireEffort !== void 0;
4781
+ const stripTemp = stripsPortableSampling(descriptor, "responses", wireEffort);
4586
4782
  const systemMessages = messages.filter((m) => m.role === "system");
4587
4783
  const nonSystemMessages = messages.filter((m) => m.role !== "system");
4588
4784
  const body = {
@@ -4631,6 +4827,7 @@ var OpenAIResponsesProvider = class {
4631
4827
  if (options.providerOptions) {
4632
4828
  Object.assign(body, options.providerOptions);
4633
4829
  }
4830
+ validateOpenAIFinalRequestBody(body, "responses");
4634
4831
  return body;
4635
4832
  }
4636
4833
  requestModel(body, fallback) {
@@ -4945,6 +5142,47 @@ function anthropicImageBlocks(parts, model) {
4945
5142
  }
4946
5143
  var ANTHROPIC_API_VERSION = "2023-06-01";
4947
5144
  var ANTHROPIC_FILES_BETA = "files-api-2025-04-14";
5145
+ var ANTHROPIC_THINKING_BINDING_BETA = "thinking-binding-controls-2026-08-01";
5146
+ function isRecord(value) {
5147
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5148
+ }
5149
+ function isFinalThinkingAdaptive(capability, body) {
5150
+ const thinking = body.thinking;
5151
+ if (thinking === void 0) {
5152
+ return capability.thinking === "adaptive-default-on" || capability.thinking === "adaptive-always-on";
5153
+ }
5154
+ return isRecord(thinking) && thinking.type === "adaptive";
5155
+ }
5156
+ var NATIVE_CLAUDE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
5157
+ function isNativeClaudeEffort(value) {
5158
+ return NATIVE_CLAUDE_EFFORTS.includes(value);
5159
+ }
5160
+ function hasReplayedThinking(body) {
5161
+ return Array.isArray(body.messages) && body.messages.some(
5162
+ (message) => isRecord(message) && Array.isArray(message.content) && message.content.some(
5163
+ (block) => isRecord(block) && (block.type === "thinking" || block.type === "redacted_thinking")
5164
+ )
5165
+ );
5166
+ }
5167
+ function hasFinalFileImage(body) {
5168
+ return Array.isArray(body.messages) && body.messages.some(
5169
+ (message) => isRecord(message) && Array.isArray(message.content) && message.content.some(
5170
+ (block) => isRecord(block) && block.type === "image" && isRecord(block.source) && block.source.type === "file"
5171
+ )
5172
+ );
5173
+ }
5174
+ function normalizeThinkingDrops(value) {
5175
+ if (!Array.isArray(value)) return void 0;
5176
+ const reasons = {};
5177
+ let droppedBlocks = 0;
5178
+ for (const entry of value) {
5179
+ if (!isRecord(entry) || entry.type !== "thinking_dropped") continue;
5180
+ 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";
5181
+ reasons[reason] = (reasons[reason] ?? 0) + 1;
5182
+ droppedBlocks++;
5183
+ }
5184
+ return droppedBlocks > 0 ? { droppedBlocks, reasons } : void 0;
5185
+ }
4948
5186
  function hasAnthropicProviderFileImage(messages) {
4949
5187
  return messages.some(
4950
5188
  (message) => Array.isArray(message.content) && message.content.some(
@@ -4953,11 +5191,51 @@ function hasAnthropicProviderFileImage(messages) {
4953
5191
  );
4954
5192
  }
4955
5193
  var CLAUDE_CAPABILITIES = {
5194
+ // Thinking off is `between_tools` (progress updates between tool calls);
5195
+ // `disabled` is a provider 400. Forced tool choice is a 400.
5196
+ "claude-sonnet-5-5": {
5197
+ thinking: "adaptive-default-on",
5198
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
5199
+ manualBudget: false,
5200
+ stripTemperature: true,
5201
+ disableAt: "high",
5202
+ offShape: "between_tools",
5203
+ forcedToolChoice: "rejected",
5204
+ defaultOnlySampling: true,
5205
+ strictThinking: true,
5206
+ thinkingBinding: true
5207
+ },
5208
+ // Thinking off is `disabled`, only at effort high or below. Forced tool
5209
+ // choice is accepted (the provider skips thinking for that turn).
5210
+ "claude-haiku-5-5": {
5211
+ thinking: "adaptive-default-on",
5212
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
5213
+ manualBudget: false,
5214
+ stripTemperature: true,
5215
+ disableAt: "high",
5216
+ defaultOnlySampling: true,
5217
+ strictThinking: true,
5218
+ thinkingBinding: true
5219
+ },
5220
+ "claude-opus-5-5": {
5221
+ thinking: "adaptive-always-on",
5222
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
5223
+ manualBudget: false,
5224
+ stripTemperature: true,
5225
+ forcedToolChoice: "rejected",
5226
+ defaultOnlySampling: true,
5227
+ strictThinking: true,
5228
+ thinkingBinding: true
5229
+ },
4956
5230
  "claude-fable-5-1": {
4957
5231
  thinking: "adaptive-always-on",
4958
5232
  effortLevels: ["low", "medium", "high", "xhigh", "max"],
4959
5233
  manualBudget: false,
4960
- stripTemperature: true
5234
+ stripTemperature: true,
5235
+ forcedToolChoice: "rejected",
5236
+ defaultOnlySampling: true,
5237
+ strictThinking: true,
5238
+ thinkingBinding: true
4961
5239
  },
4962
5240
  "claude-fable-5": {
4963
5241
  thinking: "adaptive-always-on",
@@ -5051,6 +5329,16 @@ function resolveClaudeCapability(model) {
5051
5329
  }
5052
5330
  var DEFAULT_CACHE_READ_MULTIPLIER = 0.1;
5053
5331
  var ANTHROPIC_RATES = {
5332
+ // Sonnet 5.5 cache reads are 0.05x ($0.10/MTok): the pricing table, its
5333
+ // footnote, the models overview, and the prompt-caching guide agree
5334
+ // (re-checked 2026-10-07).
5335
+ "claude-sonnet-5-5": { input: 2e-6, output: 1e-5, cacheReadMultiplier: 0.05 },
5336
+ "claude-haiku-5-5": {
5337
+ input: 1e-7,
5338
+ output: 5e-7,
5339
+ longPrompt: { aboveTokens: 1e5, card: { input: 5e-7, output: 25e-7 } }
5340
+ },
5341
+ "claude-opus-5-5": { input: 4e-6, output: 2e-5, cacheReadMultiplier: 0.05 },
5054
5342
  "claude-fable-5-1": { input: 1e-5, output: 5e-5, cacheReadMultiplier: 0.025 },
5055
5343
  "claude-fable-5": { input: 1e-5, output: 5e-5 },
5056
5344
  // Limited availability (Glasswing). Priced from the public pricing table; no
@@ -5112,14 +5400,16 @@ function estimateAnthropicCost(model, usage) {
5112
5400
  return void 0;
5113
5401
  }
5114
5402
  const cacheWriteTotal = safeTokenSum(cacheWrite5m, cacheWrite1h);
5115
- if (cacheWriteTotal === void 0 || safeTokenSum(usage.inputTokens, cacheRead, cacheWriteTotal) === void 0) {
5403
+ const promptTokens = cacheWriteTotal === void 0 ? void 0 : safeTokenSum(usage.inputTokens, cacheRead, cacheWriteTotal);
5404
+ if (cacheWriteTotal === void 0 || promptTokens === void 0) {
5116
5405
  return void 0;
5117
5406
  }
5118
5407
  if (usage.aggregateCacheWriteTokens !== void 0 && (!isValidTokenCount(usage.aggregateCacheWriteTokens) || usage.aggregateCacheWriteTokens !== cacheWriteTotal)) {
5119
5408
  return void 0;
5120
5409
  }
5121
- const { input, output } = rate;
5122
- const cacheReadRate = input * (rate.cacheReadMultiplier ?? DEFAULT_CACHE_READ_MULTIPLIER);
5410
+ const card = rate.longPrompt && promptTokens > rate.longPrompt.aboveTokens ? rate.longPrompt.card : rate;
5411
+ const { input, output } = card;
5412
+ const cacheReadRate = input * (card.cacheReadMultiplier ?? DEFAULT_CACHE_READ_MULTIPLIER);
5123
5413
  return usage.inputTokens * input + cacheRead * cacheReadRate + cacheWrite5m * input * 1.25 + cacheWrite1h * input * 2 + usage.outputTokens * output;
5124
5414
  }
5125
5415
  var THINKING_BUDGETS = {
@@ -5181,7 +5471,7 @@ function resolveClaudeThinking(model, resolved) {
5181
5471
  };
5182
5472
  }
5183
5473
  return {
5184
- thinking: { type: "disabled" },
5474
+ thinking: { type: capability.offShape ?? "disabled" },
5185
5475
  outputConfig: activeEffort ? { effort: activeEffort } : void 0,
5186
5476
  stripTemperature: true
5187
5477
  };
@@ -5485,11 +5775,12 @@ var AnthropicProvider = class {
5485
5775
  // chat - non-streaming completion
5486
5776
  // ---------------------------------------------------------------------------
5487
5777
  async chat(messages, options) {
5778
+ const body = this.buildRequestBody(messages, options, false);
5488
5779
  const headers = this.buildHeaders(
5489
5780
  await this.resolveKey(),
5490
- hasAnthropicProviderFileImage(messages)
5781
+ hasAnthropicProviderFileImage(messages) || hasFinalFileImage(body),
5782
+ this.requiresBindingBeta(body)
5491
5783
  );
5492
- const body = this.buildRequestBody(messages, options, false);
5493
5784
  const pricingContext = pricingContextFromBody(body);
5494
5785
  const recorder = new CallTimingRecorder(options.requestLifecycle);
5495
5786
  const governor = this.governorFor(pricingContext.model ?? options.model);
@@ -5527,11 +5818,12 @@ var AnthropicProvider = class {
5527
5818
  // stream - SSE streaming completion
5528
5819
  // ---------------------------------------------------------------------------
5529
5820
  async *stream(messages, options) {
5821
+ const body = this.buildRequestBody(messages, options, true);
5530
5822
  const headers = this.buildHeaders(
5531
5823
  await this.resolveKey(),
5532
- hasAnthropicProviderFileImage(messages)
5824
+ hasAnthropicProviderFileImage(messages) || hasFinalFileImage(body),
5825
+ this.requiresBindingBeta(body)
5533
5826
  );
5534
- const body = this.buildRequestBody(messages, options, true);
5535
5827
  const pricingContext = pricingContextFromBody(body);
5536
5828
  const recorder = new CallTimingRecorder(options.requestLifecycle);
5537
5829
  const governor = this.governorFor(pricingContext.model ?? options.model);
@@ -5573,14 +5865,119 @@ var AnthropicProvider = class {
5573
5865
  // ---------------------------------------------------------------------------
5574
5866
  // Internal: request building
5575
5867
  // ---------------------------------------------------------------------------
5576
- buildHeaders(apiKey, requiresFilesBeta = false) {
5868
+ buildHeaders(apiKey, requiresFilesBeta = false, requiresBindingBeta = false) {
5869
+ const betas = [
5870
+ ...requiresFilesBeta ? [ANTHROPIC_FILES_BETA] : [],
5871
+ ...requiresBindingBeta ? [ANTHROPIC_THINKING_BINDING_BETA] : []
5872
+ ];
5577
5873
  return {
5578
5874
  "Content-Type": "application/json",
5579
5875
  "x-api-key": apiKey,
5580
5876
  "anthropic-version": ANTHROPIC_API_VERSION,
5581
- ...requiresFilesBeta ? { "anthropic-beta": ANTHROPIC_FILES_BETA } : {}
5877
+ ...betas.length > 0 ? { "anthropic-beta": betas.join(",") } : {}
5582
5878
  };
5583
5879
  }
5880
+ /**
5881
+ * Whether the final body needs the binding beta header. Runs after
5882
+ * `validateAndBindFinalBody`, which has already injected `block_binding`
5883
+ * wherever it applies, so the header follows the final body rather than
5884
+ * re-deriving the injection rule.
5885
+ */
5886
+ requiresBindingBeta(body) {
5887
+ const capability = resolveClaudeCapability(String(body.model));
5888
+ const thinking = body.thinking;
5889
+ return capability?.thinkingBinding === true && isRecord(thinking) && thinking.type === "adaptive" && thinking.block_binding !== void 0;
5890
+ }
5891
+ /**
5892
+ * Validate the final body (after `providerOptions`) against the effective
5893
+ * model's capability, then inject `block_binding` where it applies. Every
5894
+ * rule is gated on its own capability field and is a no-op when unset, so a
5895
+ * model only gets the guards its provider contract calls for.
5896
+ */
5897
+ validateAndBindFinalBody(body) {
5898
+ const model = typeof body.model === "string" ? body.model : "";
5899
+ const capability = resolveClaudeCapability(model);
5900
+ if (!capability) return;
5901
+ const reject = (option, remediation) => {
5902
+ throw new UnsupportedModelOptionError({ provider: this.name, model, option, remediation });
5903
+ };
5904
+ if (capability.forcedToolChoice === "rejected") {
5905
+ const toolChoice = body.tool_choice;
5906
+ if (isRecord(toolChoice) && (toolChoice.type === "any" || toolChoice.type === "tool")) {
5907
+ reject("forced tool choice", "Use toolChoice: 'auto' or 'none' on this model.");
5908
+ }
5909
+ }
5910
+ if (capability.strictThinking) {
5911
+ const offShape = capability.thinking === "adaptive-always-on" ? void 0 : capability.offShape ?? "disabled";
5912
+ const thinking = body.thinking;
5913
+ const outputConfig = body.output_config;
5914
+ const effort = isRecord(outputConfig) ? outputConfig.effort : void 0;
5915
+ if (thinking !== void 0 && offShape === void 0) {
5916
+ if (!isRecord(thinking) || thinking.type !== "adaptive" || thinking.budget_tokens !== void 0) {
5917
+ reject(
5918
+ "manual or disabled thinking",
5919
+ "Use thinking: { type: 'adaptive' } and output_config.effort."
5920
+ );
5921
+ }
5922
+ } else if (thinking !== void 0) {
5923
+ if (!isRecord(thinking) || thinking.type !== "adaptive" && thinking.type !== offShape) {
5924
+ reject(
5925
+ "thinking.type",
5926
+ `Use thinking: { type: 'adaptive' }, or { type: '${offShape}' } to turn thinking off.`
5927
+ );
5928
+ }
5929
+ const record = thinking;
5930
+ if (record.type === "adaptive" && record.budget_tokens !== void 0) {
5931
+ reject("thinking.budget_tokens", "Use output_config.effort instead of budget_tokens.");
5932
+ }
5933
+ if (record.type === offShape) {
5934
+ const extra = Object.keys(record).filter((key) => key !== "type");
5935
+ if (extra.length > 0) {
5936
+ reject(
5937
+ `thinking.${offShape}.${extra[0]}`,
5938
+ `Send thinking: { type: '${offShape}' } with no other fields.`
5939
+ );
5940
+ }
5941
+ }
5942
+ }
5943
+ if (outputConfig !== void 0 && (!isRecord(outputConfig) || effort !== void 0 && !isNativeClaudeEffort(effort))) {
5944
+ reject("output_config.effort", "Use low, medium, high, xhigh, or max effort.");
5945
+ }
5946
+ if (offShape !== void 0 && isRecord(thinking) && thinking.type === offShape && effort !== void 0 && capability.disableAt && !isEffortAtOrBelow(effort, capability.disableAt)) {
5947
+ reject(
5948
+ "output_config.effort",
5949
+ `Thinking can be turned off only at effort '${capability.disableAt}' or below; lower the effort or use adaptive thinking.`
5950
+ );
5951
+ }
5952
+ }
5953
+ if (capability.defaultOnlySampling) {
5954
+ if (body.temperature !== void 0 && body.temperature !== 1) {
5955
+ reject("temperature", "Omit temperature or use the default value 1.");
5956
+ }
5957
+ if (body.top_p !== void 0 && body.top_p !== 0.99) {
5958
+ reject("top_p", "Omit top_p or use the default value 0.99.");
5959
+ }
5960
+ if (body.temperature !== void 0 && body.top_p !== void 0) {
5961
+ reject("temperature and top_p", "Send at most one of temperature and top_p.");
5962
+ }
5963
+ if (body.top_k !== void 0) {
5964
+ reject("top_k", "Omit top_k on this model.");
5965
+ }
5966
+ }
5967
+ if (capability.thinkingBinding === true && isFinalThinkingAdaptive(capability, body)) {
5968
+ const thinking = isRecord(body.thinking) ? body.thinking : { type: "adaptive" };
5969
+ const binding = thinking.block_binding;
5970
+ if (binding !== void 0 && (!isRecord(binding) || binding.prefix_mismatch_behavior !== "error" && binding.prefix_mismatch_behavior !== "drop_block")) {
5971
+ reject("thinking.block_binding", "Use prefix_mismatch_behavior: 'error' or 'drop_block'.");
5972
+ }
5973
+ if (hasReplayedThinking(body)) {
5974
+ body.thinking = {
5975
+ ...thinking,
5976
+ block_binding: binding ?? { prefix_mismatch_behavior: "drop_block" }
5977
+ };
5978
+ }
5979
+ }
5980
+ }
5584
5981
  /** Extract a human-readable message from an API error response body. */
5585
5982
  extractErrorMessage(body, status) {
5586
5983
  try {
@@ -5657,6 +6054,7 @@ ${jsonInstruction}` : jsonInstruction;
5657
6054
  if (options.providerOptions) {
5658
6055
  Object.assign(body, options.providerOptions);
5659
6056
  }
6057
+ this.validateAndBindFinalBody(body);
5660
6058
  return body;
5661
6059
  }
5662
6060
  /**
@@ -5813,6 +6211,7 @@ ${jsonInstruction}` : jsonInstruction;
5813
6211
  }
5814
6212
  }
5815
6213
  const normalized = json.usage ? normalizeAnthropicUsage(json.usage) : void 0;
6214
+ const reasoningContextReset = normalizeThinkingDrops(json.input_transformations);
5816
6215
  const effectiveModel = typeof json.model === "string" ? json.model : pricingContext.model;
5817
6216
  if (hasFallbackBoundary) {
5818
6217
  throw new Error(
@@ -5827,7 +6226,8 @@ ${jsonInstruction}` : jsonInstruction;
5827
6226
  usage: normalized?.usage,
5828
6227
  cost,
5829
6228
  costProvenance: tableEstimate(cost),
5830
- providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
6229
+ providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary ? { anthropicThinkingBlocks: thinkingBlocks } : void 0,
6230
+ ...reasoningContextReset ? { diagnostics: { reasoningContextReset } } : {}
5831
6231
  };
5832
6232
  }
5833
6233
  // ---------------------------------------------------------------------------
@@ -5849,6 +6249,7 @@ ${jsonInstruction}` : jsonInstruction;
5849
6249
  let hasFallbackBoundary = false;
5850
6250
  let hasFallbackIterationSignal = false;
5851
6251
  let refused = false;
6252
+ let reasoningContextReset;
5852
6253
  const finalizeUsage = () => {
5853
6254
  const normalized = rawUsage ? normalizeAnthropicUsage(rawUsage) : void 0;
5854
6255
  usage = normalized?.usage;
@@ -5861,7 +6262,8 @@ ${jsonInstruction}` : jsonInstruction;
5861
6262
  usage,
5862
6263
  cost,
5863
6264
  costProvenance: tableEstimate(cost),
5864
- providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
6265
+ providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0,
6266
+ ...reasoningContextReset ? { diagnostics: { reasoningContextReset } } : {}
5865
6267
  };
5866
6268
  };
5867
6269
  try {
@@ -5954,6 +6356,7 @@ ${jsonInstruction}` : jsonInstruction;
5954
6356
  rawUsage = mergeAnthropicUsage(rawUsage, event.message.usage);
5955
6357
  }
5956
6358
  if (typeof event.message?.model === "string") effectiveModel = event.message.model;
6359
+ reasoningContextReset = normalizeThinkingDrops(event.message?.input_transformations);
5957
6360
  const messageFallback = hasFallbackIteration(event.message?.usage?.iterations);
5958
6361
  if (isAnthropicResponseModifier(
5959
6362
  "inference_geo",
@@ -5970,6 +6373,10 @@ ${jsonInstruction}` : jsonInstruction;
5970
6373
  break;
5971
6374
  }
5972
6375
  case "message_delta": {
6376
+ const finalTransformations = event.input_transformations ?? event.delta?.input_transformations;
6377
+ if (finalTransformations !== void 0) {
6378
+ reasoningContextReset = normalizeThinkingDrops(finalTransformations);
6379
+ }
5973
6380
  if (event.usage) {
5974
6381
  rawUsage = mergeAnthropicUsage(rawUsage, event.usage);
5975
6382
  finalizeUsage();
@@ -6198,7 +6605,31 @@ var GEMINI_PRICING = {
6198
6605
  },
6199
6606
  "gemini-3.8-flash": { input: 75e-8, cached: 75e-9, output: 375e-8 }
6200
6607
  };
6608
+ var GEMINI_FLASH_SUCCESSOR_PRICING = {
6609
+ ...GEMINI_PRICING,
6610
+ "gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 },
6611
+ "gemini-3.7-flash": {
6612
+ input: 15e-7,
6613
+ cached: 15e-8,
6614
+ output: 75e-7,
6615
+ audioInput: 15e-7
6616
+ },
6617
+ "gemini-3.8-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
6618
+ };
6619
+ var GEMINI_FLASH_SUCCESSOR_START_MS = Date.UTC(2027, 0, 1);
6620
+ function geminiRatesAtDispatch(at) {
6621
+ return at < GEMINI_FLASH_SUCCESSOR_START_MS ? GEMINI_PRICING : GEMINI_FLASH_SUCCESSOR_PRICING;
6622
+ }
6201
6623
  var CANONICAL_GEMINI_BASE_URL = GEMINI_DEFAULT_BASE_URL;
6624
+ function geminiPricingTimingObserver(context, recorder) {
6625
+ return {
6626
+ ...recorder.observer,
6627
+ onDispatch: (attempt, at) => {
6628
+ context.rates = geminiRatesAtDispatch(at);
6629
+ recorder.observer.onDispatch?.(attempt, at);
6630
+ }
6631
+ };
6632
+ }
6202
6633
  function isValidTokenCount2(value) {
6203
6634
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
6204
6635
  }
@@ -6279,8 +6710,8 @@ function geminiInteractionErrorStatus(code) {
6279
6710
  function isEligibleGeminiPricing(context, hasDefinitiveStandardResponseTier, hasInvalidResponseTier) {
6280
6711
  return context.eligibleRequest && isStandardGeminiRequestTier(context.serviceTier) && hasDefinitiveStandardResponseTier && !hasInvalidResponseTier;
6281
6712
  }
6282
- function estimateGeminiCost(model, usage) {
6283
- const pricing = GEMINI_PRICING[model];
6713
+ function estimateGeminiCost(model, usage, rates) {
6714
+ const pricing = rates && Object.hasOwn(rates, model) ? rates[model] : void 0;
6284
6715
  const cached = usage.cachedTokens ?? 0;
6285
6716
  if (!pricing || !isValidTokenCount2(usage.inputTokens) || !isValidTokenCount2(usage.outputTokens) || !isValidTokenCount2(cached) || cached > usage.inputTokens) {
6286
6717
  return void 0;
@@ -6608,7 +7039,7 @@ var GeminiProvider = class {
6608
7039
  {
6609
7040
  governor: this.governorFor(pricingContext.model),
6610
7041
  provider: this.name,
6611
- timing: recorder.observer,
7042
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6612
7043
  admission: options.dispatchAdmission
6613
7044
  }
6614
7045
  );
@@ -6651,7 +7082,7 @@ var GeminiProvider = class {
6651
7082
  {
6652
7083
  governor: this.governorFor(pricingContext.model),
6653
7084
  provider: this.name,
6654
- timing: recorder.observer,
7085
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6655
7086
  admission: options.dispatchAdmission
6656
7087
  }
6657
7088
  );
@@ -6689,7 +7120,7 @@ var GeminiProvider = class {
6689
7120
  {
6690
7121
  governor: this.governorFor(pricingContext.model),
6691
7122
  provider: this.name,
6692
- timing: recorder.observer,
7123
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6693
7124
  admission: options.dispatchAdmission
6694
7125
  }
6695
7126
  );
@@ -6725,7 +7156,7 @@ var GeminiProvider = class {
6725
7156
  {
6726
7157
  governor: this.governorFor(pricingContext.model),
6727
7158
  provider: this.name,
6728
- timing: recorder.observer,
7159
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6729
7160
  admission: options.dispatchAdmission
6730
7161
  }
6731
7162
  );
@@ -6944,7 +7375,7 @@ var GeminiProvider = class {
6944
7375
  )) {
6945
7376
  return void 0;
6946
7377
  }
6947
- return estimateGeminiCost(effectiveModel, normalized.pricingUsage);
7378
+ return estimateGeminiCost(effectiveModel, normalized.pricingUsage, pricingContext.rates);
6948
7379
  }
6949
7380
  /**
6950
7381
  * Whether every model-produced step is text. A non-text reply part (image,
@@ -7529,7 +7960,7 @@ var GeminiProvider = class {
7529
7960
  pricingContext,
7530
7961
  isDefinitiveStandardGeminiResponseTier(json.usageMetadata?.serviceTier),
7531
7962
  json.usageMetadata?.serviceTier !== void 0 && !isDefinitiveStandardGeminiResponseTier(json.usageMetadata.serviceTier)
7532
- ) ? estimateGeminiCost(effectiveModel, normalized.pricingUsage) : void 0;
7963
+ ) ? estimateGeminiCost(effectiveModel, normalized.pricingUsage, pricingContext.rates) : void 0;
7533
7964
  const rawParts = candidate?.content?.parts;
7534
7965
  const providerMetadata = rawParts ? { geminiParts: rawParts } : void 0;
7535
7966
  return {
@@ -7620,7 +8051,7 @@ var GeminiProvider = class {
7620
8051
  pricingContext,
7621
8052
  hasDefinitiveStandardResponseTier,
7622
8053
  hasInvalidResponseTier
7623
- ) ? estimateGeminiCost(effectiveModel, normalizedUsage.pricingUsage) : void 0;
8054
+ ) ? estimateGeminiCost(effectiveModel, normalizedUsage.pricingUsage, pricingContext.rates) : void 0;
7624
8055
  yield {
7625
8056
  type: "done",
7626
8057
  usage: normalizedUsage?.usage,
@@ -9270,7 +9701,7 @@ var TranscriptionProviderRegistry = class {
9270
9701
 
9271
9702
  // src/context.ts
9272
9703
  var import_node_async_hooks3 = require("async_hooks");
9273
- var import_node_crypto = require("crypto");
9704
+ var import_node_crypto2 = require("crypto");
9274
9705
  var import_zod2 = require("zod");
9275
9706
 
9276
9707
  // src/input.ts
@@ -9533,6 +9964,95 @@ function sessionHistoryForAsk(history, normalizedInput) {
9533
9964
  return snapshot;
9534
9965
  }
9535
9966
 
9967
+ // src/compaction.ts
9968
+ var import_node_crypto = require("crypto");
9969
+ var SUMMARY_SYSTEM_PROMPT = "Summarize the following conversation concisely, preserving key facts, decisions, and context needed for continuing the conversation.";
9970
+ var SUMMARY_MAX_TOKENS = 1024;
9971
+ function buildSummaryPrompt(messages, previousSummary) {
9972
+ return [
9973
+ ...previousSummary ? [`Previous conversation summary: ${previousSummary}`] : [],
9974
+ ...messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`)
9975
+ ].join("\n");
9976
+ }
9977
+ function requestSummary(provider, options) {
9978
+ return provider.chat(
9979
+ [
9980
+ { role: "system", content: SUMMARY_SYSTEM_PROMPT },
9981
+ { role: "user", content: options.prompt }
9982
+ ],
9983
+ {
9984
+ model: options.model,
9985
+ maxTokens: SUMMARY_MAX_TOKENS,
9986
+ ...options.signal ? { signal: options.signal } : {}
9987
+ }
9988
+ );
9989
+ }
9990
+ function summaryContextMessage(summary) {
9991
+ return {
9992
+ role: "system",
9993
+ origin: "runtime",
9994
+ content: `Summary of earlier conversation:
9995
+ ${summary}`
9996
+ };
9997
+ }
9998
+ function withoutAnthropicThinking(message) {
9999
+ const metadata = message.providerMetadata;
10000
+ if (!metadata || !Object.hasOwn(metadata, "anthropicThinkingBlocks")) return message;
10001
+ const retainedMetadata = { ...metadata };
10002
+ delete retainedMetadata.anthropicThinkingBlocks;
10003
+ return {
10004
+ ...message,
10005
+ ...Object.keys(retainedMetadata).length > 0 ? { providerMetadata: retainedMetadata } : { providerMetadata: void 0 }
10006
+ };
10007
+ }
10008
+ function anthropicThinkingBlockCount(message) {
10009
+ const blocks = message.providerMetadata?.anthropicThinkingBlocks;
10010
+ if (!Array.isArray(blocks)) return 0;
10011
+ return blocks.filter(
10012
+ (block) => block !== null && typeof block === "object" && (block.type === "thinking" || block.type === "redacted_thinking")
10013
+ ).length;
10014
+ }
10015
+ function askSummaryMetaKey(agentName) {
10016
+ return `askSummary:${agentName}`;
10017
+ }
10018
+ function isCount(value) {
10019
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
10020
+ }
10021
+ function parseAskSummaryRecord(value) {
10022
+ if (!value || typeof value !== "object") return void 0;
10023
+ const record = value;
10024
+ 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") {
10025
+ return void 0;
10026
+ }
10027
+ return {
10028
+ summary: record.summary,
10029
+ coveredCount: record.coveredCount,
10030
+ prefixHash: record.prefixHash,
10031
+ ...record.sourceSummary !== void 0 ? { sourceSummary: record.sourceSummary } : {},
10032
+ summaryModelUri: record.summaryModelUri,
10033
+ invalidatedThinkingThrough: record.invalidatedThinkingThrough
10034
+ };
10035
+ }
10036
+ function isPlainObject(value) {
10037
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
10038
+ const proto = Object.getPrototypeOf(value);
10039
+ return proto === Object.prototype || proto === null;
10040
+ }
10041
+ function summaryPrefixHash(history, coveredCount) {
10042
+ let canonical;
10043
+ try {
10044
+ canonical = JSON.stringify(
10045
+ history.slice(0, coveredCount),
10046
+ (_key, value) => isPlainObject(value) ? Object.fromEntries(
10047
+ Object.keys(value).sort().map((key) => [key, value[key]])
10048
+ ) : value
10049
+ );
10050
+ } catch {
10051
+ return void 0;
10052
+ }
10053
+ return (0, import_node_crypto.createHash)("sha256").update(canonical).digest("hex");
10054
+ }
10055
+
9536
10056
  // src/partial-json.ts
9537
10057
  var MAX_DEPTH = 256;
9538
10058
  function parsePartialJson(input) {
@@ -11169,9 +11689,9 @@ var LEGACY_REDACTION_RULES = {
11169
11689
  // which is user content or PII (field paths are schema-shape, preserved like
11170
11690
  // `string_delta.path`). Passthrough, as an explicit reviewed decision.
11171
11691
  schema_diagnostic: passthrough,
11172
- // `provider_diagnostic` carries only provider capability metadata — a provider
11173
- // name, a model id, and two effort level strings. No application data or user
11174
- // content ever reaches it. Passthrough, as an explicit reviewed decision.
11692
+ // `provider_diagnostic` carries only provider/model metadata, effort levels,
11693
+ // and bounded dropped-thinking reason counts. Raw transformation paths and
11694
+ // signed thinking never enter this event. Passthrough is reviewed here.
11175
11695
  provider_diagnostic: passthrough,
11176
11696
  log: (e) => {
11177
11697
  if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
@@ -13176,7 +13696,14 @@ var WorkflowContext = class _WorkflowContext {
13176
13696
  * suppresses late branch events after bounded finalization. */
13177
13697
  workflowLifecycleState;
13178
13698
  signal;
13179
- summaryCache;
13699
+ /** Durable summary of messages removed by Session.history.maxMessages. */
13700
+ sessionSummary;
13701
+ /** Per-agent `maxContext` summaries, keyed by agent name. `null` records a
13702
+ * store lookup that found nothing. Mirrored to session metadata
13703
+ * (`askSummary:<agent>`) when this execution belongs to a session, so a
13704
+ * later execution can reuse the same exact-prefix boundary. Never the
13705
+ * durable `sessionSummary`. */
13706
+ askSummaries = /* @__PURE__ */ new Map();
13180
13707
  /** Consecutive provider calls with promptCache on that wrote to the cache
13181
13708
  * without reading from it (prefix changes per call), and consecutive calls
13182
13709
  * with no cache activity at all (prefix below the model's minimum, which
@@ -13352,7 +13879,7 @@ var WorkflowContext = class _WorkflowContext {
13352
13879
  endEmitted: false
13353
13880
  };
13354
13881
  if (init.metadata?.summaryCache) {
13355
- this.summaryCache = init.metadata.summaryCache;
13882
+ this.sessionSummary = init.metadata.summaryCache;
13356
13883
  }
13357
13884
  }
13358
13885
  /**
@@ -13680,7 +14207,7 @@ var WorkflowContext = class _WorkflowContext {
13680
14207
  this._autoCheckpointName("ask", agentName),
13681
14208
  async () => {
13682
14209
  const parentFrame = askStorage.getStore();
13683
- const askId = (0, import_node_crypto.randomUUID)();
14210
+ const askId = (0, import_node_crypto2.randomUUID)();
13684
14211
  const depth = (parentFrame?.depth ?? -1) + 1;
13685
14212
  const stepRef = parentFrame?.stepRef ?? this.stepRefRoot;
13686
14213
  const frame = {
@@ -13977,6 +14504,7 @@ var WorkflowContext = class _WorkflowContext {
13977
14504
  if (systemPrompt) {
13978
14505
  messages.push({ role: "system", content: systemPrompt });
13979
14506
  }
14507
+ const cachedSummaryMessage = this.sessionSummary ? summaryContextMessage(this.sessionSummary) : void 0;
13980
14508
  const maxContext = agent2._config.maxContext;
13981
14509
  if (maxContext && sessionHistory.length > 0) {
13982
14510
  const reserveTokens = this.config.contextManagement?.reserveTokens ?? 2e3;
@@ -14001,22 +14529,44 @@ var WorkflowContext = class _WorkflowContext {
14001
14529
  }
14002
14530
  });
14003
14531
  }
14004
- if (historyEstimate.tokens > availableForHistory) {
14005
- const summarizedMessages = await this.summarizeHistory(
14532
+ const summaryTokens = cachedSummaryMessage ? estimateMessagesTokens([cachedSummaryMessage]).tokens : 0;
14533
+ if (historyEstimate.tokens + summaryTokens > availableForHistory) {
14534
+ const projection = await this.summarizeHistory(
14535
+ agent2._name,
14536
+ modelUri,
14006
14537
  provider,
14007
14538
  model,
14008
14539
  sessionHistory,
14009
14540
  availableForHistory
14010
14541
  );
14011
- for (const msg of summarizedMessages) {
14542
+ if (this.budgetContext?.exceeded && this.budgetContext.policy !== "warn") {
14543
+ const { limit, totalCost: spent, policy } = this.budgetContext;
14544
+ throw new BudgetExceededError(limit, spent, policy);
14545
+ }
14546
+ if (projection.removedThinkingBlocks > 0) {
14547
+ this.emitEvent({
14548
+ type: "provider_diagnostic",
14549
+ agent: agent2._name,
14550
+ data: {
14551
+ kind: "reasoning_context_reset",
14552
+ ...provider.name ? { provider: provider.name } : {},
14553
+ model: typeof providerOptions?.model === "string" ? providerOptions.model : model,
14554
+ droppedBlocks: projection.removedThinkingBlocks,
14555
+ reasons: { client_prefix_rewrite: projection.removedThinkingBlocks }
14556
+ }
14557
+ });
14558
+ }
14559
+ for (const msg of projection.messages) {
14012
14560
  messages.push(msg);
14013
14561
  }
14014
14562
  } else {
14563
+ if (cachedSummaryMessage) messages.push(cachedSummaryMessage);
14015
14564
  for (const msg of sessionHistory) {
14016
14565
  messages.push(msg);
14017
14566
  }
14018
14567
  }
14019
14568
  } else {
14569
+ if (cachedSummaryMessage) messages.push(cachedSummaryMessage);
14020
14570
  for (const msg of sessionHistory) {
14021
14571
  messages.push(msg);
14022
14572
  }
@@ -14117,6 +14667,7 @@ ${appendedSchemaText}`;
14117
14667
  }
14118
14668
  const startTime = Date.now();
14119
14669
  const askClock = currentAskClock();
14670
+ const pausedAtStartMs = askClock ? pausedAskClockMs(askClock, startTime) : 0;
14120
14671
  const timingTotals = { turns: 0, queuedMs: 0, retryMs: 0, wireMs: 0 };
14121
14672
  const currentMessages = [...messages];
14122
14673
  let turns = 0;
@@ -14134,7 +14685,7 @@ ${appendedSchemaText}`;
14134
14685
  this.currentSignal?.throwIfAborted();
14135
14686
  const now = Date.now();
14136
14687
  const elapsedMs = now - startTime;
14137
- const pausedMs = askClock ? pausedAskClockMs(askClock, now) : 0;
14688
+ const pausedMs = askClock ? Math.max(0, pausedAskClockMs(askClock, now) - pausedAtStartMs) : 0;
14138
14689
  const chargedMs = Math.max(0, elapsedMs - Math.min(pausedMs, Math.max(0, elapsedMs)));
14139
14690
  if (chargedMs > timeoutMs) {
14140
14691
  throw new TimeoutError(
@@ -14389,6 +14940,7 @@ ${appendedSchemaText}`;
14389
14940
  usage: chunk.usage,
14390
14941
  cost: chunk.cost,
14391
14942
  providerMetadata: chunk.providerMetadata,
14943
+ diagnostics: chunk.diagnostics,
14392
14944
  timing: chunk.timing
14393
14945
  };
14394
14946
  }
@@ -14506,6 +15058,34 @@ ${appendedSchemaText}`;
14506
15058
  timingTotals.retryMs += response.timing.retryMs;
14507
15059
  timingTotals.wireMs += response.timing.wireMs;
14508
15060
  }
15061
+ const reasoningReset = response.diagnostics?.reasoningContextReset;
15062
+ if (reasoningReset && Number.isSafeInteger(reasoningReset.droppedBlocks) && reasoningReset.droppedBlocks > 0) {
15063
+ const reasons = {};
15064
+ for (const reason of [
15065
+ "prefix_binding_mismatch",
15066
+ "model_binding_mismatch",
15067
+ "organization_binding_mismatch",
15068
+ "end_user_binding_mismatch",
15069
+ "client_prefix_rewrite",
15070
+ "other"
15071
+ ]) {
15072
+ const count = reasoningReset.reasons?.[reason];
15073
+ if (typeof count === "number" && Number.isSafeInteger(count) && count > 0) {
15074
+ reasons[reason] = count;
15075
+ }
15076
+ }
15077
+ this.emitEvent({
15078
+ type: "provider_diagnostic",
15079
+ agent: agent2._name,
15080
+ data: {
15081
+ kind: "reasoning_context_reset",
15082
+ ...provider.name ? { provider: provider.name } : {},
15083
+ model: typeof providerOptions?.model === "string" ? providerOptions.model : model,
15084
+ droppedBlocks: reasoningReset.droppedBlocks,
15085
+ reasons
15086
+ }
15087
+ });
15088
+ }
14509
15089
  this.emitEvent({
14510
15090
  type: "agent_call_end",
14511
15091
  agent: agent2._name,
@@ -14556,7 +15136,7 @@ ${appendedSchemaText}`;
14556
15136
  const sourceFrame = askStorage.getStore();
14557
15137
  const handoffFromAskId = sourceFrame?.askId ?? this.executionId;
14558
15138
  const handoffSourceDepth = sourceFrame?.depth ?? 0;
14559
- const handoffToAskId = (0, import_node_crypto.randomUUID)();
15139
+ const handoffToAskId = (0, import_node_crypto2.randomUUID)();
14560
15140
  const handoffTargetDepth = handoffSourceDepth + 1;
14561
15141
  const targetFrame = {
14562
15142
  askId: handoffToAskId,
@@ -15248,31 +15828,65 @@ ${appendedSchemaText}`;
15248
15828
  }
15249
15829
  return defs;
15250
15830
  }
15831
+ /** Session id for session-scoped ask-summary persistence, when this
15832
+ * execution belongs to a session and has a store to persist into. */
15833
+ askSummarySessionId() {
15834
+ const sessionId = this.metadata?.sessionId;
15835
+ if (this.metadata?.sessionPersist === false) return void 0;
15836
+ return this.stateStore && typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
15837
+ }
15838
+ /** This agent's ask summary: the in-memory copy first, else the session's
15839
+ * persisted record (read once per execution). */
15840
+ async loadAskSummary(agentName) {
15841
+ if (this.askSummaries.has(agentName)) return this.askSummaries.get(agentName) ?? void 0;
15842
+ const sessionId = this.askSummarySessionId();
15843
+ const stored = sessionId ? parseAskSummaryRecord(
15844
+ await this.stateStore.getSessionMeta(sessionId, askSummaryMetaKey(agentName))
15845
+ ) : void 0;
15846
+ this.askSummaries.set(agentName, stored ?? null);
15847
+ return stored;
15848
+ }
15849
+ async saveAskSummary(agentName, record) {
15850
+ this.askSummaries.set(agentName, record);
15851
+ const sessionId = this.askSummarySessionId();
15852
+ if (!sessionId) return;
15853
+ const key = askSummaryMetaKey(agentName);
15854
+ try {
15855
+ await this.stateStore.saveSessionMeta(sessionId, key, record);
15856
+ } catch (error) {
15857
+ this.emitEvent({
15858
+ type: "log",
15859
+ agent: agentName,
15860
+ data: {
15861
+ warning: `Could not persist ask summary '${key}' for session '${sessionId}'; later executions will regenerate it. ` + (error instanceof Error ? error.message : String(error))
15862
+ }
15863
+ });
15864
+ }
15865
+ }
15251
15866
  /**
15252
- * Summarize old messages to fit within context window.
15253
- * Keeps recent messages intact, summarizes older ones.
15867
+ * Project `history` into `availableTokens` for one agent's request: a summary
15868
+ * of an exact covered prefix followed by every later message. Reuses the
15869
+ * agent's cached boundary (in memory or persisted for the session) while the
15870
+ * covered prefix, durable session summary, and summary model are unchanged
15871
+ * and the whole tail fits; otherwise generates a new summary. Also reports how
15872
+ * many Anthropic thinking blocks the projection removed from carried turns.
15254
15873
  */
15255
- async summarizeHistory(provider, model, history, availableTokens) {
15256
- if (this.summaryCache) {
15257
- const summaryMsg2 = {
15258
- role: "system",
15259
- origin: "runtime",
15260
- content: `Summary of earlier conversation:
15261
- ${this.summaryCache}`
15262
- };
15263
- const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg2.content)) + 4;
15264
- const remaining = availableTokens - summaryTokens;
15265
- let recentTokens2 = 0;
15266
- let splitIdx2 = history.length;
15267
- for (let i = history.length - 1; i >= 0; i--) {
15268
- const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
15269
- if (recentTokens2 + msgTokens > remaining) break;
15270
- recentTokens2 += msgTokens;
15271
- splitIdx2 = i;
15272
- }
15273
- splitIdx2 = nextUserTurn(history, splitIdx2) ?? history.length;
15274
- if (splitIdx2 < history.length) {
15275
- return [summaryMsg2, ...history.slice(splitIdx2)];
15874
+ async summarizeHistory(agentName, modelUri, provider, model, history, availableTokens) {
15875
+ const summaryModelUri = this.config.contextManagement?.summaryModel ?? modelUri;
15876
+ const cache = await this.loadAskSummary(agentName);
15877
+ if (cache && cache.sourceSummary === this.sessionSummary && cache.summaryModelUri === summaryModelUri && cache.coveredCount <= history.length && cache.prefixHash === summaryPrefixHash(history, cache.coveredCount)) {
15878
+ const summaryMsg = summaryContextMessage(cache.summary);
15879
+ const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg.content)) + 4;
15880
+ const tail2 = history.slice(cache.coveredCount);
15881
+ const tailTokens = estimateMessagesTokens(tail2).tokens;
15882
+ if (summaryTokens + tailTokens <= availableTokens && (tail2.length === 0 || tail2[0].role === "user")) {
15883
+ let removedThinkingBlocks = 0;
15884
+ const projected = tail2.map((message, index) => {
15885
+ if (cache.coveredCount + index >= cache.invalidatedThinkingThrough) return message;
15886
+ removedThinkingBlocks += anthropicThinkingBlockCount(message);
15887
+ return withoutAnthropicThinking(message);
15888
+ });
15889
+ return { messages: [summaryMsg, ...projected], removedThinkingBlocks };
15276
15890
  }
15277
15891
  }
15278
15892
  let recentTokens = 0;
@@ -15297,42 +15911,103 @@ ${this.summaryCache}`
15297
15911
  } else {
15298
15912
  splitIdx = history.length;
15299
15913
  }
15300
- if (splitIdx === 0) return history;
15914
+ if (splitIdx === 0 && this.sessionSummary) splitIdx = history.length;
15915
+ if (splitIdx === 0) return { messages: history, removedThinkingBlocks: 0 };
15301
15916
  const oldMessages = history.slice(0, splitIdx);
15302
- const summaryModelUri = this.config.contextManagement?.summaryModel;
15917
+ const configuredSummaryModelUri = this.config.contextManagement?.summaryModel;
15303
15918
  let summaryProvider;
15304
15919
  let summaryModel;
15305
- if (summaryModelUri) {
15306
- const resolved = this.resolveProviderUri(summaryModelUri);
15920
+ if (configuredSummaryModelUri) {
15921
+ const resolved = this.resolveProviderUri(configuredSummaryModelUri);
15307
15922
  summaryProvider = resolved.provider;
15308
15923
  summaryModel = resolved.model;
15309
15924
  } else {
15310
15925
  summaryProvider = provider;
15311
15926
  summaryModel = model;
15312
15927
  }
15313
- const oldContent = oldMessages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n");
15314
- const summaryResponse = await summaryProvider.chat(
15315
- [
15316
- {
15317
- role: "system",
15318
- content: "Summarize the following conversation concisely, preserving key facts, decisions, and context needed for continuing the conversation."
15319
- },
15320
- { role: "user", content: oldContent }
15928
+ const summaryPrompt = buildSummaryPrompt(oldMessages, this.sessionSummary);
15929
+ const summaryStart = Date.now();
15930
+ this.emitEvent({
15931
+ type: "agent_call_start",
15932
+ agent: agentName,
15933
+ model: summaryModelUri,
15934
+ turn: 0,
15935
+ data: {
15936
+ purpose: "summary",
15937
+ prompt: summaryPrompt,
15938
+ system: SUMMARY_SYSTEM_PROMPT,
15939
+ params: { maxTokens: SUMMARY_MAX_TOKENS },
15940
+ turn: 0
15941
+ }
15942
+ });
15943
+ let summaryResponse;
15944
+ try {
15945
+ this.currentSignal?.throwIfAborted();
15946
+ summaryResponse = await requestSummary(summaryProvider, {
15947
+ model: summaryModel,
15948
+ prompt: summaryPrompt,
15949
+ signal: this.currentSignal
15950
+ });
15951
+ } catch (error) {
15952
+ const providerError = error instanceof ProviderError ? error : void 0;
15953
+ this.emitEvent({
15954
+ type: "agent_call_end",
15955
+ agent: agentName,
15956
+ model: summaryModelUri,
15957
+ duration: Date.now() - summaryStart,
15958
+ ...providerError?.timing ? { timing: providerError.timing } : {},
15959
+ data: {
15960
+ purpose: "summary",
15961
+ response: "",
15962
+ turn: 0,
15963
+ // Provider errors may echo the summarized history, so the event
15964
+ // uses a fixed projection even when trace redaction is disabled.
15965
+ error: "Context-management summary call failed",
15966
+ ...providerError ? { status: providerError.status, retryable: providerError.retryable } : {}
15967
+ }
15968
+ });
15969
+ throw error;
15970
+ }
15971
+ if (isUsableCost2(summaryResponse.cost)) this._accumulateBudgetCost(summaryResponse.cost);
15972
+ this.emitEvent({
15973
+ type: "agent_call_end",
15974
+ agent: agentName,
15975
+ model: summaryModelUri,
15976
+ cost: summaryResponse.cost,
15977
+ ...!isUsableCost2(summaryResponse.cost) ? { unpriced: true } : {},
15978
+ tokens: summaryResponse.usage ? {
15979
+ input: summaryResponse.usage.prompt_tokens,
15980
+ output: summaryResponse.usage.completion_tokens,
15981
+ reasoning: summaryResponse.usage.reasoning_tokens,
15982
+ cached: summaryResponse.usage.cached_tokens,
15983
+ cacheWrite: summaryResponse.usage.cache_write_tokens
15984
+ } : void 0,
15985
+ duration: Date.now() - summaryStart,
15986
+ ...summaryResponse.timing ? { timing: summaryResponse.timing } : {},
15987
+ data: { purpose: "summary", response: summaryResponse.content, turn: 0 }
15988
+ });
15989
+ const prefixHash = summaryPrefixHash(history, splitIdx);
15990
+ if (prefixHash) {
15991
+ await this.saveAskSummary(agentName, {
15992
+ summary: summaryResponse.content,
15993
+ coveredCount: splitIdx,
15994
+ prefixHash,
15995
+ ...this.sessionSummary !== void 0 ? { sourceSummary: this.sessionSummary } : {},
15996
+ summaryModelUri,
15997
+ invalidatedThinkingThrough: history.length
15998
+ });
15999
+ }
16000
+ const tail = history.slice(splitIdx);
16001
+ return {
16002
+ messages: [
16003
+ summaryContextMessage(summaryResponse.content),
16004
+ ...tail.map(withoutAnthropicThinking)
15321
16005
  ],
15322
- { model: summaryModel, maxTokens: 1024, signal: this.currentSignal }
15323
- );
15324
- this.summaryCache = summaryResponse.content;
15325
- const sessionId = this.metadata?.sessionId;
15326
- if (sessionId && this.stateStore) {
15327
- await this.stateStore.saveSessionMeta(sessionId, "summaryCache", this.summaryCache);
15328
- }
15329
- const summaryMsg = {
15330
- role: "system",
15331
- origin: "runtime",
15332
- content: `Summary of earlier conversation:
15333
- ${summaryResponse.content}`
16006
+ removedThinkingBlocks: tail.reduce(
16007
+ (count, message) => count + anthropicThinkingBlockCount(message),
16008
+ 0
16009
+ )
15334
16010
  };
15335
- return [summaryMsg, ...history.slice(splitIdx)];
15336
16011
  }
15337
16012
  // ── ctx.checkpoint() ────────────────────────────────────────────────
15338
16013
  /**
@@ -16590,7 +17265,7 @@ ${summaryResponse.content}`
16590
17265
  * This operation never enters the chat history or chat-provider path. */
16591
17266
  async transcribe(request) {
16592
17267
  const startedAt = Date.now();
16593
- const transcriptionId = (0, import_node_crypto.randomUUID)();
17268
+ const transcriptionId = (0, import_node_crypto2.randomUUID)();
16594
17269
  const signal = this.currentSignal;
16595
17270
  const rawAudio = request && typeof request === "object" && "audio" in request ? request.audio : void 0;
16596
17271
  const source = rawAudio?.type;
@@ -16979,7 +17654,7 @@ ${summaryResponse.content}`
16979
17654
  };
16980
17655
 
16981
17656
  // src/agent.ts
16982
- var import_node_crypto2 = require("crypto");
17657
+ var import_node_crypto3 = require("crypto");
16983
17658
  var agentCounter = 0;
16984
17659
  function agent(config) {
16985
17660
  agentCounter++;
@@ -17000,7 +17675,7 @@ function agent(config) {
17000
17675
  const transcriptionRegistry = new TranscriptionProviderRegistry();
17001
17676
  const ctx = new WorkflowContext({
17002
17677
  input: prompt,
17003
- executionId: (0, import_node_crypto2.randomUUID)(),
17678
+ executionId: (0, import_node_crypto3.randomUUID)(),
17004
17679
  config: {},
17005
17680
  providerRegistry: registry2,
17006
17681
  transcriptionProviderRegistry: transcriptionRegistry
@@ -17025,7 +17700,7 @@ function workflow(config) {
17025
17700
 
17026
17701
  // src/runtime.ts
17027
17702
  var import_node_events2 = require("events");
17028
- var import_node_crypto4 = require("crypto");
17703
+ var import_node_crypto5 = require("crypto");
17029
17704
  var import_node_async_hooks4 = require("async_hooks");
17030
17705
  var import_node_fs3 = require("fs");
17031
17706
  var import_node_path3 = require("path");
@@ -17741,17 +18416,17 @@ var Session = class _Session {
17741
18416
  throw new Error("SessionOptions.history.summaryModel is required when summarize is true");
17742
18417
  }
17743
18418
  const messagesToDrop = history.slice(0, history.length - maxMessages);
17744
- const toSummarize = cachedSummary ? [
17745
- { role: "system", content: `Previous conversation summary: ${cachedSummary}` },
17746
- ...messagesToDrop
17747
- ] : messagesToDrop;
17748
- const summary = await this.runtime.summarizeMessages(toSummarize, summaryModel);
18419
+ const summary = await this.runtime.summarizeMessages(
18420
+ messagesToDrop,
18421
+ summaryModel,
18422
+ cachedSummary ? { previousSummary: cachedSummary } : void 0
18423
+ );
17749
18424
  await this.store.saveSessionMeta(this.sessionId, "summaryCache", summary);
17750
18425
  cachedSummary = summary;
17751
18426
  }
17752
18427
  const trimmed = history.slice(-maxMessages);
17753
18428
  history.length = 0;
17754
- history.push(...trimmed);
18429
+ history.push(...trimmed.map(withoutAnthropicThinking));
17755
18430
  }
17756
18431
  const userMessage = { role: "user", content: preparedInput.content };
17757
18432
  history.push(userMessage);
@@ -17761,7 +18436,10 @@ var Session = class _Session {
17761
18436
  const metadata = {
17762
18437
  sessionId: this.sessionId,
17763
18438
  sessionHistory: history,
17764
- ...cachedSummary ? { summaryCache: cachedSummary } : {}
18439
+ ...cachedSummary ? { summaryCache: cachedSummary } : {},
18440
+ // An opted-out session must leave nothing at rest, including the
18441
+ // agent-scoped ask-summary records the runtime would otherwise cache.
18442
+ ...this.options.persist === false ? { sessionPersist: false } : {}
17765
18443
  };
17766
18444
  return { history, metadata };
17767
18445
  }
@@ -18678,7 +19356,7 @@ function warnMissingMemoryMethodsOnce(stateStore) {
18678
19356
  var import_node_fs2 = require("fs");
18679
19357
  var import_promises = require("fs/promises");
18680
19358
  var import_node_readline = require("readline");
18681
- var import_node_crypto3 = require("crypto");
19359
+ var import_node_crypto4 = require("crypto");
18682
19360
  var import_node_path2 = __toESM(require("path"), 1);
18683
19361
  var MANIFEST_FILE = "manifest.json";
18684
19362
  var RECORDS_FILE = "records.jsonl";
@@ -18743,7 +19421,7 @@ var FileDiagnosticArtifactStore = class {
18743
19421
  async writeManifest(manifest) {
18744
19422
  const dir = this.dir(manifest.artifactId);
18745
19423
  await (0, import_promises.mkdir)(dir, { recursive: true });
18746
- const temp = import_node_path2.default.join(dir, `${MANIFEST_FILE}.${(0, import_node_crypto3.randomUUID)()}.tmp`);
19424
+ const temp = import_node_path2.default.join(dir, `${MANIFEST_FILE}.${(0, import_node_crypto4.randomUUID)()}.tmp`);
18747
19425
  await (0, import_promises.writeFile)(temp, JSON.stringify(manifest), "utf-8");
18748
19426
  await (0, import_promises.rename)(temp, import_node_path2.default.join(dir, MANIFEST_FILE));
18749
19427
  }
@@ -18780,7 +19458,7 @@ var FileDiagnosticArtifactStore = class {
18780
19458
  });
18781
19459
  }
18782
19460
  async stage(owner, opts) {
18783
- const artifactId = `art_${(0, import_node_crypto3.randomUUID)()}`;
19461
+ const artifactId = `art_${(0, import_node_crypto4.randomUUID)()}`;
18784
19462
  const now = Date.now();
18785
19463
  await this.writeManifest({
18786
19464
  version: 1,
@@ -19981,7 +20659,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
19981
20659
  }
19982
20660
  try {
19983
20661
  await this.saveEvalResult({
19984
- id: resultObj.id ?? (0, import_node_crypto4.randomUUID)(),
20662
+ id: resultObj.id ?? (0, import_node_crypto5.randomUUID)(),
19985
20663
  eval: name,
19986
20664
  timestamp: Date.now(),
19987
20665
  data: structuredClone(result)
@@ -20039,7 +20717,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
20039
20717
  */
20040
20718
  createContext(options) {
20041
20719
  assertNoRemovedObservationCallbacks(options);
20042
- const executionId = (0, import_node_crypto4.randomUUID)();
20720
+ const executionId = (0, import_node_crypto5.randomUUID)();
20043
20721
  const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
20044
20722
  this.registerWithCostScope(executionId);
20045
20723
  return new WorkflowContext({
@@ -20118,7 +20796,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
20118
20796
  }
20119
20797
  const validated = workflow2.inputSchema.parse(input);
20120
20798
  const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(options?.branchDrainTimeoutMs);
20121
- const executionId = (0, import_node_crypto4.randomUUID)();
20799
+ const executionId = (0, import_node_crypto5.randomUUID)();
20122
20800
  const controller = new AbortController();
20123
20801
  this.abortControllers.set(executionId, controller);
20124
20802
  const cleanupAbortForwarder = forwardAbortSignal(options?.signal, controller);
@@ -20233,7 +20911,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
20233
20911
  const controller = new AbortController();
20234
20912
  axlStream.on("close", () => controller.abort());
20235
20913
  const cleanupAbortForwarder = forwardAbortSignal(options?.signal, controller);
20236
- const executionId = (0, import_node_crypto4.randomUUID)();
20914
+ const executionId = (0, import_node_crypto5.randomUUID)();
20237
20915
  this.abortControllers.set(executionId, controller);
20238
20916
  let execInfo;
20239
20917
  let wfCtx;
@@ -20834,22 +21512,17 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
20834
21512
  /**
20835
21513
  * Summarize a list of chat messages into a concise summary string.
20836
21514
  * Used by Session to summarize dropped messages when history.summarize is enabled.
21515
+ * `previousSummary` is folded in so a rolling summary never loses what it
21516
+ * alone still carries. Shares its prompt and provider call with the
21517
+ * `AgentConfig.maxContext` summarizer (see `compaction.ts`). It runs outside
21518
+ * any workflow execution, so it emits no ask-scoped events.
20837
21519
  */
20838
- async summarizeMessages(messages, modelUri) {
21520
+ async summarizeMessages(messages, modelUri, options) {
20839
21521
  const { provider, model } = this.resolveProvider(modelUri);
20840
- const response = await provider.chat(
20841
- [
20842
- {
20843
- role: "system",
20844
- content: "Summarize the following conversation concisely, preserving key facts, decisions, and context needed for continuing the conversation."
20845
- },
20846
- {
20847
- role: "user",
20848
- content: messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n")
20849
- }
20850
- ],
20851
- { model, maxTokens: 1024 }
20852
- );
21522
+ const response = await requestSummary(provider, {
21523
+ model,
21524
+ prompt: buildSummaryPrompt(messages, options?.previousSummary)
21525
+ });
20853
21526
  return response.content;
20854
21527
  }
20855
21528
  /** Get the state store (for testing and advanced use cases). */
@@ -22280,6 +22953,7 @@ function cosineSimilarity2(a, b) {
22280
22953
  TranscriptionProviderRegistry,
22281
22954
  UnsupportedEventSchemaVersionError,
22282
22955
  UnsupportedModelInputError,
22956
+ UnsupportedModelOptionError,
22283
22957
  UnsupportedTranscriptionInputError,
22284
22958
  VLLM_PROFILE,
22285
22959
  ValidationError,