@pensar/apex 2.6.1 → 2.7.0-canary.6d9d1e0d

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.
Files changed (144) hide show
  1. package/build/agent-davhjnxc.js +13 -0
  2. package/build/{agent-yby11zaq.js → agent-gk4vmr2h.js} +6 -6
  3. package/build/agent-mqqd38dq.js +13 -0
  4. package/build/{agent-runs-g37vkrjr.js → agent-runs-12pmj6vc.js} +16 -16
  5. package/build/{agent-ayreaj6z.js → agent-xtn1masf.js} +5 -5
  6. package/build/{apps-xn6km9yn.js → apps-yjyvn8vk.js} +13 -13
  7. package/build/{auth-d105wc64.js → auth-xzp4f1bh.js} +13 -13
  8. package/build/{blackboxAgent-s56sgx9b.js → blackboxAgent-ettjqj45.js} +6 -6
  9. package/build/{blackboxPentest-ps78v3w7.js → blackboxPentest-kf0pbhp5.js} +11 -11
  10. package/build/{cli-c05y640n.js → cli-0cwdrrq7.js} +1 -1
  11. package/build/{cli-fy6vh2a7.js → cli-0ry9v2cb.js} +1 -1
  12. package/build/{cli-7eea4e2y.js → cli-4mar7kvr.js} +6 -6
  13. package/build/{cli-k2nwxta0.js → cli-5gaxk6hd.js} +2 -2
  14. package/build/{cli-d37a42dc.js → cli-8hxrn15h.js} +8 -8
  15. package/build/{cli-az3xq70v.js → cli-arxx5hbs.js} +1 -1
  16. package/build/{cli-n2en7j5b.js → cli-ge8q6nxr.js} +1 -1
  17. package/build/{cli-1ve18npr.js → cli-gr4az5sf.js} +1 -1
  18. package/build/{cli-709rvekb.js → cli-h5wkaqwe.js} +1 -1
  19. package/build/{cli-65g6rcat.js → cli-hprqe9j2.js} +1 -1
  20. package/build/{cli-kb21qhvm.js → cli-m5ryqeav.js} +2 -2
  21. package/build/{cli-xtc2t6me.js → cli-metec8r8.js} +3 -3
  22. package/build/{cli-dpsw2fe6.js → cli-nj73990n.js} +353 -185
  23. package/build/{cli-ae8jqxqr.js → cli-pchzt5f7.js} +2 -2
  24. package/build/{cli-3sp6nd8d.js → cli-t3m0e9t2.js} +2 -2
  25. package/build/{cli-qgz1q9g5.js → cli-tfaqp0ty.js} +105 -36
  26. package/build/{cli-gx71jcke.js → cli-xpspbhqs.js} +1 -1
  27. package/build/{cli-ngq9dr8n.js → cli-zmeb85er.js} +3 -3
  28. package/build/cli.js +37 -37
  29. package/build/{config-zpqkd5cz.js → config-n03g5jad.js} +1 -1
  30. package/build/{fastStrike-mcjdvwez.js → fastStrike-9mys4r1b.js} +5 -5
  31. package/build/{fixes-kytx5dmj.js → fixes-tcd29w2t.js} +13 -13
  32. package/build/{index-je6hcsqr.js → index-3n19dh6s.js} +1 -1
  33. package/build/{index-wqetp6gq.js → index-42dnvkns.js} +5 -5
  34. package/build/{index-fv64eb32.js → index-6ptkm3fe.js} +2 -2
  35. package/build/{index-qv4cc65p.js → index-93br69mn.js} +13 -13
  36. package/build/{index-4vqdgb42.js → index-hh9bqe4y.js} +1236 -1195
  37. package/build/{index-dr3tg1w3.js → index-k1pvqjaq.js} +1 -1
  38. package/build/{index-r9yjkfrv.js → index-mmmx59w3.js} +4 -4
  39. package/build/{index-n65z6v7b.js → index-msb8ht2s.js} +6 -6
  40. package/build/{index-ns4m4s01.js → index-qtpprc5a.js} +4 -4
  41. package/build/{issues-eb3q9d3a.js → issues-d0e1tzda.js} +13 -13
  42. package/build/{knowledge-fqsvtx0f.js → knowledge-vgvrzey3.js} +13 -13
  43. package/build/lib/{chunk-1azdancq.js → chunk-0ec46jyd.js} +1 -1
  44. package/build/lib/{chunk-9yksqgzr.js → chunk-29bvrw6m.js} +11 -11
  45. package/build/lib/{chunk-z137x08b.js → chunk-3ac8kcj9.js} +5 -5
  46. package/build/lib/{chunk-bb0ekhkw.js → chunk-3g3ksmsv.js} +8 -8
  47. package/build/lib/{chunk-9tt2021m.js → chunk-45nvxhrc.js} +9 -9
  48. package/build/lib/{chunk-w66ce82a.js → chunk-66t5k77r.js} +1 -1
  49. package/build/lib/{chunk-r4j8ybn9.js → chunk-74ddy2e7.js} +37 -20
  50. package/build/lib/{chunk-4hwzqthr.js → chunk-851r7zp8.js} +1 -1
  51. package/build/lib/{chunk-1fy8azee.js → chunk-8m15nv6s.js} +1 -1
  52. package/build/lib/{chunk-7pwa92rn.js → chunk-9etzp9n2.js} +1 -1
  53. package/build/lib/{chunk-xhdpgpkm.js → chunk-acfewabw.js} +2 -2
  54. package/build/lib/{chunk-ba41jh41.js → chunk-bdbd8d8q.js} +297 -127
  55. package/build/lib/{chunk-z96pbjhd.js → chunk-c6gj39ew.js} +1 -1
  56. package/build/lib/{chunk-yrnrbjes.js → chunk-fytt5jz7.js} +1 -1
  57. package/build/lib/{chunk-3c00n0cy.js → chunk-gv1p302b.js} +3 -3
  58. package/build/lib/{chunk-1yct6cra.js → chunk-j1ftnbgr.js} +1 -1
  59. package/build/lib/{chunk-nhfax4gb.js → chunk-mepzy07f.js} +1 -1
  60. package/build/lib/{chunk-e8yy2k73.js → chunk-mqcwxrnh.js} +1 -1
  61. package/build/lib/{chunk-v3dk9j7k.js → chunk-n3kr09mb.js} +11 -11
  62. package/build/lib/{chunk-acgbyf5h.js → chunk-pd487tpr.js} +1 -1
  63. package/build/lib/{chunk-1jvwqnem.js → chunk-rchnq1ez.js} +3 -3
  64. package/build/lib/{chunk-2qsq76xq.js → chunk-wa0q3smh.js} +62 -10
  65. package/build/lib/{chunk-mg03jdmt.js → chunk-xw8d7pv7.js} +1 -1
  66. package/build/lib/{chunk-r2dkp4tb.js → chunk-y9m8yw7k.js} +1 -1
  67. package/build/lib/core/agents/offSecAgent/index.js +7 -7
  68. package/build/lib/core/agents/offSecAgent/subagentSpawner.js +5 -5
  69. package/build/lib/core/agents/offSecAgent/tools/index.js +4 -4
  70. package/build/lib/core/agents/offSecAgent/tools/listMemories.d.ts +11 -1
  71. package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +9 -9
  72. package/build/lib/core/agents/specialized/attackSurface/schemas.js +9 -9
  73. package/build/lib/core/agents/specialized/authenticationAgent/index.js +10 -10
  74. package/build/lib/core/agents/specialized/codeAgent/agent.js +9 -9
  75. package/build/lib/core/agents/specialized/findingJudge/agent.js +8 -8
  76. package/build/lib/core/agents/specialized/findingJudge/index.js +3 -3
  77. package/build/lib/core/agents/specialized/patching/index.js +10 -10
  78. package/build/lib/core/agents/specialized/pentest/agent.js +9 -9
  79. package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +9 -9
  80. package/build/lib/core/ai/ai.d.ts +23 -6
  81. package/build/lib/core/ai/ai.js +2 -2
  82. package/build/lib/core/ai/index.js +2 -2
  83. package/build/lib/core/ai/models/claude-capabilities.d.ts +3 -2
  84. package/build/lib/core/ai/models/index.d.ts +1 -1
  85. package/build/lib/core/ai/utils.d.ts +2 -0
  86. package/build/lib/core/ai/utils.js +4 -2
  87. package/build/lib/core/api/index.js +19 -19
  88. package/build/lib/core/api/targetedPentest.js +10 -10
  89. package/build/lib/core/config/index.js +1 -1
  90. package/build/lib/core/findings/registry.js +3 -3
  91. package/build/lib/core/session/index.js +2 -2
  92. package/build/lib/core/session/persistence.js +3 -3
  93. package/build/lib/core/workflows/pentest.js +15 -15
  94. package/build/lib/core/workflows/seams.js +4 -4
  95. package/build/lib/core/workflows/whiteboxAttackSurface.js +11 -11
  96. package/build/lib/index.js +22 -22
  97. package/build/{logs-x62b7hk1.js → logs-mvrvrkfa.js} +13 -13
  98. package/build/{native-rollout-evidence-c9yhfqcs.js → native-rollout-evidence-h2za8fyf.js} +3 -3
  99. package/build/{offesecAgent-5whnpeta.js → offesecAgent-r4hz607z.js} +4 -4
  100. package/build/pentest-52s2dn28.js +22 -0
  101. package/build/{pentests-e0f32aqx.js → pentests-yvt9wvxd.js} +13 -13
  102. package/build/recordedRunClient-6z78az3b.js +11 -0
  103. package/build/{targetedPentest-rcbs4nvp.js → targetedPentest-6aeg3nxd.js} +6 -6
  104. package/build/{targets-xj9g3q0z.js → targets-a38xnsgm.js} +13 -13
  105. package/build/threatModel-1gsb7ytk.js +21 -0
  106. package/build/{uninstall-7s784q4a.js → uninstall-kt485ay4.js} +1 -1
  107. package/build/{upload-4gvhwmvr.js → upload-ahw3are7.js} +4 -4
  108. package/build/{utils-cjrz64pc.js → utils-yqdepzr3.js} +3 -3
  109. package/package.json +1 -1
  110. package/src/core/agents/offSecAgent/offensiveSecurityAgent.test.ts +92 -5
  111. package/src/core/agents/offSecAgent/offensiveSecurityAgent.ts +49 -28
  112. package/src/core/agents/offSecAgent/tools/listMemories.test.ts +230 -0
  113. package/src/core/agents/offSecAgent/tools/listMemories.ts +73 -6
  114. package/src/core/ai/ai.operator-context.bench.test.ts +1408 -0
  115. package/src/core/ai/ai.recorded-context.test.ts +74 -0
  116. package/src/core/ai/ai.test.ts +54 -0
  117. package/src/core/ai/ai.ts +299 -80
  118. package/src/core/ai/compactionTelemetry.test.ts +2 -2
  119. package/src/core/ai/models/anthropic.ts +6 -0
  120. package/src/core/ai/models/bedrock.ts +18 -0
  121. package/src/core/ai/models/claude-capabilities.ts +29 -8
  122. package/src/core/ai/models/index.ts +4 -1
  123. package/src/core/ai/models/models.test.ts +23 -0
  124. package/src/core/ai/models/openrouter.ts +6 -0
  125. package/src/core/ai/providers/anthropic-models.test.ts +60 -1
  126. package/src/core/ai/providers/bedrock-models.test.ts +81 -0
  127. package/src/core/ai/providers/gateway-models.test.ts +16 -6
  128. package/src/core/ai/utils.native-rollout-evidence.test.ts +1 -0
  129. package/src/core/ai/utils.ts +189 -143
  130. package/src/core/observability/telemetry.test.ts +2 -3
  131. package/src/tui/components/chat/loading-indicator.tsx +20 -4
  132. package/src/tui/components/chat/message-list.tsx +1 -0
  133. package/src/tui/components/operator-dashboard/run-events.test.ts +3 -1
  134. package/src/tui/components/shared/__tests__/tool-renderer-lifecycle.tsx +18 -0
  135. package/src/tui/components/shared/action-label.test.ts +9 -0
  136. package/src/tui/components/shared/compacting-icon.tsx +18 -0
  137. package/src/tui/components/shared/index.ts +1 -0
  138. package/src/tui/components/shared/tool-registry.ts +1 -0
  139. package/src/tui/components/shared/tool-renderer.tsx +21 -7
  140. package/build/agent-6w7egcf1.js +0 -13
  141. package/build/agent-9zx75feh.js +0 -13
  142. package/build/pentest-e61hc38s.js +0 -22
  143. package/build/recordedRunClient-dp032c32.js +0 -11
  144. package/build/threatModel-jkxfa40s.js +0 -21
package/src/core/ai/ai.ts CHANGED
@@ -51,6 +51,9 @@ import {
51
51
  } from "./compactionTelemetry";
52
52
  import {
53
53
  estimateMessageTokens,
54
+ estimateTokens,
55
+ estimateToolsOverheadTokens,
56
+ type FitContextResult,
54
57
  fitMessagesToContext,
55
58
  truncateWithMarker,
56
59
  } from "./contextManagement";
@@ -60,6 +63,7 @@ import {
60
63
  runWithInferenceRecorder,
61
64
  } from "./inference-attempt";
62
65
  import {
66
+ type ClaudeThinkingEffort,
63
67
  getClaudeCapabilities,
64
68
  getMaxOutputTokens,
65
69
  getModelInfo,
@@ -74,6 +78,7 @@ import { OPENAI_PRO_TIMEOUT_MS } from "./providers/openai-pro-fetch";
74
78
  import { STREAM_DEBUG } from "./streamTelemetry";
75
79
  import {
76
80
  type AIAuthConfig,
81
+ CompactionRejectedError,
77
82
  checkIfContextLengthError,
78
83
  createSummarizationStream,
79
84
  getProviderModel,
@@ -136,11 +141,11 @@ export const DEFAULT_OPENAI_REASONING_EFFORT: OpenAIReasoningEffort = "medium";
136
141
 
137
142
  /**
138
143
  * Anthropic adaptive-thinking effort hint. Soft guidance for how much the model
139
- * reasons (see AWS "adaptive thinking"): `low` minimizes thinking, `max`
140
- * reasons hardest (`high` is the model default). Only meaningful for models that
141
- * support adaptive thinking (Claude Opus/Sonnet 4.6+); ignored elsewhere.
144
+ * reasons (see AWS "adaptive thinking"): `low` minimizes thinking while
145
+ * `xhigh` and `max` allow progressively deeper work. Only meaningful for
146
+ * models that support adaptive thinking; ignored elsewhere.
142
147
  */
143
- export type ThinkingEffort = "low" | "medium" | "high" | "xhigh" | "max";
148
+ export type ThinkingEffort = ClaudeThinkingEffort;
144
149
 
145
150
  const OPENAI_REASONING_MODEL_IDS = new Set([
146
151
  "gpt-6.1-sol",
@@ -676,6 +681,43 @@ function createToolExecutionGate(): {
676
681
  };
677
682
  }
678
683
 
684
+ class InStreamCompactionRequired extends Error {
685
+ constructor(readonly fitted: FitContextResult) {
686
+ super("In-stream compaction required before provider dispatch");
687
+ }
688
+ }
689
+
690
+ // Retry once after new tool output, then let fitting handle this turn.
691
+ const MAX_PROACTIVE_SUMMARY_FAILURES = 2;
692
+
693
+ function shouldSummarizeBeforeFitting(
694
+ messages: ModelMessage[],
695
+ contextWindow: number,
696
+ maxOutputTokens: number,
697
+ overheadTokens: number,
698
+ safetyMarginTokens: number,
699
+ system?: string,
700
+ toolChoice?: ToolChoice<ToolSet>,
701
+ ): boolean {
702
+ const historyBudget =
703
+ contextWindow -
704
+ maxOutputTokens -
705
+ overheadTokens -
706
+ safetyMarginTokens -
707
+ (system ? estimateTokens(system) : 0);
708
+ const estimatedHistoryTokens = estimateMessageTokens(messages);
709
+ // Forced-tool replays missed the summary cache; keep their fitted history path.
710
+ return (
711
+ historyBudget > 0 &&
712
+ messages.length > 2 &&
713
+ toolChoice !== "required" &&
714
+ typeof toolChoice !== "object" &&
715
+ estimatedHistoryTokens >=
716
+ Math.max(100_000, Math.floor(historyBudget * 0.8)) &&
717
+ estimatedHistoryTokens <= historyBudget
718
+ );
719
+ }
720
+
679
721
  // Helper function to wrap a stream with error handling for async errors
680
722
  function wrapStreamWithErrorHandler(
681
723
  originalStream: StreamTextResult<ToolSet, never>,
@@ -786,7 +828,10 @@ function wrapStreamWithErrorHandler(
786
828
 
787
829
  // Check context length FIRST — these should never be retried
788
830
  // as-is; the prompt must be reduced via summarization.
789
- const isCtxError = checkIfContextLengthError(error);
831
+ const isCtxError =
832
+ error instanceof InStreamCompactionRequired ||
833
+ error instanceof CompactionRejectedError ||
834
+ checkIfContextLengthError(error);
790
835
 
791
836
  // A latched persistence failure is terminal: retrying would
792
837
  // dispatch a turn whose context was never durably committed.
@@ -893,42 +938,50 @@ function wrapStreamWithErrorHandler(
893
938
  }
894
939
 
895
940
  if (isCtxError) {
896
- let currentMessages: ModelMessage[] = messagesContainer.current;
897
- try {
898
- const response = await originalStream.response;
899
- if (response.messages && response.messages.length > 0) {
900
- currentMessages = response.messages as ModelMessage[];
941
+ let currentMessages: ModelMessage[] =
942
+ error instanceof InStreamCompactionRequired
943
+ ? error.fitted.messages
944
+ : messagesContainer.current;
945
+ if (!(error instanceof InStreamCompactionRequired)) {
946
+ try {
947
+ const response = await originalStream.response;
948
+ if (response.messages && response.messages.length > 0) {
949
+ currentMessages = response.messages as ModelMessage[];
950
+ }
951
+ } catch {
952
+ // Fall back to container messages.
901
953
  }
902
- } catch {
903
- // Fall back to container messages.
904
954
  }
905
955
 
906
- const fitted = fitMessagesToContext(currentMessages, {
907
- contextWindow: getContextWindow(
908
- opts.model,
909
- opts.authConfig?.customProviders,
910
- opts.authConfig?.hoonifyModels,
911
- ),
912
- maxOutputTokens: getMaxOutputTokens(
913
- opts.model,
914
- opts.authConfig?.customProviders,
915
- opts.authConfig?.hoonifyModels,
916
- ),
917
- system: applySequentialToolCallPolicy(
918
- opts.system,
919
- opts.tools,
920
- opts.model,
921
- ),
922
- tools: opts.tools,
923
- sessionPath: opts.sessionPath,
924
- telemetry: {
925
- trigger: "context_overflow",
926
- model: opts.model,
927
- sessionId: opts.sessionId,
928
- restartDepth: opts._restartDepth,
929
- previous: opts._compaction?.last,
930
- },
931
- });
956
+ const fitted =
957
+ error instanceof InStreamCompactionRequired
958
+ ? error.fitted
959
+ : fitMessagesToContext(currentMessages, {
960
+ contextWindow: getContextWindow(
961
+ opts.model,
962
+ opts.authConfig?.customProviders,
963
+ opts.authConfig?.hoonifyModels,
964
+ ),
965
+ maxOutputTokens: getMaxOutputTokens(
966
+ opts.model,
967
+ opts.authConfig?.customProviders,
968
+ opts.authConfig?.hoonifyModels,
969
+ ),
970
+ system: applySequentialToolCallPolicy(
971
+ opts.system,
972
+ opts.tools,
973
+ opts.model,
974
+ ),
975
+ tools: opts.tools,
976
+ sessionPath: opts.sessionPath,
977
+ telemetry: {
978
+ trigger: "context_overflow",
979
+ model: opts.model,
980
+ sessionId: opts.sessionId,
981
+ restartDepth: opts._restartDepth,
982
+ previous: opts._compaction?.last,
983
+ },
984
+ });
932
985
 
933
986
  if (fitted.compaction && opts._compaction)
934
987
  opts._compaction.last = fitted.compaction;
@@ -944,7 +997,11 @@ function wrapStreamWithErrorHandler(
944
997
  // `MAX_RESTART_DEPTH` finally trips.
945
998
  let postReactiveDepth = opts._restartDepth ?? 0;
946
999
 
947
- if (fitted.fitsBudget && fitted.modified) {
1000
+ if (
1001
+ !(error instanceof CompactionRejectedError) &&
1002
+ fitted.fitsBudget &&
1003
+ fitted.modified
1004
+ ) {
948
1005
  if (!silent) {
949
1006
  log.warn(
950
1007
  `Context length error — Layer 1+2 reduced to ~${fitted.estimatedInputTokens} tokens, retrying`,
@@ -1017,12 +1074,19 @@ function wrapStreamWithErrorHandler(
1017
1074
  }
1018
1075
 
1019
1076
  try {
1077
+ if (error instanceof CompactionRejectedError) throw error;
1020
1078
  const summarizationStream = createSummarizationStream(
1021
1079
  messagesForSummary,
1022
1080
  {
1023
1081
  ...opts,
1024
1082
  _restartDepth: postReactiveDepth,
1025
- _compactionTrigger: "context_overflow",
1083
+ _compactionTrigger:
1084
+ error instanceof InStreamCompactionRequired
1085
+ ? "proactive"
1086
+ : "context_overflow",
1087
+ _proactiveSummaryMayFit:
1088
+ error instanceof InStreamCompactionRequired &&
1089
+ error.fitted.fitsBudget,
1026
1090
  },
1027
1091
  model,
1028
1092
  );
@@ -1030,7 +1094,7 @@ function wrapStreamWithErrorHandler(
1030
1094
  yield chunk;
1031
1095
  }
1032
1096
  } catch (summarizeError) {
1033
- // Only fall through to minimal-restart for context-length
1097
+ // Fall through to minimal restart for context or rejected-summary
1034
1098
  // errors. For everything else — auth failures, network
1035
1099
  // errors, abort signals, the typed terminal
1036
1100
  // `ContextLengthExhaustedError`, or non-`Error` throws —
@@ -1043,7 +1107,10 @@ function wrapStreamWithErrorHandler(
1043
1107
  (summarizeError instanceof Error &&
1044
1108
  summarizeError.name === "AbortError") ||
1045
1109
  opts.abortSignal?.aborted ||
1046
- !checkIfContextLengthError(summarizeError)
1110
+ !(
1111
+ checkIfContextLengthError(summarizeError) ||
1112
+ summarizeError instanceof CompactionRejectedError
1113
+ )
1047
1114
  ) {
1048
1115
  throw summarizeError;
1049
1116
  }
@@ -1279,12 +1346,19 @@ export type ReasoningProviderOptions = {
1279
1346
  effort?: ThinkingEffort;
1280
1347
  };
1281
1348
  bedrock?: {
1282
- reasoningConfig: {
1283
- type: "adaptive";
1284
- display: "summarized";
1285
- // Bedrock's channel for the adaptive effort hint (maps to Claude's
1286
- // output_config.effort). Omitted when no level requested.
1287
- maxReasoningEffort?: ThinkingEffort;
1349
+ reasoningConfig:
1350
+ | {
1351
+ type: "adaptive";
1352
+ display: "summarized";
1353
+ // Bedrock's channel for the adaptive effort hint (maps to Claude's
1354
+ // output_config.effort).
1355
+ maxReasoningEffort?: ThinkingEffort;
1356
+ }
1357
+ | { type: "disabled" };
1358
+ /** Explicitly forwarded for Anthropic because the Bedrock SDK currently
1359
+ * drops reasoningConfig.type="disabled" instead of sending it. */
1360
+ additionalModelRequestFields?: {
1361
+ thinking: { type: "disabled" };
1288
1362
  };
1289
1363
  };
1290
1364
  openai?: OpenAIResponsesProviderOptions;
@@ -1360,8 +1434,8 @@ export function buildReasoningProviderOptions(
1360
1434
  enableThinking?: boolean;
1361
1435
  /**
1362
1436
  * Adaptive-thinking effort hint for Anthropic models that support it
1363
- * (Opus/Sonnet 4.6+). Ignored on models without adaptive support. When
1364
- * omitted the model uses its own default (`high`).
1437
+ * Ignored on models without adaptive support. When omitted, models in the
1438
+ * Claude capability registry use their documented provider default.
1365
1439
  */
1366
1440
  thinkingEffort?: ThinkingEffort | null;
1367
1441
  openAIReasoningEffort?: OpenAIReasoningEffort | null;
@@ -1371,11 +1445,12 @@ export function buildReasoningProviderOptions(
1371
1445
  if (claude) {
1372
1446
  const provider = getModelInfo(model).provider;
1373
1447
  const thinking = !!opts.enableThinking || claude.alwaysOnThinking;
1448
+ const effort = opts.thinkingEffort ?? claude.defaultEffort;
1374
1449
  if (provider === "openrouter") {
1375
1450
  return {
1376
1451
  openrouter: {
1377
1452
  reasoning: {
1378
- effort: thinking ? (opts.thinkingEffort ?? "high") : "none",
1453
+ effort: thinking ? effort : "none",
1379
1454
  enabled: thinking,
1380
1455
  },
1381
1456
  },
@@ -1385,19 +1460,38 @@ export function buildReasoningProviderOptions(
1385
1460
  return {
1386
1461
  openai: {
1387
1462
  forceReasoning: true,
1388
- reasoningEffort: thinking ? (opts.thinkingEffort ?? "high") : "none",
1463
+ reasoningEffort: thinking ? effort : "none",
1389
1464
  reasoningSummary: "auto",
1390
1465
  },
1391
1466
  };
1392
1467
  }
1393
1468
  return {
1394
1469
  anthropic: {
1395
- thinking:
1396
- opts.enableThinking || claude.alwaysOnThinking
1397
- ? { type: "adaptive", display: "summarized" }
1398
- : { type: "disabled" },
1399
- ...(opts.thinkingEffort ? { effort: opts.thinkingEffort } : {}),
1470
+ thinking: thinking
1471
+ ? { type: "adaptive", display: "summarized" }
1472
+ : { type: "disabled" },
1473
+ ...(thinking ? { effort } : {}),
1400
1474
  },
1475
+ ...(provider === "bedrock"
1476
+ ? {
1477
+ bedrock: {
1478
+ reasoningConfig: thinking
1479
+ ? {
1480
+ type: "adaptive" as const,
1481
+ display: "summarized" as const,
1482
+ maxReasoningEffort: effort,
1483
+ }
1484
+ : { type: "disabled" as const },
1485
+ ...(!thinking
1486
+ ? {
1487
+ additionalModelRequestFields: {
1488
+ thinking: { type: "disabled" as const },
1489
+ },
1490
+ }
1491
+ : {}),
1492
+ },
1493
+ }
1494
+ : {}),
1401
1495
  };
1402
1496
  }
1403
1497
  const useThinking =
@@ -1474,6 +1568,11 @@ export interface StreamResponseOpts {
1474
1568
  * new messages (not the full pre-summarization history).
1475
1569
  */
1476
1570
  onSummarized?: (summary: string) => void;
1571
+ /** The fitted context and prior generated-message count for transcript writers. */
1572
+ onContextPrepared?: (context: {
1573
+ messages: ModelMessage[];
1574
+ responseMessageOffset: number;
1575
+ }) => void;
1477
1576
  /**
1478
1577
  * Deprecated host shim: fires only when a cache counter is `> 0`.
1479
1578
  * Apex internals read cache from {@link normalizeStepUsage} on the step.
@@ -1506,6 +1605,8 @@ export interface StreamResponseOpts {
1506
1605
  /** Shared only by continuations of this stream, never across agents. */
1507
1606
  _compaction?: { last?: CompactionLink };
1508
1607
  _compactionTrigger?: CompactionTrigger;
1608
+ _proactiveSummaryMayFit?: boolean;
1609
+ _proactiveSummaryFailures?: number;
1509
1610
  }
1510
1611
 
1511
1612
  const NATIVE_STREAM_RECOVERY = Symbol("native-stream-recovery");
@@ -1581,13 +1682,19 @@ function streamResponseWithinOperation(
1581
1682
  thinkingEffort,
1582
1683
  openAIReasoningEffort,
1583
1684
  sessionId,
1685
+ sessionPath,
1584
1686
  contextRecorder,
1687
+ onContextPrepared,
1585
1688
  } = opts;
1586
1689
 
1587
- // Recorded-run base, captured once at the first prepareStep of this
1588
- // streamText call; only used to reconstruct cumulative prefixes in
1589
- // onStepFinish (response.messages excludes the input base).
1690
+ // response.messages accumulates generated steps after prepareStep rewrites
1691
+ // provider context. The offset prevents transcript writers from appending
1692
+ // older, pre-fit messages again.
1590
1693
  let recorderBase: ModelMessage[] | undefined;
1694
+ let responseMessageLength = 0;
1695
+ const estimatedOverheadTokens = estimateToolsOverheadTokens(tools) + 1_000;
1696
+ let preparedInputEstimateTokens = 0;
1697
+ let inputSafetyMarginTokens = 10_000;
1591
1698
 
1592
1699
  // Wrap onStepFinish to fire cache metrics and the usage callback for every
1593
1700
  // step. Must be async so that callers returning a Promise (persistence,
@@ -1596,6 +1703,18 @@ function streamResponseWithinOperation(
1596
1703
  // provider metadata is never parsed twice.
1597
1704
  const onStepFinish: typeof userOnStepFinish = async (step) => {
1598
1705
  const stepUsage = normalizeStepUsage(step);
1706
+ if (
1707
+ preparedInputEstimateTokens > 0 &&
1708
+ stepUsage.inputTokens > 0 &&
1709
+ step.response?.id !== "summarization" &&
1710
+ step.response?.id !== "tool-repair"
1711
+ ) {
1712
+ // Fixed system and tool overhead is already reserved by the fitter; calibrate only the remaining tokenization gap.
1713
+ inputSafetyMarginTokens = Math.max(
1714
+ inputSafetyMarginTokens,
1715
+ stepUsage.inputTokens - preparedInputEstimateTokens + 10_000,
1716
+ );
1717
+ }
1599
1718
  // Canonical commit precedes every downstream use; the recorder latches
1600
1719
  // rejections (SDK notify() swallows them) until the next turn gate or
1601
1720
  // agent-side flush turns the latch into a stream failure.
@@ -1611,10 +1730,20 @@ function streamResponseWithinOperation(
1611
1730
  throw new Error("context recorder active without a committed base");
1612
1731
  }
1613
1732
  await contextRecorder.checkpoint({
1614
- messages: [...recorderBase, ...step.response.messages],
1733
+ messages: [
1734
+ ...recorderBase,
1735
+ ...step.response.messages.slice(responseMessageLength),
1736
+ ],
1615
1737
  system: effectiveSystem ?? null,
1616
1738
  });
1617
1739
  }
1740
+ if (
1741
+ step.response?.id !== "summarization" &&
1742
+ step.response?.id !== "tool-repair" &&
1743
+ step.response?.messages
1744
+ ) {
1745
+ responseMessageLength = step.response.messages.length;
1746
+ }
1618
1747
  if (
1619
1748
  onCacheMetrics &&
1620
1749
  (stepUsage.cacheReadTokens > 0 || stepUsage.cacheWriteTokens > 0)
@@ -1661,22 +1790,54 @@ function streamResponseWithinOperation(
1661
1790
  model,
1662
1791
  );
1663
1792
 
1793
+ const contextWindow = getContextWindow(
1794
+ model,
1795
+ authConfig?.customProviders,
1796
+ authConfig?.hoonifyModels,
1797
+ );
1798
+ const maxOutputTokens = getMaxOutputTokens(
1799
+ model,
1800
+ authConfig?.customProviders,
1801
+ authConfig?.hoonifyModels,
1802
+ );
1803
+ if (
1804
+ messages &&
1805
+ !opts._proactiveSummaryFailures &&
1806
+ shouldSummarizeBeforeFitting(
1807
+ messages,
1808
+ contextWindow,
1809
+ maxOutputTokens,
1810
+ estimatedOverheadTokens,
1811
+ inputSafetyMarginTokens,
1812
+ systemWithToolPolicy,
1813
+ toolChoice,
1814
+ )
1815
+ ) {
1816
+ return wrapStreamWithErrorHandler(
1817
+ createSummarizationStream(
1818
+ messages,
1819
+ {
1820
+ ...opts,
1821
+ _compactionTrigger: "proactive",
1822
+ _proactiveSummaryMayFit: true,
1823
+ },
1824
+ providerModel,
1825
+ ),
1826
+ { current: messages },
1827
+ opts,
1828
+ providerModel,
1829
+ silent,
1830
+ );
1831
+ }
1832
+
1664
1833
  // Proactive context fit: compact before the call. Reactive recovery
1665
1834
  // below is a safety net for tokenizer drift the estimate doesn't catch.
1666
1835
  let fittedMessages = messages;
1667
1836
  let proactiveFitFailed = false;
1668
1837
  if (messages && messages.length > 0) {
1669
1838
  const fitted = fitMessagesToContext(messages, {
1670
- contextWindow: getContextWindow(
1671
- model,
1672
- authConfig?.customProviders,
1673
- authConfig?.hoonifyModels,
1674
- ),
1675
- maxOutputTokens: getMaxOutputTokens(
1676
- model,
1677
- authConfig?.customProviders,
1678
- authConfig?.hoonifyModels,
1679
- ),
1839
+ contextWindow,
1840
+ maxOutputTokens,
1680
1841
  system: systemWithToolPolicy,
1681
1842
  tools,
1682
1843
  sessionPath: opts.sessionPath,
@@ -1723,7 +1884,11 @@ function streamResponseWithinOperation(
1723
1884
  return wrapStreamWithErrorHandler(
1724
1885
  createSummarizationStream(
1725
1886
  fittedMessages,
1726
- { ...opts, _compactionTrigger: "proactive" },
1887
+ {
1888
+ ...opts,
1889
+ _compactionTrigger: "proactive",
1890
+ _proactiveSummaryMayFit: false,
1891
+ },
1727
1892
  providerModel,
1728
1893
  ),
1729
1894
  messagesContainer,
@@ -1818,28 +1983,82 @@ function streamResponseWithinOperation(
1818
1983
  // Awaiting inside prepareStep keeps streamResponse synchronous; the
1819
1984
  // SDK awaits it before provider dispatch, so every turn and every
1820
1985
  // recovery re-entry commits before use.
1821
- prepareStep: async (opts) => {
1822
- if (contextRecorder) {
1823
- if (recorderBase === undefined) {
1824
- recorderBase = structuredClone(opts.messages);
1986
+ prepareStep: async (step) => {
1987
+ let stepMessages = step.messages;
1988
+ if (step.stepNumber > 0) {
1989
+ if (
1990
+ (opts._proactiveSummaryFailures ?? 0) <
1991
+ MAX_PROACTIVE_SUMMARY_FAILURES &&
1992
+ shouldSummarizeBeforeFitting(
1993
+ stepMessages,
1994
+ contextWindow,
1995
+ maxOutputTokens,
1996
+ estimatedOverheadTokens,
1997
+ inputSafetyMarginTokens,
1998
+ effectiveSystem,
1999
+ toolChoice,
2000
+ )
2001
+ ) {
2002
+ messagesContainer.current = stepMessages;
2003
+ throw new InStreamCompactionRequired({
2004
+ messages: stepMessages,
2005
+ estimatedInputTokens: estimateMessageTokens(stepMessages),
2006
+ modified: false,
2007
+ fitsBudget: true,
2008
+ });
1825
2009
  }
2010
+ const fitted = fitMessagesToContext(stepMessages, {
2011
+ contextWindow,
2012
+ maxOutputTokens,
2013
+ system: effectiveSystem,
2014
+ tools,
2015
+ overheadTokens: estimatedOverheadTokens,
2016
+ sessionPath,
2017
+ safetyMarginTokens: inputSafetyMarginTokens,
2018
+ telemetry: {
2019
+ trigger: "proactive",
2020
+ model,
2021
+ sessionId,
2022
+ restartDepth,
2023
+ previous: compactionState.last,
2024
+ },
2025
+ });
2026
+ if (fitted.compaction) compactionState.last = fitted.compaction;
2027
+ stepMessages = fitted.messages;
2028
+ if (!fitted.fitsBudget) {
2029
+ messagesContainer.current = stepMessages;
2030
+ throw new InStreamCompactionRequired(fitted);
2031
+ }
2032
+ }
2033
+ if (contextRecorder) {
2034
+ recorderBase = structuredClone(stepMessages);
1826
2035
  // Full logical context each turn — accepted step outputs must
1827
2036
  // survive into every later commit, and system is explicit (when
1828
2037
  // cached into messages, effectiveSystem is undefined → null).
1829
2038
  await contextRecorder.checkpoint({
1830
- messages: opts.messages,
2039
+ messages: stepMessages,
1831
2040
  system: effectiveSystem ?? null,
1832
2041
  });
1833
2042
  }
1834
2043
  // Update the container with the latest messages
1835
- messagesContainer.current = opts.messages;
2044
+ messagesContainer.current = stepMessages;
2045
+ preparedInputEstimateTokens =
2046
+ estimateMessageTokens(stepMessages) +
2047
+ estimatedOverheadTokens +
2048
+ (effectiveSystem ? estimateTokens(effectiveSystem) : 0);
2049
+ onContextPrepared?.({
2050
+ messages: stepMessages,
2051
+ responseMessageOffset: responseMessageLength,
2052
+ });
1836
2053
  // Mark the last message so the growing conversation caches incrementally
1837
2054
  if (cacheBreakpoint) {
1838
2055
  return {
1839
- messages: withCachedLastMessage(opts.messages, cacheBreakpoint),
2056
+ messages: withCachedLastMessage(stepMessages, cacheBreakpoint),
1840
2057
  };
1841
2058
  }
1842
- return undefined;
2059
+ return stepMessages === step.messages
2060
+ ? undefined
2061
+ : { messages: stepMessages };
1843
2062
  },
1844
2063
  onError: async ({ error }: { error: unknown }) => {
1845
2064
  // Never throw here: the SDK calls this inside its stream pipeline,
@@ -387,7 +387,7 @@ describe("compaction observability", () => {
387
387
  ).toBe(true);
388
388
  });
389
389
 
390
- it("preserves tool evidence omitted by the summarizer and links its actual request and response", async () => {
390
+ it("passes tool evidence to the summarizer and links its actual request and response", async () => {
391
391
  const summaryModel = model();
392
392
  state.model = summaryModel;
393
393
  const messages: ModelMessage[] = [
@@ -422,7 +422,7 @@ describe("compaction observability", () => {
422
422
  expect(summary.attributes["ai.prompt.messages"]).toContain(
423
423
  "continue researching",
424
424
  );
425
- expect(summary.attributes["ai.prompt.messages"]).not.toContain(
425
+ expect(summary.attributes["ai.prompt.messages"]).toContain(
426
426
  "evidence evidence",
427
427
  );
428
428
  expect(summary.attributes["ai.response.text"]).toBe("A compact summary");
@@ -112,6 +112,12 @@ export const ANTHROPIC_MODELS: ModelInfo[] = [
112
112
  provider: "anthropic",
113
113
  contextLength: 1000000,
114
114
  },
115
+ {
116
+ id: "claude-haiku-5-5",
117
+ name: "Claude Haiku 5.5",
118
+ provider: "anthropic",
119
+ contextLength: 1000000,
120
+ },
115
121
  {
116
122
  id: "claude-sonnet-5-5",
117
123
  name: "Claude Sonnet 5.5",
@@ -465,6 +465,18 @@ export const BEDROCK_MODELS: ModelInfo[] = [
465
465
  provider: "bedrock",
466
466
  contextLength: 262000,
467
467
  },
468
+ {
469
+ id: "anthropic.claude-haiku-5-5",
470
+ name: "Claude Haiku 5.5 (Bedrock)",
471
+ provider: "bedrock",
472
+ contextLength: 1000000,
473
+ },
474
+ {
475
+ id: "us.anthropic.claude-haiku-5-5",
476
+ name: "Claude Haiku 5.5 (US)",
477
+ provider: "bedrock",
478
+ contextLength: 1000000,
479
+ },
468
480
  {
469
481
  id: "anthropic.claude-opus-4-8",
470
482
  name: "Claude Opus 4.8 (Bedrock)",
@@ -597,4 +609,10 @@ export const BEDROCK_MODELS: ModelInfo[] = [
597
609
  provider: "bedrock",
598
610
  contextLength: 1000000,
599
611
  },
612
+ {
613
+ id: "global.anthropic.claude-haiku-5-5",
614
+ name: "Claude Haiku 5.5 (Global)",
615
+ provider: "bedrock",
616
+ contextLength: 1000000,
617
+ },
600
618
  ];