@axlsdk/axl 0.24.3 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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);
@@ -425,7 +443,7 @@ var TimeoutError = class extends AxlError {
425
443
  constructor(operation, timeoutMs, breakdown, agent2) {
426
444
  super(
427
445
  "TIMEOUT",
428
- `${operation} exceeded timeout of ${timeoutMs}ms` + (breakdown ? ` (elapsed ${breakdown.elapsedMs}ms: queued ${breakdown.queuedMs}ms, retries ${breakdown.retryMs}ms, wire ${breakdown.wireMs}ms, other ${breakdown.otherMs}ms)` : "") + (agent2 ? ` for agent '${agent2}'` : "")
446
+ `${operation} exceeded timeout of ${timeoutMs}ms` + (breakdown ? ` (elapsed ${breakdown.elapsedMs}ms: queued ${breakdown.queuedMs}ms, retries ${breakdown.retryMs}ms, wire ${breakdown.wireMs}ms, other ${breakdown.otherMs}ms, charged ${breakdown.chargedMs}ms)` : "") + (agent2 ? ` for agent '${agent2}'` : "")
429
447
  );
430
448
  this.name = "TimeoutError";
431
449
  if (breakdown) this.breakdown = breakdown;
@@ -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.24.3" : "unknown"
584
+ version: true ? "0.25.1" : "unknown"
567
585
  };
568
586
  function warnCrossCopy(other, joined) {
569
587
  if (other === thisCopy) return;
@@ -1547,6 +1565,41 @@ var OPENAI_DEFAULT_BASE_URL = "https://api.openai.com/v1";
1547
1565
  var ANTHROPIC_DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
1548
1566
  var GEMINI_DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
1549
1567
 
1568
+ // src/ask-clock.ts
1569
+ var import_node_async_hooks2 = require("async_hooks");
1570
+ var askClockStorage = new import_node_async_hooks2.AsyncLocalStorage();
1571
+ function runWithAskClock(fn) {
1572
+ const tracker = { pausedMs: 0, activePauses: 0 };
1573
+ const enclosing = askClockStorage.getStore() ?? [];
1574
+ return askClockStorage.run([...enclosing, tracker], fn);
1575
+ }
1576
+ function currentAskClock() {
1577
+ return askClockStorage.getStore()?.at(-1);
1578
+ }
1579
+ function pausedAskClockMs(tracker, now = Date.now()) {
1580
+ return tracker.pausedMs + (tracker.activePauses > 0 && tracker.pauseStartedAt !== void 0 ? now - tracker.pauseStartedAt : 0);
1581
+ }
1582
+ async function pauseAskClocks(wait) {
1583
+ const trackers = askClockStorage.getStore();
1584
+ if (!trackers || trackers.length === 0) return wait();
1585
+ const startedAt = Date.now();
1586
+ for (const tracker of trackers) {
1587
+ if (tracker.activePauses++ === 0) tracker.pauseStartedAt = startedAt;
1588
+ }
1589
+ try {
1590
+ return await wait();
1591
+ } finally {
1592
+ const finishedAt = Date.now();
1593
+ for (const tracker of trackers) {
1594
+ tracker.activePauses--;
1595
+ if (tracker.activePauses === 0 && tracker.pauseStartedAt !== void 0) {
1596
+ tracker.pausedMs += finishedAt - tracker.pauseStartedAt;
1597
+ tracker.pauseStartedAt = void 0;
1598
+ }
1599
+ }
1600
+ }
1601
+ }
1602
+
1550
1603
  // src/providers/rate-limiter.ts
1551
1604
  function sanitizeRateLimitConfig(config) {
1552
1605
  const limits = {};
@@ -2609,7 +2662,7 @@ async function fetchWithRetry(input, init, opts) {
2609
2662
  let headersAt = 0;
2610
2663
  const waitSelfImposed = async (wait) => {
2611
2664
  const start = Date.now();
2612
- await wait();
2665
+ await pauseAskClocks(wait);
2613
2666
  const waited = Date.now() - start;
2614
2667
  queuedMs += waited;
2615
2668
  if (dispatches > 0) queuedAfterFirstDispatchMs += waited;
@@ -3401,8 +3454,12 @@ var OpenAICompatibleProvider = class {
3401
3454
  for (const key of forbidden) {
3402
3455
  if (!userKeys.includes(key)) delete body[key];
3403
3456
  }
3457
+ this.validateFinalRequestBody(body);
3404
3458
  return body;
3405
3459
  }
3460
+ /** Native subclasses may reject model-invalid fields after providerOptions merge. */
3461
+ validateFinalRequestBody(_body) {
3462
+ }
3406
3463
  formatMessage(msg, model) {
3407
3464
  const modalities = this.inputModalities(model);
3408
3465
  const out = {
@@ -3810,6 +3867,24 @@ var withLongContext = (short) => ({
3810
3867
  output: short.output * 1.5
3811
3868
  });
3812
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
+ },
3813
3888
  {
3814
3889
  aliases: ["gpt-5.6", "gpt-5.6-sol"],
3815
3890
  snapshotBase: "gpt-5.6-sol",
@@ -4140,7 +4215,7 @@ function estimateDirectOpenAICost(model, usage, context) {
4140
4215
  const audioOutput = audio_output_tokens ?? 0;
4141
4216
  if (![prompt_tokens, completion_tokens, cached, cacheWrite, audioInput, audioOutput].every(
4142
4217
  (count) => Number.isSafeInteger(count) && count >= 0
4143
- ) || 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) {
4144
4219
  return void 0;
4145
4220
  }
4146
4221
  if (audioInput > 0 && entry.audio === void 0 || audioOutput > 0 && entry.audio?.output === void 0) {
@@ -4165,57 +4240,206 @@ function estimateOpenAICost(model, promptTokens, completionTokens, cachedTokens)
4165
4240
  cached_tokens: cachedTokens
4166
4241
  });
4167
4242
  }
4168
- function isOSeriesModel(model) {
4169
- 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 };
4170
4254
  }
4171
- function supportsReasoningEffort(model) {
4172
- 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
+ };
4173
4270
  }
4174
- var GPT_56_REASONING_MODELS = /* @__PURE__ */ new Set([
4175
- "gpt-5.6",
4176
- "gpt-5.6-sol",
4177
- "gpt-5.6-terra",
4178
- "gpt-5.6-luna"
4179
- ]);
4180
- function supportsMaxReasoningEffort(model) {
4181
- 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
+ };
4182
4284
  }
4183
- function supportsReasoningNone(model) {
4184
- 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
+ };
4185
4348
  }
4186
- function supportsXhigh(model) {
4187
- 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;
4188
4372
  }
4189
- function clampReasoningEffort(model, effort) {
4190
- if (model.startsWith("gpt-5-pro")) return "high";
4191
- if (effort === "none" && !supportsReasoningNone(model)) return "minimal";
4192
- if (effort === "max" && !supportsMaxReasoningEffort(model)) {
4193
- 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";
4194
4383
  }
4195
- if (effort === "xhigh" && !supportsXhigh(model)) return "high";
4196
- return effort;
4197
4384
  }
4198
- function effortToReasoningEffort(effort, model) {
4199
- return effort === "max" && model !== void 0 && supportsMaxReasoningEffort(model) ? "max" : effort === "max" ? "xhigh" : effort;
4385
+ function isOSeriesModel(model) {
4386
+ return resolveOpenAIModelDescriptor(model).family === "o-series";
4200
4387
  }
4201
4388
  function budgetToReasoningEffort(budget) {
4202
4389
  if (budget <= 1024) return "low";
4203
4390
  if (budget <= 8192) return "medium";
4204
4391
  return "high";
4205
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
+ }
4206
4399
  function resolveOpenAIReasoningEffort(model, resolved) {
4207
- if (!supportsReasoningEffort(model)) return void 0;
4208
- if (resolved.hasBudgetOverride) {
4209
- return clampReasoningEffort(model, budgetToReasoningEffort(resolved.thinkingBudget));
4210
- }
4211
- if (!resolved.thinkingDisabled && resolved.activeEffort) {
4212
- return clampReasoningEffort(model, effortToReasoningEffort(resolved.activeEffort, model));
4213
- }
4214
- return resolved.thinkingDisabled ? clampReasoningEffort(model, "none") : void 0;
4400
+ return resolveEndpointReasoningEffort(model, "responses", resolved);
4215
4401
  }
4216
4402
  function resolveOpenAIChatReasoningEffort(model, resolved) {
4217
- const effort = resolveOpenAIReasoningEffort(model, resolved);
4218
- 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
+ }
4219
4443
  }
4220
4444
  function resolveOpenAIEffortResolution(options, resolveWireEffort, endpoint) {
4221
4445
  const resolved = resolveThinkingOptions(options);
@@ -4231,11 +4455,15 @@ function resolveOpenAIEffortResolution(options, resolveWireEffort, endpoint) {
4231
4455
  };
4232
4456
  }
4233
4457
  var openaiReasoningEmit = (body, resolved, model) => {
4234
- const oSeries = isOSeriesModel(model);
4235
- const reasoningCapable = supportsReasoningEffort(model);
4236
4458
  const wireEffort = resolveOpenAIChatReasoningEffort(model, resolved);
4237
4459
  if (wireEffort) body.reasoning_effort = wireEffort;
4238
- return { stripTemperature: oSeries || reasoningCapable && wireEffort !== void 0 };
4460
+ return {
4461
+ stripTemperature: stripsPortableSampling(
4462
+ resolveOpenAIModelDescriptor(model),
4463
+ "chat",
4464
+ wireEffort
4465
+ )
4466
+ };
4239
4467
  };
4240
4468
  var OPENAI_PROFILE = {
4241
4469
  name: "openai",
@@ -4270,6 +4498,9 @@ var OpenAIProvider = class extends OpenAICompatibleProvider {
4270
4498
  "OpenAI Chat Completions"
4271
4499
  );
4272
4500
  }
4501
+ validateFinalRequestBody(body) {
4502
+ validateOpenAIFinalRequestBody(body, "chat");
4503
+ }
4273
4504
  computeCost(model, usage, _reportedCost, context) {
4274
4505
  return usage ? estimateDirectOpenAICost(model, usage, { ...context, baseUrl: this.baseUrl }) : void 0;
4275
4506
  }
@@ -4542,12 +4773,12 @@ var OpenAIResponsesProvider = class {
4542
4773
  // ---------------------------------------------------------------------------
4543
4774
  buildRequestBody(messages, options, stream) {
4544
4775
  const effectiveModel = typeof options.providerOptions?.model === "string" ? options.providerOptions.model : options.model;
4545
- const oSeries = isOSeriesModel(effectiveModel);
4546
- const reasoningCapable = supportsReasoningEffort(effectiveModel);
4776
+ const descriptor = resolveOpenAIModelDescriptor(effectiveModel);
4777
+ const reasoningCapable = descriptor.reasoning;
4547
4778
  const resolved = resolveThinkingOptions(options);
4548
4779
  const { includeThoughts } = resolved;
4549
4780
  const wireEffort = resolveOpenAIReasoningEffort(effectiveModel, resolved);
4550
- const stripTemp = oSeries || supportsMaxReasoningEffort(effectiveModel) || reasoningCapable && wireEffort !== void 0;
4781
+ const stripTemp = stripsPortableSampling(descriptor, "responses", wireEffort);
4551
4782
  const systemMessages = messages.filter((m) => m.role === "system");
4552
4783
  const nonSystemMessages = messages.filter((m) => m.role !== "system");
4553
4784
  const body = {
@@ -4596,6 +4827,7 @@ var OpenAIResponsesProvider = class {
4596
4827
  if (options.providerOptions) {
4597
4828
  Object.assign(body, options.providerOptions);
4598
4829
  }
4830
+ validateOpenAIFinalRequestBody(body, "responses");
4599
4831
  return body;
4600
4832
  }
4601
4833
  requestModel(body, fallback) {
@@ -4910,6 +5142,36 @@ function anthropicImageBlocks(parts, model) {
4910
5142
  }
4911
5143
  var ANTHROPIC_API_VERSION = "2023-06-01";
4912
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 hasReplayedThinking(body) {
5150
+ return Array.isArray(body.messages) && body.messages.some(
5151
+ (message) => isRecord(message) && Array.isArray(message.content) && message.content.some(
5152
+ (block) => isRecord(block) && (block.type === "thinking" || block.type === "redacted_thinking")
5153
+ )
5154
+ );
5155
+ }
5156
+ function hasFinalFileImage(body) {
5157
+ return Array.isArray(body.messages) && body.messages.some(
5158
+ (message) => isRecord(message) && Array.isArray(message.content) && message.content.some(
5159
+ (block) => isRecord(block) && block.type === "image" && isRecord(block.source) && block.source.type === "file"
5160
+ )
5161
+ );
5162
+ }
5163
+ function normalizeThinkingDrops(value) {
5164
+ if (!Array.isArray(value)) return void 0;
5165
+ const reasons = {};
5166
+ let droppedBlocks = 0;
5167
+ for (const entry of value) {
5168
+ if (!isRecord(entry) || entry.type !== "thinking_dropped") continue;
5169
+ 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";
5170
+ reasons[reason] = (reasons[reason] ?? 0) + 1;
5171
+ droppedBlocks++;
5172
+ }
5173
+ return droppedBlocks > 0 ? { droppedBlocks, reasons } : void 0;
5174
+ }
4913
5175
  function hasAnthropicProviderFileImage(messages) {
4914
5176
  return messages.some(
4915
5177
  (message) => Array.isArray(message.content) && message.content.some(
@@ -4918,11 +5180,19 @@ function hasAnthropicProviderFileImage(messages) {
4918
5180
  );
4919
5181
  }
4920
5182
  var CLAUDE_CAPABILITIES = {
5183
+ "claude-opus-5-5": {
5184
+ thinking: "adaptive-always-on",
5185
+ effortLevels: ["low", "medium", "high", "xhigh", "max"],
5186
+ manualBudget: false,
5187
+ stripTemperature: true,
5188
+ thinkingBinding: true
5189
+ },
4921
5190
  "claude-fable-5-1": {
4922
5191
  thinking: "adaptive-always-on",
4923
5192
  effortLevels: ["low", "medium", "high", "xhigh", "max"],
4924
5193
  manualBudget: false,
4925
- stripTemperature: true
5194
+ stripTemperature: true,
5195
+ thinkingBinding: true
4926
5196
  },
4927
5197
  "claude-fable-5": {
4928
5198
  thinking: "adaptive-always-on",
@@ -5016,6 +5286,7 @@ function resolveClaudeCapability(model) {
5016
5286
  }
5017
5287
  var DEFAULT_CACHE_READ_MULTIPLIER = 0.1;
5018
5288
  var ANTHROPIC_RATES = {
5289
+ "claude-opus-5-5": { input: 4e-6, output: 2e-5, cacheReadMultiplier: 0.05 },
5019
5290
  "claude-fable-5-1": { input: 1e-5, output: 5e-5, cacheReadMultiplier: 0.025 },
5020
5291
  "claude-fable-5": { input: 1e-5, output: 5e-5 },
5021
5292
  // Limited availability (Glasswing). Priced from the public pricing table; no
@@ -5450,11 +5721,12 @@ var AnthropicProvider = class {
5450
5721
  // chat - non-streaming completion
5451
5722
  // ---------------------------------------------------------------------------
5452
5723
  async chat(messages, options) {
5724
+ const body = this.buildRequestBody(messages, options, false);
5453
5725
  const headers = this.buildHeaders(
5454
5726
  await this.resolveKey(),
5455
- hasAnthropicProviderFileImage(messages)
5727
+ hasAnthropicProviderFileImage(messages) || hasFinalFileImage(body),
5728
+ this.requiresBindingBeta(body)
5456
5729
  );
5457
- const body = this.buildRequestBody(messages, options, false);
5458
5730
  const pricingContext = pricingContextFromBody(body);
5459
5731
  const recorder = new CallTimingRecorder(options.requestLifecycle);
5460
5732
  const governor = this.governorFor(pricingContext.model ?? options.model);
@@ -5492,11 +5764,12 @@ var AnthropicProvider = class {
5492
5764
  // stream - SSE streaming completion
5493
5765
  // ---------------------------------------------------------------------------
5494
5766
  async *stream(messages, options) {
5767
+ const body = this.buildRequestBody(messages, options, true);
5495
5768
  const headers = this.buildHeaders(
5496
5769
  await this.resolveKey(),
5497
- hasAnthropicProviderFileImage(messages)
5770
+ hasAnthropicProviderFileImage(messages) || hasFinalFileImage(body),
5771
+ this.requiresBindingBeta(body)
5498
5772
  );
5499
- const body = this.buildRequestBody(messages, options, true);
5500
5773
  const pricingContext = pricingContextFromBody(body);
5501
5774
  const recorder = new CallTimingRecorder(options.requestLifecycle);
5502
5775
  const governor = this.governorFor(pricingContext.model ?? options.model);
@@ -5538,12 +5811,61 @@ var AnthropicProvider = class {
5538
5811
  // ---------------------------------------------------------------------------
5539
5812
  // Internal: request building
5540
5813
  // ---------------------------------------------------------------------------
5541
- buildHeaders(apiKey, requiresFilesBeta = false) {
5814
+ buildHeaders(apiKey, requiresFilesBeta = false, requiresBindingBeta = false) {
5815
+ const betas = [
5816
+ ...requiresFilesBeta ? [ANTHROPIC_FILES_BETA] : [],
5817
+ ...requiresBindingBeta ? [ANTHROPIC_THINKING_BINDING_BETA] : []
5818
+ ];
5542
5819
  return {
5543
5820
  "Content-Type": "application/json",
5544
5821
  "x-api-key": apiKey,
5545
5822
  "anthropic-version": ANTHROPIC_API_VERSION,
5546
- ...requiresFilesBeta ? { "anthropic-beta": ANTHROPIC_FILES_BETA } : {}
5823
+ ...betas.length > 0 ? { "anthropic-beta": betas.join(",") } : {}
5824
+ };
5825
+ }
5826
+ requiresBindingBeta(body) {
5827
+ const thinking = body.thinking;
5828
+ return resolveClaudeCapability(String(body.model))?.thinkingBinding === true && (hasReplayedThinking(body) || isRecord(thinking) && thinking.block_binding !== void 0);
5829
+ }
5830
+ validateAndBindFinalBody(body) {
5831
+ const model = typeof body.model === "string" ? body.model : "";
5832
+ if (resolveClaudeCapability(model)?.thinkingBinding !== true) return;
5833
+ const reject = (option, remediation) => {
5834
+ throw new UnsupportedModelOptionError({ provider: this.name, model, option, remediation });
5835
+ };
5836
+ const toolChoice = body.tool_choice;
5837
+ if (isRecord(toolChoice) && (toolChoice.type === "any" || toolChoice.type === "tool")) {
5838
+ reject("forced tool choice", "Use toolChoice: 'auto' or 'none' on this model.");
5839
+ }
5840
+ if (body.thinking !== void 0 && (!isRecord(body.thinking) || body.thinking.type !== "adaptive" || body.thinking.budget_tokens !== void 0)) {
5841
+ reject(
5842
+ "manual or disabled thinking",
5843
+ "Use thinking: { type: 'adaptive' } and output_config.effort."
5844
+ );
5845
+ }
5846
+ const outputConfig = body.output_config;
5847
+ if (outputConfig !== void 0 && (!isRecord(outputConfig) || outputConfig.effort !== void 0 && !["low", "medium", "high", "xhigh", "max"].includes(String(outputConfig.effort)))) {
5848
+ reject("output_config.effort", "Use low, medium, high, xhigh, or max effort.");
5849
+ }
5850
+ if (body.temperature !== void 0 && body.temperature !== 1) {
5851
+ reject("temperature", "Omit temperature or use the default value 1.");
5852
+ }
5853
+ if (body.top_p !== void 0 && (typeof body.top_p !== "number" || body.top_p < 0.99 || body.top_p > 1)) {
5854
+ reject("top_p", "Omit top_p or use a value from 0.99 to 1.");
5855
+ }
5856
+ if (body.top_k !== void 0) {
5857
+ reject("top_k", "Omit top_k on this model.");
5858
+ }
5859
+ const replayed = hasReplayedThinking(body);
5860
+ if (!replayed) return;
5861
+ const thinking = isRecord(body.thinking) ? body.thinking : { type: "adaptive" };
5862
+ const binding = thinking.block_binding;
5863
+ if (binding !== void 0 && (!isRecord(binding) || binding.prefix_mismatch_behavior !== "error" && binding.prefix_mismatch_behavior !== "drop_block")) {
5864
+ reject("thinking.block_binding", "Use prefix_mismatch_behavior: 'error' or 'drop_block'.");
5865
+ }
5866
+ body.thinking = {
5867
+ ...thinking,
5868
+ block_binding: binding ?? { prefix_mismatch_behavior: "drop_block" }
5547
5869
  };
5548
5870
  }
5549
5871
  /** Extract a human-readable message from an API error response body. */
@@ -5622,6 +5944,7 @@ ${jsonInstruction}` : jsonInstruction;
5622
5944
  if (options.providerOptions) {
5623
5945
  Object.assign(body, options.providerOptions);
5624
5946
  }
5947
+ this.validateAndBindFinalBody(body);
5625
5948
  return body;
5626
5949
  }
5627
5950
  /**
@@ -5778,6 +6101,7 @@ ${jsonInstruction}` : jsonInstruction;
5778
6101
  }
5779
6102
  }
5780
6103
  const normalized = json.usage ? normalizeAnthropicUsage(json.usage) : void 0;
6104
+ const reasoningContextReset = normalizeThinkingDrops(json.input_transformations);
5781
6105
  const effectiveModel = typeof json.model === "string" ? json.model : pricingContext.model;
5782
6106
  if (hasFallbackBoundary) {
5783
6107
  throw new Error(
@@ -5792,7 +6116,8 @@ ${jsonInstruction}` : jsonInstruction;
5792
6116
  usage: normalized?.usage,
5793
6117
  cost,
5794
6118
  costProvenance: tableEstimate(cost),
5795
- providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
6119
+ providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary ? { anthropicThinkingBlocks: thinkingBlocks } : void 0,
6120
+ ...reasoningContextReset ? { diagnostics: { reasoningContextReset } } : {}
5796
6121
  };
5797
6122
  }
5798
6123
  // ---------------------------------------------------------------------------
@@ -5814,6 +6139,7 @@ ${jsonInstruction}` : jsonInstruction;
5814
6139
  let hasFallbackBoundary = false;
5815
6140
  let hasFallbackIterationSignal = false;
5816
6141
  let refused = false;
6142
+ let reasoningContextReset;
5817
6143
  const finalizeUsage = () => {
5818
6144
  const normalized = rawUsage ? normalizeAnthropicUsage(rawUsage) : void 0;
5819
6145
  usage = normalized?.usage;
@@ -5826,7 +6152,8 @@ ${jsonInstruction}` : jsonInstruction;
5826
6152
  usage,
5827
6153
  cost,
5828
6154
  costProvenance: tableEstimate(cost),
5829
- providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0
6155
+ providerMetadata: thinkingBlocks.length > 0 && !hasFallbackBoundary && !hasFallbackIterationSignal ? { anthropicThinkingBlocks: thinkingBlocks } : void 0,
6156
+ ...reasoningContextReset ? { diagnostics: { reasoningContextReset } } : {}
5830
6157
  };
5831
6158
  };
5832
6159
  try {
@@ -5919,6 +6246,7 @@ ${jsonInstruction}` : jsonInstruction;
5919
6246
  rawUsage = mergeAnthropicUsage(rawUsage, event.message.usage);
5920
6247
  }
5921
6248
  if (typeof event.message?.model === "string") effectiveModel = event.message.model;
6249
+ reasoningContextReset = normalizeThinkingDrops(event.message?.input_transformations);
5922
6250
  const messageFallback = hasFallbackIteration(event.message?.usage?.iterations);
5923
6251
  if (isAnthropicResponseModifier(
5924
6252
  "inference_geo",
@@ -5935,6 +6263,10 @@ ${jsonInstruction}` : jsonInstruction;
5935
6263
  break;
5936
6264
  }
5937
6265
  case "message_delta": {
6266
+ const finalTransformations = event.input_transformations ?? event.delta?.input_transformations;
6267
+ if (finalTransformations !== void 0) {
6268
+ reasoningContextReset = normalizeThinkingDrops(finalTransformations);
6269
+ }
5938
6270
  if (event.usage) {
5939
6271
  rawUsage = mergeAnthropicUsage(rawUsage, event.usage);
5940
6272
  finalizeUsage();
@@ -6163,7 +6495,31 @@ var GEMINI_PRICING = {
6163
6495
  },
6164
6496
  "gemini-3.8-flash": { input: 75e-8, cached: 75e-9, output: 375e-8 }
6165
6497
  };
6498
+ var GEMINI_FLASH_SUCCESSOR_PRICING = {
6499
+ ...GEMINI_PRICING,
6500
+ "gemini-3.6-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 },
6501
+ "gemini-3.7-flash": {
6502
+ input: 15e-7,
6503
+ cached: 15e-8,
6504
+ output: 75e-7,
6505
+ audioInput: 15e-7
6506
+ },
6507
+ "gemini-3.8-flash": { input: 15e-7, cached: 15e-8, output: 75e-7 }
6508
+ };
6509
+ var GEMINI_FLASH_SUCCESSOR_START_MS = Date.UTC(2027, 0, 1);
6510
+ function geminiRatesAtDispatch(at) {
6511
+ return at < GEMINI_FLASH_SUCCESSOR_START_MS ? GEMINI_PRICING : GEMINI_FLASH_SUCCESSOR_PRICING;
6512
+ }
6166
6513
  var CANONICAL_GEMINI_BASE_URL = GEMINI_DEFAULT_BASE_URL;
6514
+ function geminiPricingTimingObserver(context, recorder) {
6515
+ return {
6516
+ ...recorder.observer,
6517
+ onDispatch: (attempt, at) => {
6518
+ context.rates = geminiRatesAtDispatch(at);
6519
+ recorder.observer.onDispatch?.(attempt, at);
6520
+ }
6521
+ };
6522
+ }
6167
6523
  function isValidTokenCount2(value) {
6168
6524
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
6169
6525
  }
@@ -6244,8 +6600,8 @@ function geminiInteractionErrorStatus(code) {
6244
6600
  function isEligibleGeminiPricing(context, hasDefinitiveStandardResponseTier, hasInvalidResponseTier) {
6245
6601
  return context.eligibleRequest && isStandardGeminiRequestTier(context.serviceTier) && hasDefinitiveStandardResponseTier && !hasInvalidResponseTier;
6246
6602
  }
6247
- function estimateGeminiCost(model, usage) {
6248
- const pricing = GEMINI_PRICING[model];
6603
+ function estimateGeminiCost(model, usage, rates) {
6604
+ const pricing = rates && Object.hasOwn(rates, model) ? rates[model] : void 0;
6249
6605
  const cached = usage.cachedTokens ?? 0;
6250
6606
  if (!pricing || !isValidTokenCount2(usage.inputTokens) || !isValidTokenCount2(usage.outputTokens) || !isValidTokenCount2(cached) || cached > usage.inputTokens) {
6251
6607
  return void 0;
@@ -6573,7 +6929,7 @@ var GeminiProvider = class {
6573
6929
  {
6574
6930
  governor: this.governorFor(pricingContext.model),
6575
6931
  provider: this.name,
6576
- timing: recorder.observer,
6932
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6577
6933
  admission: options.dispatchAdmission
6578
6934
  }
6579
6935
  );
@@ -6616,7 +6972,7 @@ var GeminiProvider = class {
6616
6972
  {
6617
6973
  governor: this.governorFor(pricingContext.model),
6618
6974
  provider: this.name,
6619
- timing: recorder.observer,
6975
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6620
6976
  admission: options.dispatchAdmission
6621
6977
  }
6622
6978
  );
@@ -6654,7 +7010,7 @@ var GeminiProvider = class {
6654
7010
  {
6655
7011
  governor: this.governorFor(pricingContext.model),
6656
7012
  provider: this.name,
6657
- timing: recorder.observer,
7013
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6658
7014
  admission: options.dispatchAdmission
6659
7015
  }
6660
7016
  );
@@ -6690,7 +7046,7 @@ var GeminiProvider = class {
6690
7046
  {
6691
7047
  governor: this.governorFor(pricingContext.model),
6692
7048
  provider: this.name,
6693
- timing: recorder.observer,
7049
+ timing: geminiPricingTimingObserver(pricingContext, recorder),
6694
7050
  admission: options.dispatchAdmission
6695
7051
  }
6696
7052
  );
@@ -6909,7 +7265,7 @@ var GeminiProvider = class {
6909
7265
  )) {
6910
7266
  return void 0;
6911
7267
  }
6912
- return estimateGeminiCost(effectiveModel, normalized.pricingUsage);
7268
+ return estimateGeminiCost(effectiveModel, normalized.pricingUsage, pricingContext.rates);
6913
7269
  }
6914
7270
  /**
6915
7271
  * Whether every model-produced step is text. A non-text reply part (image,
@@ -7494,7 +7850,7 @@ var GeminiProvider = class {
7494
7850
  pricingContext,
7495
7851
  isDefinitiveStandardGeminiResponseTier(json.usageMetadata?.serviceTier),
7496
7852
  json.usageMetadata?.serviceTier !== void 0 && !isDefinitiveStandardGeminiResponseTier(json.usageMetadata.serviceTier)
7497
- ) ? estimateGeminiCost(effectiveModel, normalized.pricingUsage) : void 0;
7853
+ ) ? estimateGeminiCost(effectiveModel, normalized.pricingUsage, pricingContext.rates) : void 0;
7498
7854
  const rawParts = candidate?.content?.parts;
7499
7855
  const providerMetadata = rawParts ? { geminiParts: rawParts } : void 0;
7500
7856
  return {
@@ -7585,7 +7941,7 @@ var GeminiProvider = class {
7585
7941
  pricingContext,
7586
7942
  hasDefinitiveStandardResponseTier,
7587
7943
  hasInvalidResponseTier
7588
- ) ? estimateGeminiCost(effectiveModel, normalizedUsage.pricingUsage) : void 0;
7944
+ ) ? estimateGeminiCost(effectiveModel, normalizedUsage.pricingUsage, pricingContext.rates) : void 0;
7589
7945
  yield {
7590
7946
  type: "done",
7591
7947
  usage: normalizedUsage?.usage,
@@ -9234,8 +9590,8 @@ var TranscriptionProviderRegistry = class {
9234
9590
  };
9235
9591
 
9236
9592
  // src/context.ts
9237
- var import_node_async_hooks2 = require("async_hooks");
9238
- var import_node_crypto = require("crypto");
9593
+ var import_node_async_hooks3 = require("async_hooks");
9594
+ var import_node_crypto2 = require("crypto");
9239
9595
  var import_zod2 = require("zod");
9240
9596
 
9241
9597
  // src/input.ts
@@ -9498,6 +9854,95 @@ function sessionHistoryForAsk(history, normalizedInput) {
9498
9854
  return snapshot;
9499
9855
  }
9500
9856
 
9857
+ // src/compaction.ts
9858
+ var import_node_crypto = require("crypto");
9859
+ var SUMMARY_SYSTEM_PROMPT = "Summarize the following conversation concisely, preserving key facts, decisions, and context needed for continuing the conversation.";
9860
+ var SUMMARY_MAX_TOKENS = 1024;
9861
+ function buildSummaryPrompt(messages, previousSummary) {
9862
+ return [
9863
+ ...previousSummary ? [`Previous conversation summary: ${previousSummary}`] : [],
9864
+ ...messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`)
9865
+ ].join("\n");
9866
+ }
9867
+ function requestSummary(provider, options) {
9868
+ return provider.chat(
9869
+ [
9870
+ { role: "system", content: SUMMARY_SYSTEM_PROMPT },
9871
+ { role: "user", content: options.prompt }
9872
+ ],
9873
+ {
9874
+ model: options.model,
9875
+ maxTokens: SUMMARY_MAX_TOKENS,
9876
+ ...options.signal ? { signal: options.signal } : {}
9877
+ }
9878
+ );
9879
+ }
9880
+ function summaryContextMessage(summary) {
9881
+ return {
9882
+ role: "system",
9883
+ origin: "runtime",
9884
+ content: `Summary of earlier conversation:
9885
+ ${summary}`
9886
+ };
9887
+ }
9888
+ function withoutAnthropicThinking(message) {
9889
+ const metadata = message.providerMetadata;
9890
+ if (!metadata || !Object.hasOwn(metadata, "anthropicThinkingBlocks")) return message;
9891
+ const retainedMetadata = { ...metadata };
9892
+ delete retainedMetadata.anthropicThinkingBlocks;
9893
+ return {
9894
+ ...message,
9895
+ ...Object.keys(retainedMetadata).length > 0 ? { providerMetadata: retainedMetadata } : { providerMetadata: void 0 }
9896
+ };
9897
+ }
9898
+ function anthropicThinkingBlockCount(message) {
9899
+ const blocks = message.providerMetadata?.anthropicThinkingBlocks;
9900
+ if (!Array.isArray(blocks)) return 0;
9901
+ return blocks.filter(
9902
+ (block) => block !== null && typeof block === "object" && (block.type === "thinking" || block.type === "redacted_thinking")
9903
+ ).length;
9904
+ }
9905
+ function askSummaryMetaKey(agentName) {
9906
+ return `askSummary:${agentName}`;
9907
+ }
9908
+ function isCount(value) {
9909
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
9910
+ }
9911
+ function parseAskSummaryRecord(value) {
9912
+ if (!value || typeof value !== "object") return void 0;
9913
+ const record = value;
9914
+ 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") {
9915
+ return void 0;
9916
+ }
9917
+ return {
9918
+ summary: record.summary,
9919
+ coveredCount: record.coveredCount,
9920
+ prefixHash: record.prefixHash,
9921
+ ...record.sourceSummary !== void 0 ? { sourceSummary: record.sourceSummary } : {},
9922
+ summaryModelUri: record.summaryModelUri,
9923
+ invalidatedThinkingThrough: record.invalidatedThinkingThrough
9924
+ };
9925
+ }
9926
+ function isPlainObject(value) {
9927
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
9928
+ const proto = Object.getPrototypeOf(value);
9929
+ return proto === Object.prototype || proto === null;
9930
+ }
9931
+ function summaryPrefixHash(history, coveredCount) {
9932
+ let canonical;
9933
+ try {
9934
+ canonical = JSON.stringify(
9935
+ history.slice(0, coveredCount),
9936
+ (_key, value) => isPlainObject(value) ? Object.fromEntries(
9937
+ Object.keys(value).sort().map((key) => [key, value[key]])
9938
+ ) : value
9939
+ );
9940
+ } catch {
9941
+ return void 0;
9942
+ }
9943
+ return (0, import_node_crypto.createHash)("sha256").update(canonical).digest("hex");
9944
+ }
9945
+
9501
9946
  // src/partial-json.ts
9502
9947
  var MAX_DEPTH = 256;
9503
9948
  function parsePartialJson(input) {
@@ -11134,9 +11579,9 @@ var LEGACY_REDACTION_RULES = {
11134
11579
  // which is user content or PII (field paths are schema-shape, preserved like
11135
11580
  // `string_delta.path`). Passthrough, as an explicit reviewed decision.
11136
11581
  schema_diagnostic: passthrough,
11137
- // `provider_diagnostic` carries only provider capability metadata — a provider
11138
- // name, a model id, and two effort level strings. No application data or user
11139
- // content ever reaches it. Passthrough, as an explicit reviewed decision.
11582
+ // `provider_diagnostic` carries only provider/model metadata, effort levels,
11583
+ // and bounded dropped-thinking reason counts. Raw transformation paths and
11584
+ // signed thinking never enter this event. Passthrough is reviewed here.
11140
11585
  provider_diagnostic: passthrough,
11141
11586
  log: (e) => {
11142
11587
  if (!e.data || typeof e.data !== "object" || Array.isArray(e.data)) return e;
@@ -12766,9 +13211,8 @@ function recordToolSpanOutcome(span, outcome) {
12766
13211
  }
12767
13212
 
12768
13213
  // src/context.ts
12769
- var signalStorage = new import_node_async_hooks2.AsyncLocalStorage();
12770
- var hardSignalStorage = new import_node_async_hooks2.AsyncLocalStorage();
12771
- var timeoutTrackerStorage = new import_node_async_hooks2.AsyncLocalStorage();
13214
+ var signalStorage = new import_node_async_hooks3.AsyncLocalStorage();
13215
+ var hardSignalStorage = new import_node_async_hooks3.AsyncLocalStorage();
12772
13216
  var MAX_TIMER_DELAY_MS = 2147483647;
12773
13217
  function composeAbortSignals(...signals) {
12774
13218
  const active = signals.filter((signal) => signal !== void 0);
@@ -12800,9 +13244,6 @@ function raceWithAbortSignal(promise, signal) {
12800
13244
  );
12801
13245
  });
12802
13246
  }
12803
- function pausedHumanWaitMs(tracker, now = Date.now()) {
12804
- return tracker.humanWaitMs + (tracker.activeHumanWaits > 0 && tracker.humanWaitStartedAt !== void 0 ? now - tracker.humanWaitStartedAt : 0);
12805
- }
12806
13247
  var DEFAULT_BRANCH_DRAIN_TIMEOUT_MS = 5e3;
12807
13248
  function resolveBranchDrainTimeoutMs(value) {
12808
13249
  const resolved = value === void 0 ? DEFAULT_BRANCH_DRAIN_TIMEOUT_MS : value;
@@ -12873,7 +13314,7 @@ function parseHumanDecision(value) {
12873
13314
  }
12874
13315
  return invalid3();
12875
13316
  }
12876
- var askStorage = new import_node_async_hooks2.AsyncLocalStorage();
13317
+ var askStorage = new import_node_async_hooks3.AsyncLocalStorage();
12877
13318
  var inlineSchemaCache = /* @__PURE__ */ new WeakMap();
12878
13319
  function zodToJsonSchema(schema) {
12879
13320
  const cached = inlineSchemaCache.get(schema);
@@ -13145,7 +13586,14 @@ var WorkflowContext = class _WorkflowContext {
13145
13586
  * suppresses late branch events after bounded finalization. */
13146
13587
  workflowLifecycleState;
13147
13588
  signal;
13148
- summaryCache;
13589
+ /** Durable summary of messages removed by Session.history.maxMessages. */
13590
+ sessionSummary;
13591
+ /** Per-agent `maxContext` summaries, keyed by agent name. `null` records a
13592
+ * store lookup that found nothing. Mirrored to session metadata
13593
+ * (`askSummary:<agent>`) when this execution belongs to a session, so a
13594
+ * later execution can reuse the same exact-prefix boundary. Never the
13595
+ * durable `sessionSummary`. */
13596
+ askSummaries = /* @__PURE__ */ new Map();
13149
13597
  /** Consecutive provider calls with promptCache on that wrote to the cache
13150
13598
  * without reading from it (prefix changes per call), and consecutive calls
13151
13599
  * with no cache activity at all (prefix below the model's minimum, which
@@ -13321,7 +13769,7 @@ var WorkflowContext = class _WorkflowContext {
13321
13769
  endEmitted: false
13322
13770
  };
13323
13771
  if (init.metadata?.summaryCache) {
13324
- this.summaryCache = init.metadata.summaryCache;
13772
+ this.sessionSummary = init.metadata.summaryCache;
13325
13773
  }
13326
13774
  }
13327
13775
  /**
@@ -13649,7 +14097,7 @@ var WorkflowContext = class _WorkflowContext {
13649
14097
  this._autoCheckpointName("ask", agentName),
13650
14098
  async () => {
13651
14099
  const parentFrame = askStorage.getStore();
13652
- const askId = (0, import_node_crypto.randomUUID)();
14100
+ const askId = (0, import_node_crypto2.randomUUID)();
13653
14101
  const depth = (parentFrame?.depth ?? -1) + 1;
13654
14102
  const stepRef = parentFrame?.stepRef ?? this.stepRefRoot;
13655
14103
  const frame = {
@@ -13810,10 +14258,7 @@ var WorkflowContext = class _WorkflowContext {
13810
14258
  return hardAskSignal ? hardSignalStorage.run(hardAskSignal, runWithAskSignal) : runWithAskSignal();
13811
14259
  }
13812
14260
  async executeAgentCall(agent2, input, options, handoffMessages, usageCapture, sessionHistory = normalizeSessionHistory(this.sessionHistory)) {
13813
- const tracker = { humanWaitMs: 0, activeHumanWaits: 0 };
13814
- const enclosing = timeoutTrackerStorage.getStore() ?? [];
13815
- return timeoutTrackerStorage.run(
13816
- [...enclosing, tracker],
14261
+ return runWithAskClock(
13817
14262
  () => this.executeAgentCallImpl(
13818
14263
  agent2,
13819
14264
  input,
@@ -13949,6 +14394,7 @@ var WorkflowContext = class _WorkflowContext {
13949
14394
  if (systemPrompt) {
13950
14395
  messages.push({ role: "system", content: systemPrompt });
13951
14396
  }
14397
+ const cachedSummaryMessage = this.sessionSummary ? summaryContextMessage(this.sessionSummary) : void 0;
13952
14398
  const maxContext = agent2._config.maxContext;
13953
14399
  if (maxContext && sessionHistory.length > 0) {
13954
14400
  const reserveTokens = this.config.contextManagement?.reserveTokens ?? 2e3;
@@ -13973,22 +14419,44 @@ var WorkflowContext = class _WorkflowContext {
13973
14419
  }
13974
14420
  });
13975
14421
  }
13976
- if (historyEstimate.tokens > availableForHistory) {
13977
- const summarizedMessages = await this.summarizeHistory(
14422
+ const summaryTokens = cachedSummaryMessage ? estimateMessagesTokens([cachedSummaryMessage]).tokens : 0;
14423
+ if (historyEstimate.tokens + summaryTokens > availableForHistory) {
14424
+ const projection = await this.summarizeHistory(
14425
+ agent2._name,
14426
+ modelUri,
13978
14427
  provider,
13979
14428
  model,
13980
14429
  sessionHistory,
13981
14430
  availableForHistory
13982
14431
  );
13983
- for (const msg of summarizedMessages) {
14432
+ if (this.budgetContext?.exceeded && this.budgetContext.policy !== "warn") {
14433
+ const { limit, totalCost: spent, policy } = this.budgetContext;
14434
+ throw new BudgetExceededError(limit, spent, policy);
14435
+ }
14436
+ if (projection.removedThinkingBlocks > 0) {
14437
+ this.emitEvent({
14438
+ type: "provider_diagnostic",
14439
+ agent: agent2._name,
14440
+ data: {
14441
+ kind: "reasoning_context_reset",
14442
+ ...provider.name ? { provider: provider.name } : {},
14443
+ model: typeof providerOptions?.model === "string" ? providerOptions.model : model,
14444
+ droppedBlocks: projection.removedThinkingBlocks,
14445
+ reasons: { client_prefix_rewrite: projection.removedThinkingBlocks }
14446
+ }
14447
+ });
14448
+ }
14449
+ for (const msg of projection.messages) {
13984
14450
  messages.push(msg);
13985
14451
  }
13986
14452
  } else {
14453
+ if (cachedSummaryMessage) messages.push(cachedSummaryMessage);
13987
14454
  for (const msg of sessionHistory) {
13988
14455
  messages.push(msg);
13989
14456
  }
13990
14457
  }
13991
14458
  } else {
14459
+ if (cachedSummaryMessage) messages.push(cachedSummaryMessage);
13992
14460
  for (const msg of sessionHistory) {
13993
14461
  messages.push(msg);
13994
14462
  }
@@ -14088,7 +14556,8 @@ ${appendedSchemaText}`;
14088
14556
  );
14089
14557
  }
14090
14558
  const startTime = Date.now();
14091
- const timeoutTracker = timeoutTrackerStorage.getStore()?.at(-1);
14559
+ const askClock = currentAskClock();
14560
+ const pausedAtStartMs = askClock ? pausedAskClockMs(askClock, startTime) : 0;
14092
14561
  const timingTotals = { turns: 0, queuedMs: 0, retryMs: 0, wireMs: 0 };
14093
14562
  const currentMessages = [...messages];
14094
14563
  let turns = 0;
@@ -14105,13 +14574,16 @@ ${appendedSchemaText}`;
14105
14574
  while (turns < maxTurns) {
14106
14575
  this.currentSignal?.throwIfAborted();
14107
14576
  const now = Date.now();
14108
- const elapsedMs = now - startTime - (timeoutTracker ? pausedHumanWaitMs(timeoutTracker, now) : 0);
14109
- if (elapsedMs > timeoutMs) {
14577
+ const elapsedMs = now - startTime;
14578
+ const pausedMs = askClock ? Math.max(0, pausedAskClockMs(askClock, now) - pausedAtStartMs) : 0;
14579
+ const chargedMs = Math.max(0, elapsedMs - Math.min(pausedMs, Math.max(0, elapsedMs)));
14580
+ if (chargedMs > timeoutMs) {
14110
14581
  throw new TimeoutError(
14111
14582
  "ctx.ask()",
14112
14583
  timeoutMs,
14113
14584
  timingTotals.turns > 0 ? {
14114
14585
  elapsedMs,
14586
+ chargedMs,
14115
14587
  queuedMs: timingTotals.queuedMs,
14116
14588
  retryMs: timingTotals.retryMs,
14117
14589
  wireMs: timingTotals.wireMs,
@@ -14358,6 +14830,7 @@ ${appendedSchemaText}`;
14358
14830
  usage: chunk.usage,
14359
14831
  cost: chunk.cost,
14360
14832
  providerMetadata: chunk.providerMetadata,
14833
+ diagnostics: chunk.diagnostics,
14361
14834
  timing: chunk.timing
14362
14835
  };
14363
14836
  }
@@ -14475,6 +14948,34 @@ ${appendedSchemaText}`;
14475
14948
  timingTotals.retryMs += response.timing.retryMs;
14476
14949
  timingTotals.wireMs += response.timing.wireMs;
14477
14950
  }
14951
+ const reasoningReset = response.diagnostics?.reasoningContextReset;
14952
+ if (reasoningReset && Number.isSafeInteger(reasoningReset.droppedBlocks) && reasoningReset.droppedBlocks > 0) {
14953
+ const reasons = {};
14954
+ for (const reason of [
14955
+ "prefix_binding_mismatch",
14956
+ "model_binding_mismatch",
14957
+ "organization_binding_mismatch",
14958
+ "end_user_binding_mismatch",
14959
+ "client_prefix_rewrite",
14960
+ "other"
14961
+ ]) {
14962
+ const count = reasoningReset.reasons?.[reason];
14963
+ if (typeof count === "number" && Number.isSafeInteger(count) && count > 0) {
14964
+ reasons[reason] = count;
14965
+ }
14966
+ }
14967
+ this.emitEvent({
14968
+ type: "provider_diagnostic",
14969
+ agent: agent2._name,
14970
+ data: {
14971
+ kind: "reasoning_context_reset",
14972
+ ...provider.name ? { provider: provider.name } : {},
14973
+ model: typeof providerOptions?.model === "string" ? providerOptions.model : model,
14974
+ droppedBlocks: reasoningReset.droppedBlocks,
14975
+ reasons
14976
+ }
14977
+ });
14978
+ }
14478
14979
  this.emitEvent({
14479
14980
  type: "agent_call_end",
14480
14981
  agent: agent2._name,
@@ -14525,7 +15026,7 @@ ${appendedSchemaText}`;
14525
15026
  const sourceFrame = askStorage.getStore();
14526
15027
  const handoffFromAskId = sourceFrame?.askId ?? this.executionId;
14527
15028
  const handoffSourceDepth = sourceFrame?.depth ?? 0;
14528
- const handoffToAskId = (0, import_node_crypto.randomUUID)();
15029
+ const handoffToAskId = (0, import_node_crypto2.randomUUID)();
14529
15030
  const handoffTargetDepth = handoffSourceDepth + 1;
14530
15031
  const targetFrame = {
14531
15032
  askId: handoffToAskId,
@@ -15217,31 +15718,65 @@ ${appendedSchemaText}`;
15217
15718
  }
15218
15719
  return defs;
15219
15720
  }
15721
+ /** Session id for session-scoped ask-summary persistence, when this
15722
+ * execution belongs to a session and has a store to persist into. */
15723
+ askSummarySessionId() {
15724
+ const sessionId = this.metadata?.sessionId;
15725
+ if (this.metadata?.sessionPersist === false) return void 0;
15726
+ return this.stateStore && typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
15727
+ }
15728
+ /** This agent's ask summary: the in-memory copy first, else the session's
15729
+ * persisted record (read once per execution). */
15730
+ async loadAskSummary(agentName) {
15731
+ if (this.askSummaries.has(agentName)) return this.askSummaries.get(agentName) ?? void 0;
15732
+ const sessionId = this.askSummarySessionId();
15733
+ const stored = sessionId ? parseAskSummaryRecord(
15734
+ await this.stateStore.getSessionMeta(sessionId, askSummaryMetaKey(agentName))
15735
+ ) : void 0;
15736
+ this.askSummaries.set(agentName, stored ?? null);
15737
+ return stored;
15738
+ }
15739
+ async saveAskSummary(agentName, record) {
15740
+ this.askSummaries.set(agentName, record);
15741
+ const sessionId = this.askSummarySessionId();
15742
+ if (!sessionId) return;
15743
+ const key = askSummaryMetaKey(agentName);
15744
+ try {
15745
+ await this.stateStore.saveSessionMeta(sessionId, key, record);
15746
+ } catch (error) {
15747
+ this.emitEvent({
15748
+ type: "log",
15749
+ agent: agentName,
15750
+ data: {
15751
+ warning: `Could not persist ask summary '${key}' for session '${sessionId}'; later executions will regenerate it. ` + (error instanceof Error ? error.message : String(error))
15752
+ }
15753
+ });
15754
+ }
15755
+ }
15220
15756
  /**
15221
- * Summarize old messages to fit within context window.
15222
- * Keeps recent messages intact, summarizes older ones.
15757
+ * Project `history` into `availableTokens` for one agent's request: a summary
15758
+ * of an exact covered prefix followed by every later message. Reuses the
15759
+ * agent's cached boundary (in memory or persisted for the session) while the
15760
+ * covered prefix, durable session summary, and summary model are unchanged
15761
+ * and the whole tail fits; otherwise generates a new summary. Also reports how
15762
+ * many Anthropic thinking blocks the projection removed from carried turns.
15223
15763
  */
15224
- async summarizeHistory(provider, model, history, availableTokens) {
15225
- if (this.summaryCache) {
15226
- const summaryMsg2 = {
15227
- role: "system",
15228
- origin: "runtime",
15229
- content: `Summary of earlier conversation:
15230
- ${this.summaryCache}`
15231
- };
15232
- const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg2.content)) + 4;
15233
- const remaining = availableTokens - summaryTokens;
15234
- let recentTokens2 = 0;
15235
- let splitIdx2 = history.length;
15236
- for (let i = history.length - 1; i >= 0; i--) {
15237
- const msgTokens = estimateTokens(summarizeModelInput(history[i].content)) + 4;
15238
- if (recentTokens2 + msgTokens > remaining) break;
15239
- recentTokens2 += msgTokens;
15240
- splitIdx2 = i;
15241
- }
15242
- splitIdx2 = nextUserTurn(history, splitIdx2) ?? history.length;
15243
- if (splitIdx2 < history.length) {
15244
- return [summaryMsg2, ...history.slice(splitIdx2)];
15764
+ async summarizeHistory(agentName, modelUri, provider, model, history, availableTokens) {
15765
+ const summaryModelUri = this.config.contextManagement?.summaryModel ?? modelUri;
15766
+ const cache = await this.loadAskSummary(agentName);
15767
+ if (cache && cache.sourceSummary === this.sessionSummary && cache.summaryModelUri === summaryModelUri && cache.coveredCount <= history.length && cache.prefixHash === summaryPrefixHash(history, cache.coveredCount)) {
15768
+ const summaryMsg = summaryContextMessage(cache.summary);
15769
+ const summaryTokens = estimateTokens(summarizeModelInput(summaryMsg.content)) + 4;
15770
+ const tail2 = history.slice(cache.coveredCount);
15771
+ const tailTokens = estimateMessagesTokens(tail2).tokens;
15772
+ if (summaryTokens + tailTokens <= availableTokens && (tail2.length === 0 || tail2[0].role === "user")) {
15773
+ let removedThinkingBlocks = 0;
15774
+ const projected = tail2.map((message, index) => {
15775
+ if (cache.coveredCount + index >= cache.invalidatedThinkingThrough) return message;
15776
+ removedThinkingBlocks += anthropicThinkingBlockCount(message);
15777
+ return withoutAnthropicThinking(message);
15778
+ });
15779
+ return { messages: [summaryMsg, ...projected], removedThinkingBlocks };
15245
15780
  }
15246
15781
  }
15247
15782
  let recentTokens = 0;
@@ -15266,42 +15801,103 @@ ${this.summaryCache}`
15266
15801
  } else {
15267
15802
  splitIdx = history.length;
15268
15803
  }
15269
- if (splitIdx === 0) return history;
15804
+ if (splitIdx === 0 && this.sessionSummary) splitIdx = history.length;
15805
+ if (splitIdx === 0) return { messages: history, removedThinkingBlocks: 0 };
15270
15806
  const oldMessages = history.slice(0, splitIdx);
15271
- const summaryModelUri = this.config.contextManagement?.summaryModel;
15807
+ const configuredSummaryModelUri = this.config.contextManagement?.summaryModel;
15272
15808
  let summaryProvider;
15273
15809
  let summaryModel;
15274
- if (summaryModelUri) {
15275
- const resolved = this.resolveProviderUri(summaryModelUri);
15810
+ if (configuredSummaryModelUri) {
15811
+ const resolved = this.resolveProviderUri(configuredSummaryModelUri);
15276
15812
  summaryProvider = resolved.provider;
15277
15813
  summaryModel = resolved.model;
15278
15814
  } else {
15279
15815
  summaryProvider = provider;
15280
15816
  summaryModel = model;
15281
15817
  }
15282
- const oldContent = oldMessages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n");
15283
- const summaryResponse = await summaryProvider.chat(
15284
- [
15285
- {
15286
- role: "system",
15287
- content: "Summarize the following conversation concisely, preserving key facts, decisions, and context needed for continuing the conversation."
15288
- },
15289
- { role: "user", content: oldContent }
15818
+ const summaryPrompt = buildSummaryPrompt(oldMessages, this.sessionSummary);
15819
+ const summaryStart = Date.now();
15820
+ this.emitEvent({
15821
+ type: "agent_call_start",
15822
+ agent: agentName,
15823
+ model: summaryModelUri,
15824
+ turn: 0,
15825
+ data: {
15826
+ purpose: "summary",
15827
+ prompt: summaryPrompt,
15828
+ system: SUMMARY_SYSTEM_PROMPT,
15829
+ params: { maxTokens: SUMMARY_MAX_TOKENS },
15830
+ turn: 0
15831
+ }
15832
+ });
15833
+ let summaryResponse;
15834
+ try {
15835
+ this.currentSignal?.throwIfAborted();
15836
+ summaryResponse = await requestSummary(summaryProvider, {
15837
+ model: summaryModel,
15838
+ prompt: summaryPrompt,
15839
+ signal: this.currentSignal
15840
+ });
15841
+ } catch (error) {
15842
+ const providerError = error instanceof ProviderError ? error : void 0;
15843
+ this.emitEvent({
15844
+ type: "agent_call_end",
15845
+ agent: agentName,
15846
+ model: summaryModelUri,
15847
+ duration: Date.now() - summaryStart,
15848
+ ...providerError?.timing ? { timing: providerError.timing } : {},
15849
+ data: {
15850
+ purpose: "summary",
15851
+ response: "",
15852
+ turn: 0,
15853
+ // Provider errors may echo the summarized history, so the event
15854
+ // uses a fixed projection even when trace redaction is disabled.
15855
+ error: "Context-management summary call failed",
15856
+ ...providerError ? { status: providerError.status, retryable: providerError.retryable } : {}
15857
+ }
15858
+ });
15859
+ throw error;
15860
+ }
15861
+ if (isUsableCost2(summaryResponse.cost)) this._accumulateBudgetCost(summaryResponse.cost);
15862
+ this.emitEvent({
15863
+ type: "agent_call_end",
15864
+ agent: agentName,
15865
+ model: summaryModelUri,
15866
+ cost: summaryResponse.cost,
15867
+ ...!isUsableCost2(summaryResponse.cost) ? { unpriced: true } : {},
15868
+ tokens: summaryResponse.usage ? {
15869
+ input: summaryResponse.usage.prompt_tokens,
15870
+ output: summaryResponse.usage.completion_tokens,
15871
+ reasoning: summaryResponse.usage.reasoning_tokens,
15872
+ cached: summaryResponse.usage.cached_tokens,
15873
+ cacheWrite: summaryResponse.usage.cache_write_tokens
15874
+ } : void 0,
15875
+ duration: Date.now() - summaryStart,
15876
+ ...summaryResponse.timing ? { timing: summaryResponse.timing } : {},
15877
+ data: { purpose: "summary", response: summaryResponse.content, turn: 0 }
15878
+ });
15879
+ const prefixHash = summaryPrefixHash(history, splitIdx);
15880
+ if (prefixHash) {
15881
+ await this.saveAskSummary(agentName, {
15882
+ summary: summaryResponse.content,
15883
+ coveredCount: splitIdx,
15884
+ prefixHash,
15885
+ ...this.sessionSummary !== void 0 ? { sourceSummary: this.sessionSummary } : {},
15886
+ summaryModelUri,
15887
+ invalidatedThinkingThrough: history.length
15888
+ });
15889
+ }
15890
+ const tail = history.slice(splitIdx);
15891
+ return {
15892
+ messages: [
15893
+ summaryContextMessage(summaryResponse.content),
15894
+ ...tail.map(withoutAnthropicThinking)
15290
15895
  ],
15291
- { model: summaryModel, maxTokens: 1024, signal: this.currentSignal }
15292
- );
15293
- this.summaryCache = summaryResponse.content;
15294
- const sessionId = this.metadata?.sessionId;
15295
- if (sessionId && this.stateStore) {
15296
- await this.stateStore.saveSessionMeta(sessionId, "summaryCache", this.summaryCache);
15297
- }
15298
- const summaryMsg = {
15299
- role: "system",
15300
- origin: "runtime",
15301
- content: `Summary of earlier conversation:
15302
- ${summaryResponse.content}`
15896
+ removedThinkingBlocks: tail.reduce(
15897
+ (count, message) => count + anthropicThinkingBlockCount(message),
15898
+ 0
15899
+ )
15303
15900
  };
15304
- return [summaryMsg, ...history.slice(splitIdx)];
15305
15901
  }
15306
15902
  // ── ctx.checkpoint() ────────────────────────────────────────────────
15307
15903
  /**
@@ -15981,12 +16577,7 @@ ${summaryResponse.content}`
15981
16577
  }
15982
16578
  // ── ctx.awaitHuman() ──────────────────────────────────────────────────
15983
16579
  async awaitHuman(options) {
15984
- const trackers = timeoutTrackerStorage.getStore();
15985
- const startedAt = Date.now();
15986
- for (const tracker of trackers ?? []) {
15987
- if (tracker.activeHumanWaits++ === 0) tracker.humanWaitStartedAt = startedAt;
15988
- }
15989
- try {
16580
+ return pauseAskClocks(async () => {
15990
16581
  if (this.spanManager) {
15991
16582
  return await this.spanManager.withSpanAsync(
15992
16583
  "axl.ctx.awaitHuman",
@@ -16003,16 +16594,7 @@ ${summaryResponse.content}`
16003
16594
  );
16004
16595
  }
16005
16596
  return await this._awaitHumanImpl(options);
16006
- } finally {
16007
- const finishedAt = Date.now();
16008
- for (const tracker of trackers ?? []) {
16009
- tracker.activeHumanWaits--;
16010
- if (tracker.activeHumanWaits === 0 && tracker.humanWaitStartedAt !== void 0) {
16011
- tracker.humanWaitMs += finishedAt - tracker.humanWaitStartedAt;
16012
- tracker.humanWaitStartedAt = void 0;
16013
- }
16014
- }
16015
- }
16597
+ });
16016
16598
  }
16017
16599
  async _awaitHumanImpl(options) {
16018
16600
  const branchSignal = this.currentSignal;
@@ -16573,7 +17155,7 @@ ${summaryResponse.content}`
16573
17155
  * This operation never enters the chat history or chat-provider path. */
16574
17156
  async transcribe(request) {
16575
17157
  const startedAt = Date.now();
16576
- const transcriptionId = (0, import_node_crypto.randomUUID)();
17158
+ const transcriptionId = (0, import_node_crypto2.randomUUID)();
16577
17159
  const signal = this.currentSignal;
16578
17160
  const rawAudio = request && typeof request === "object" && "audio" in request ? request.audio : void 0;
16579
17161
  const source = rawAudio?.type;
@@ -16962,7 +17544,7 @@ ${summaryResponse.content}`
16962
17544
  };
16963
17545
 
16964
17546
  // src/agent.ts
16965
- var import_node_crypto2 = require("crypto");
17547
+ var import_node_crypto3 = require("crypto");
16966
17548
  var agentCounter = 0;
16967
17549
  function agent(config) {
16968
17550
  agentCounter++;
@@ -16983,7 +17565,7 @@ function agent(config) {
16983
17565
  const transcriptionRegistry = new TranscriptionProviderRegistry();
16984
17566
  const ctx = new WorkflowContext({
16985
17567
  input: prompt,
16986
- executionId: (0, import_node_crypto2.randomUUID)(),
17568
+ executionId: (0, import_node_crypto3.randomUUID)(),
16987
17569
  config: {},
16988
17570
  providerRegistry: registry2,
16989
17571
  transcriptionProviderRegistry: transcriptionRegistry
@@ -17008,8 +17590,8 @@ function workflow(config) {
17008
17590
 
17009
17591
  // src/runtime.ts
17010
17592
  var import_node_events2 = require("events");
17011
- var import_node_crypto4 = require("crypto");
17012
- var import_node_async_hooks3 = require("async_hooks");
17593
+ var import_node_crypto5 = require("crypto");
17594
+ var import_node_async_hooks4 = require("async_hooks");
17013
17595
  var import_node_fs3 = require("fs");
17014
17596
  var import_node_path3 = require("path");
17015
17597
 
@@ -17724,17 +18306,17 @@ var Session = class _Session {
17724
18306
  throw new Error("SessionOptions.history.summaryModel is required when summarize is true");
17725
18307
  }
17726
18308
  const messagesToDrop = history.slice(0, history.length - maxMessages);
17727
- const toSummarize = cachedSummary ? [
17728
- { role: "system", content: `Previous conversation summary: ${cachedSummary}` },
17729
- ...messagesToDrop
17730
- ] : messagesToDrop;
17731
- const summary = await this.runtime.summarizeMessages(toSummarize, summaryModel);
18309
+ const summary = await this.runtime.summarizeMessages(
18310
+ messagesToDrop,
18311
+ summaryModel,
18312
+ cachedSummary ? { previousSummary: cachedSummary } : void 0
18313
+ );
17732
18314
  await this.store.saveSessionMeta(this.sessionId, "summaryCache", summary);
17733
18315
  cachedSummary = summary;
17734
18316
  }
17735
18317
  const trimmed = history.slice(-maxMessages);
17736
18318
  history.length = 0;
17737
- history.push(...trimmed);
18319
+ history.push(...trimmed.map(withoutAnthropicThinking));
17738
18320
  }
17739
18321
  const userMessage = { role: "user", content: preparedInput.content };
17740
18322
  history.push(userMessage);
@@ -17744,7 +18326,10 @@ var Session = class _Session {
17744
18326
  const metadata = {
17745
18327
  sessionId: this.sessionId,
17746
18328
  sessionHistory: history,
17747
- ...cachedSummary ? { summaryCache: cachedSummary } : {}
18329
+ ...cachedSummary ? { summaryCache: cachedSummary } : {},
18330
+ // An opted-out session must leave nothing at rest, including the
18331
+ // agent-scoped ask-summary records the runtime would otherwise cache.
18332
+ ...this.options.persist === false ? { sessionPersist: false } : {}
17748
18333
  };
17749
18334
  return { history, metadata };
17750
18335
  }
@@ -18661,7 +19246,7 @@ function warnMissingMemoryMethodsOnce(stateStore) {
18661
19246
  var import_node_fs2 = require("fs");
18662
19247
  var import_promises = require("fs/promises");
18663
19248
  var import_node_readline = require("readline");
18664
- var import_node_crypto3 = require("crypto");
19249
+ var import_node_crypto4 = require("crypto");
18665
19250
  var import_node_path2 = __toESM(require("path"), 1);
18666
19251
  var MANIFEST_FILE = "manifest.json";
18667
19252
  var RECORDS_FILE = "records.jsonl";
@@ -18726,7 +19311,7 @@ var FileDiagnosticArtifactStore = class {
18726
19311
  async writeManifest(manifest) {
18727
19312
  const dir = this.dir(manifest.artifactId);
18728
19313
  await (0, import_promises.mkdir)(dir, { recursive: true });
18729
- const temp = import_node_path2.default.join(dir, `${MANIFEST_FILE}.${(0, import_node_crypto3.randomUUID)()}.tmp`);
19314
+ const temp = import_node_path2.default.join(dir, `${MANIFEST_FILE}.${(0, import_node_crypto4.randomUUID)()}.tmp`);
18730
19315
  await (0, import_promises.writeFile)(temp, JSON.stringify(manifest), "utf-8");
18731
19316
  await (0, import_promises.rename)(temp, import_node_path2.default.join(dir, MANIFEST_FILE));
18732
19317
  }
@@ -18763,7 +19348,7 @@ var FileDiagnosticArtifactStore = class {
18763
19348
  });
18764
19349
  }
18765
19350
  async stage(owner, opts) {
18766
- const artifactId = `art_${(0, import_node_crypto3.randomUUID)()}`;
19351
+ const artifactId = `art_${(0, import_node_crypto4.randomUUID)()}`;
18767
19352
  const now = Date.now();
18768
19353
  await this.writeManifest({
18769
19354
  version: 1,
@@ -19189,7 +19774,7 @@ function assertNoRemovedObservationCallbacks(options) {
19189
19774
  `runtime.createContext() no longer accepts ${removed.join(", ")}. Observe ctx.events or use runtime.stream() instead. See docs/migration/stream-first-observation.md.`
19190
19775
  );
19191
19776
  }
19192
- var costScopeStorage = new import_node_async_hooks3.AsyncLocalStorage();
19777
+ var costScopeStorage = new import_node_async_hooks4.AsyncLocalStorage();
19193
19778
  function forwardAbortSignal(external, internal) {
19194
19779
  if (!external) return () => {
19195
19780
  };
@@ -19964,7 +20549,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
19964
20549
  }
19965
20550
  try {
19966
20551
  await this.saveEvalResult({
19967
- id: resultObj.id ?? (0, import_node_crypto4.randomUUID)(),
20552
+ id: resultObj.id ?? (0, import_node_crypto5.randomUUID)(),
19968
20553
  eval: name,
19969
20554
  timestamp: Date.now(),
19970
20555
  data: structuredClone(result)
@@ -20022,7 +20607,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
20022
20607
  */
20023
20608
  createContext(options) {
20024
20609
  assertNoRemovedObservationCallbacks(options);
20025
- const executionId = (0, import_node_crypto4.randomUUID)();
20610
+ const executionId = (0, import_node_crypto5.randomUUID)();
20026
20611
  const budgetLimit = options?.budget ? parseCost(options.budget) : Infinity;
20027
20612
  this.registerWithCostScope(executionId);
20028
20613
  return new WorkflowContext({
@@ -20101,7 +20686,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
20101
20686
  }
20102
20687
  const validated = workflow2.inputSchema.parse(input);
20103
20688
  const branchDrainTimeoutMs = resolveBranchDrainTimeoutMs(options?.branchDrainTimeoutMs);
20104
- const executionId = (0, import_node_crypto4.randomUUID)();
20689
+ const executionId = (0, import_node_crypto5.randomUUID)();
20105
20690
  const controller = new AbortController();
20106
20691
  this.abortControllers.set(executionId, controller);
20107
20692
  const cleanupAbortForwarder = forwardAbortSignal(options?.signal, controller);
@@ -20216,7 +20801,7 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
20216
20801
  const controller = new AbortController();
20217
20802
  axlStream.on("close", () => controller.abort());
20218
20803
  const cleanupAbortForwarder = forwardAbortSignal(options?.signal, controller);
20219
- const executionId = (0, import_node_crypto4.randomUUID)();
20804
+ const executionId = (0, import_node_crypto5.randomUUID)();
20220
20805
  this.abortControllers.set(executionId, controller);
20221
20806
  let execInfo;
20222
20807
  let wfCtx;
@@ -20817,22 +21402,17 @@ var AxlRuntime = class extends import_node_events2.EventEmitter {
20817
21402
  /**
20818
21403
  * Summarize a list of chat messages into a concise summary string.
20819
21404
  * Used by Session to summarize dropped messages when history.summarize is enabled.
21405
+ * `previousSummary` is folded in so a rolling summary never loses what it
21406
+ * alone still carries. Shares its prompt and provider call with the
21407
+ * `AgentConfig.maxContext` summarizer (see `compaction.ts`). It runs outside
21408
+ * any workflow execution, so it emits no ask-scoped events.
20820
21409
  */
20821
- async summarizeMessages(messages, modelUri) {
21410
+ async summarizeMessages(messages, modelUri, options) {
20822
21411
  const { provider, model } = this.resolveProvider(modelUri);
20823
- const response = await provider.chat(
20824
- [
20825
- {
20826
- role: "system",
20827
- content: "Summarize the following conversation concisely, preserving key facts, decisions, and context needed for continuing the conversation."
20828
- },
20829
- {
20830
- role: "user",
20831
- content: messages.map((m) => `${m.role}: ${summarizeModelInput(m.content)}`).join("\n")
20832
- }
20833
- ],
20834
- { model, maxTokens: 1024 }
20835
- );
21412
+ const response = await requestSummary(provider, {
21413
+ model,
21414
+ prompt: buildSummaryPrompt(messages, options?.previousSummary)
21415
+ });
20836
21416
  return response.content;
20837
21417
  }
20838
21418
  /** Get the state store (for testing and advanced use cases). */
@@ -22263,6 +22843,7 @@ function cosineSimilarity2(a, b) {
22263
22843
  TranscriptionProviderRegistry,
22264
22844
  UnsupportedEventSchemaVersionError,
22265
22845
  UnsupportedModelInputError,
22846
+ UnsupportedModelOptionError,
22266
22847
  UnsupportedTranscriptionInputError,
22267
22848
  VLLM_PROFILE,
22268
22849
  ValidationError,