@librechat/agents 3.7.7 → 3.7.9

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 (167) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +26 -5
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/constants.cjs +12 -0
  4. package/dist/cjs/common/constants.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +56 -11
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
  8. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  9. package/dist/cjs/hooks/executeHooks.cjs +8 -0
  10. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  11. package/dist/cjs/hooks/index.cjs +2 -0
  12. package/dist/cjs/hooks/index.cjs.map +1 -1
  13. package/dist/cjs/hooks/types.cjs +1 -0
  14. package/dist/cjs/hooks/types.cjs.map +1 -1
  15. package/dist/cjs/langfuse.cjs +43 -3
  16. package/dist/cjs/langfuse.cjs.map +1 -1
  17. package/dist/cjs/langfuseTraceShaping.cjs +75 -0
  18. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  19. package/dist/cjs/llm/bedrock/toolCache.cjs +1 -1
  20. package/dist/cjs/llm/fake.cjs +9 -5
  21. package/dist/cjs/llm/fake.cjs.map +1 -1
  22. package/dist/cjs/llm/invoke.cjs +3 -7
  23. package/dist/cjs/llm/invoke.cjs.map +1 -1
  24. package/dist/cjs/llm/openrouter/toolCache.cjs +1 -1
  25. package/dist/cjs/llm/prepareProviderRequest.cjs +2 -2
  26. package/dist/cjs/llm/providers.cjs +1 -1
  27. package/dist/cjs/llm/request.cjs +6 -0
  28. package/dist/cjs/llm/request.cjs.map +1 -1
  29. package/dist/cjs/llm/truncation.cjs +1 -0
  30. package/dist/cjs/main.cjs +22 -13
  31. package/dist/cjs/messages/format.cjs +375 -3
  32. package/dist/cjs/messages/format.cjs.map +1 -1
  33. package/dist/cjs/messages/index.cjs +1 -1
  34. package/dist/cjs/messages/prune.cjs +1 -1
  35. package/dist/cjs/run.cjs +217 -52
  36. package/dist/cjs/run.cjs.map +1 -1
  37. package/dist/cjs/session/AgentSession.cjs +1 -1
  38. package/dist/cjs/session/JsonlSessionStore.cjs +1 -1
  39. package/dist/cjs/stream.cjs +1 -1
  40. package/dist/cjs/summarization/index.cjs +1 -0
  41. package/dist/cjs/summarization/index.cjs.map +1 -1
  42. package/dist/cjs/summarization/node.cjs +58 -85
  43. package/dist/cjs/summarization/node.cjs.map +1 -1
  44. package/dist/cjs/summarization/semanticIndex.cjs +366 -0
  45. package/dist/cjs/summarization/semanticIndex.cjs.map +1 -0
  46. package/dist/cjs/summarization/shared.cjs +83 -0
  47. package/dist/cjs/summarization/shared.cjs.map +1 -0
  48. package/dist/cjs/tools/ToolNode.cjs +5 -5
  49. package/dist/cjs/tools/local/CompileCheckTool.cjs +1 -1
  50. package/dist/cjs/tools/local/LocalCodingTools.cjs +1 -1
  51. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +1 -1
  52. package/dist/cjs/tools/runStepResume.cjs +1 -1
  53. package/dist/cjs/tools/runStepResume.cjs.map +1 -1
  54. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +8 -12
  55. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  56. package/dist/cjs/tools/subagent/childGraphConfig.cjs +2 -1
  57. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
  58. package/dist/cjs/utils/index.cjs +1 -1
  59. package/dist/cjs/utils/tokens.cjs +8 -1
  60. package/dist/cjs/utils/tokens.cjs.map +1 -1
  61. package/dist/esm/agents/AgentContext.mjs +26 -5
  62. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  63. package/dist/esm/common/constants.mjs +11 -1
  64. package/dist/esm/common/constants.mjs.map +1 -1
  65. package/dist/esm/graphs/Graph.mjs +56 -11
  66. package/dist/esm/graphs/Graph.mjs.map +1 -1
  67. package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
  68. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  69. package/dist/esm/hooks/executeHooks.mjs +8 -1
  70. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  71. package/dist/esm/hooks/index.mjs +2 -1
  72. package/dist/esm/hooks/index.mjs.map +1 -1
  73. package/dist/esm/hooks/types.mjs +1 -0
  74. package/dist/esm/hooks/types.mjs.map +1 -1
  75. package/dist/esm/langfuse.mjs +43 -3
  76. package/dist/esm/langfuse.mjs.map +1 -1
  77. package/dist/esm/langfuseTraceShaping.mjs +75 -0
  78. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  79. package/dist/esm/llm/bedrock/toolCache.mjs +1 -1
  80. package/dist/esm/llm/fake.mjs +9 -5
  81. package/dist/esm/llm/fake.mjs.map +1 -1
  82. package/dist/esm/llm/invoke.mjs +3 -7
  83. package/dist/esm/llm/invoke.mjs.map +1 -1
  84. package/dist/esm/llm/openrouter/toolCache.mjs +1 -1
  85. package/dist/esm/llm/prepareProviderRequest.mjs +2 -2
  86. package/dist/esm/llm/providers.mjs +1 -1
  87. package/dist/esm/llm/request.mjs +6 -1
  88. package/dist/esm/llm/request.mjs.map +1 -1
  89. package/dist/esm/llm/truncation.mjs +1 -1
  90. package/dist/esm/main.mjs +15 -14
  91. package/dist/esm/messages/format.mjs +375 -3
  92. package/dist/esm/messages/format.mjs.map +1 -1
  93. package/dist/esm/messages/index.mjs +1 -1
  94. package/dist/esm/messages/prune.mjs +1 -1
  95. package/dist/esm/run.mjs +217 -52
  96. package/dist/esm/run.mjs.map +1 -1
  97. package/dist/esm/session/AgentSession.mjs +1 -1
  98. package/dist/esm/session/JsonlSessionStore.mjs +1 -1
  99. package/dist/esm/stream.mjs +1 -1
  100. package/dist/esm/summarization/index.mjs +1 -0
  101. package/dist/esm/summarization/index.mjs.map +1 -1
  102. package/dist/esm/summarization/node.mjs +57 -84
  103. package/dist/esm/summarization/node.mjs.map +1 -1
  104. package/dist/esm/summarization/semanticIndex.mjs +363 -0
  105. package/dist/esm/summarization/semanticIndex.mjs.map +1 -0
  106. package/dist/esm/summarization/shared.mjs +79 -0
  107. package/dist/esm/summarization/shared.mjs.map +1 -0
  108. package/dist/esm/tools/ToolNode.mjs +5 -5
  109. package/dist/esm/tools/local/CompileCheckTool.mjs +1 -1
  110. package/dist/esm/tools/local/LocalCodingTools.mjs +1 -1
  111. package/dist/esm/tools/local/LocalExecutionEngine.mjs +1 -1
  112. package/dist/esm/tools/runStepResume.mjs +1 -1
  113. package/dist/esm/tools/runStepResume.mjs.map +1 -1
  114. package/dist/esm/tools/subagent/SubagentExecutor.mjs +8 -12
  115. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  116. package/dist/esm/tools/subagent/childGraphConfig.mjs +2 -1
  117. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
  118. package/dist/esm/utils/index.mjs +1 -1
  119. package/dist/esm/utils/tokens.mjs +8 -2
  120. package/dist/esm/utils/tokens.mjs.map +1 -1
  121. package/dist/types/agents/AgentContext.d.ts +27 -1
  122. package/dist/types/common/constants.d.ts +17 -0
  123. package/dist/types/graphs/Graph.d.ts +24 -0
  124. package/dist/types/hooks/executeHooks.d.ts +5 -2
  125. package/dist/types/hooks/index.d.ts +7 -2
  126. package/dist/types/hooks/types.d.ts +45 -7
  127. package/dist/types/langfuse.d.ts +3 -1
  128. package/dist/types/llm/fake.d.ts +12 -2
  129. package/dist/types/llm/request.d.ts +10 -0
  130. package/dist/types/messages/format.d.ts +17 -2
  131. package/dist/types/run.d.ts +19 -6
  132. package/dist/types/summarization/index.d.ts +6 -0
  133. package/dist/types/summarization/node.d.ts +0 -4
  134. package/dist/types/summarization/semanticIndex.d.ts +21 -0
  135. package/dist/types/summarization/shared.d.ts +25 -0
  136. package/dist/types/types/graph.d.ts +9 -1
  137. package/dist/types/types/run.d.ts +8 -0
  138. package/dist/types/types/stream.d.ts +6 -0
  139. package/dist/types/types/summarize.d.ts +44 -0
  140. package/dist/types/utils/tokens.d.ts +9 -0
  141. package/package.json +2 -1
  142. package/src/agents/AgentContext.ts +63 -6
  143. package/src/common/constants.ts +19 -0
  144. package/src/graphs/Graph.ts +86 -8
  145. package/src/hooks/HookRegistry.ts +3 -1
  146. package/src/hooks/executeHooks.ts +20 -2
  147. package/src/hooks/index.ts +12 -1
  148. package/src/hooks/types.ts +47 -4
  149. package/src/langfuse.ts +70 -0
  150. package/src/langfuseTraceShaping.ts +94 -0
  151. package/src/llm/fake.ts +35 -4
  152. package/src/llm/invoke.ts +13 -29
  153. package/src/llm/request.ts +24 -0
  154. package/src/messages/format.ts +839 -5
  155. package/src/run.ts +393 -187
  156. package/src/summarization/index.ts +11 -0
  157. package/src/summarization/node.ts +202 -158
  158. package/src/summarization/semanticIndex.ts +662 -0
  159. package/src/summarization/shared.ts +130 -0
  160. package/src/tools/runStepResume.ts +9 -0
  161. package/src/tools/subagent/SubagentExecutor.ts +4 -27
  162. package/src/tools/subagent/childGraphConfig.ts +3 -0
  163. package/src/types/graph.ts +9 -0
  164. package/src/types/run.ts +8 -0
  165. package/src/types/stream.ts +6 -0
  166. package/src/types/summarize.ts +57 -0
  167. package/src/utils/tokens.ts +31 -8
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Summarization primitives shared by the in-run summarize node and by callers
3
+ * that compact a conversation outside a run. Kept apart from `node.ts` so the
4
+ * package can export them without exporting the graph node itself.
5
+ */
6
+
7
+ /**
8
+ * Instruction that follows the summary body inside the carrier. Private on
9
+ * purpose: it is only ever correct alongside `buildSummaryCarrierText`, and a
10
+ * caller that reaches for the text separately is a caller that can drift from
11
+ * the accounting.
12
+ */
13
+ const SUMMARY_CARRIER_INSTRUCTION =
14
+ 'This is your own checkpoint: you wrote it to preserve context after compaction. Pick up where you left off based on the summary above. Do not repeat prior tasks, information or acknowledge this checkpoint message directly.';
15
+
16
+ /**
17
+ * Wraps a persisted summary in the carrier it is re-injected as, ahead of the
18
+ * messages that survived compaction.
19
+ *
20
+ * A stored summary costs what this returns, not what its body costs, so a
21
+ * caller budgeting for one measures this rather than adding a remembered
22
+ * constant to the bare text. The wrapper alone is ~48 tokens on `o200k_base`
23
+ * and more on Anthropic: too much to leave out of a context calculation, and
24
+ * too easy to get wrong from memory once the instruction is edited.
25
+ */
26
+ export function buildSummaryCarrierText(summaryText: string): string {
27
+ return (
28
+ '<summary>\n' +
29
+ summaryText +
30
+ '\n</summary>\n\n' +
31
+ SUMMARY_CARRIER_INSTRUCTION
32
+ );
33
+ }
34
+
35
+ /** Structured checkpoint prompt for fresh summarization (no prior summary). */
36
+ export const DEFAULT_SUMMARIZATION_PROMPT = `Hold on, before you continue I need you to write me a checkpoint of everything so far. Your context window is filling up and this checkpoint replaces the messages above, so capture everything you need to pick right back up.
37
+
38
+ Don't second-guess or fact-check anything you did, your tool results reflect exactly what happened. If a tool result appears truncated, that's just a display artifact from context management: the tool executed fully. Just record what you did and what you observed. Only the checkpoint, don't respond to me or continue the conversation.
39
+
40
+ ## Checkpoint
41
+
42
+ ## Goal
43
+ What I asked you to do and any sub-goals you identified.
44
+
45
+ ## Constraints & Preferences
46
+ Any rules, preferences, or configuration I established.
47
+
48
+ ## Progress
49
+ ### Done
50
+ - What you completed and the outcomes
51
+
52
+ ### In Progress
53
+ - What you're currently working on
54
+
55
+ ## Key Decisions
56
+ Decisions you made and why.
57
+
58
+ ## Next Steps
59
+ Concrete task actions remaining, in priority order.
60
+
61
+ ## Critical Context
62
+ Exact identifiers, names, error messages, URLs, and details you need to preserve verbatim.
63
+
64
+ Rules:
65
+ - Record what you did and observed, don't judge or re-evaluate it
66
+ - For each tool call: the tool name, key inputs, and the outcome
67
+ - Preserve exact identifiers, names, errors, and references verbatim
68
+ - Short declarative sentences
69
+ - Skip empty sections`;
70
+
71
+ /** Prompt for re-compaction when a prior summary exists. */
72
+ export const DEFAULT_UPDATE_SUMMARIZATION_PROMPT = `Hold on again, update your checkpoint. Merge the new messages into your existing checkpoint and give me a single consolidated replacement.
73
+
74
+ Keep it roughly the same length as your last checkpoint. Compress older details to make room for what's new, don't just append. Give recent actions more detail, compress older items to one-liners.
75
+
76
+ Don't fact-check or second-guess anything, your tool results are ground truth. If a tool result appears truncated, that's just a display artifact: the tool executed fully. Only the checkpoint, don't respond to me or continue the conversation.
77
+
78
+ Rules:
79
+ - Merge new progress into existing sections, don't duplicate headers
80
+ - Compress older completed items into one-line entries
81
+ - Move items from "In Progress" to "Done" when you completed them
82
+ - Update "Next Steps" to reflect current task priorities.
83
+ - For each new tool call: the tool name, key inputs, and the outcome
84
+ - Preserve exact identifiers, names, errors, and references verbatim
85
+ - Skip empty sections`;
86
+
87
+ const SUMMARIZATION_PARAM_KEYS = new Set(['maxSummaryTokens']);
88
+
89
+ export function separateSummarizationParameters(
90
+ parameters: Record<string, unknown>
91
+ ): {
92
+ llmParams: Record<string, unknown>;
93
+ maxSummaryTokens?: number;
94
+ } {
95
+ const llmParams: Record<string, unknown> = {};
96
+ let maxSummaryTokens: number | undefined;
97
+
98
+ for (const [key, value] of Object.entries(parameters)) {
99
+ if (SUMMARIZATION_PARAM_KEYS.has(key)) {
100
+ if (
101
+ key === 'maxSummaryTokens' &&
102
+ typeof value === 'number' &&
103
+ value > 0
104
+ ) {
105
+ maxSummaryTokens = value;
106
+ }
107
+ } else {
108
+ llmParams[key] = value;
109
+ }
110
+ }
111
+
112
+ return { llmParams, maxSummaryTokens };
113
+ }
114
+
115
+ export function buildSummarizationInstruction(
116
+ promptText: string,
117
+ updatePromptText: string | undefined,
118
+ priorSummaryText?: string,
119
+ semanticIndexAppendix = ''
120
+ ): string {
121
+ const prior = priorSummaryText?.trim() ?? '';
122
+ const effectivePrompt = prior ? (updatePromptText ?? promptText) : promptText;
123
+ const parts = semanticIndexAppendix
124
+ ? [semanticIndexAppendix, '\n\n', effectivePrompt]
125
+ : [effectivePrompt];
126
+ if (prior) {
127
+ parts.push(`\n\n<previous-summary>\n${prior}\n</previous-summary>`);
128
+ }
129
+ return parts.join('');
130
+ }
@@ -23,6 +23,15 @@ export function isRunStepResumeState(
23
23
  (state.revision ?? -1) < 0 ||
24
24
  !Number.isSafeInteger(state.nextIndex) ||
25
25
  (state.nextIndex ?? -1) < 0 ||
26
+ (state.stopContinuationCount != null &&
27
+ (!Number.isSafeInteger(state.stopContinuationCount) ||
28
+ state.stopContinuationCount < 0)) ||
29
+ (state.stopContinuationExecutionId != null &&
30
+ (typeof state.stopContinuationExecutionId !== 'string' ||
31
+ state.stopContinuationExecutionId.length === 0)) ||
32
+ (state.streamSegment != null &&
33
+ (!Number.isSafeInteger(state.streamSegment) ||
34
+ state.streamSegment < 0)) ||
26
35
  !Array.isArray(state.toolCallSteps) ||
27
36
  !Array.isArray(state.steps)
28
37
  ) {
@@ -131,6 +131,7 @@ import { stripRunStepResumeState } from '@/tools/runStepResume';
131
131
  import { seedAgentInitialSessions } from '@/utils/toolSessions';
132
132
  import { stableStringify } from '@/tools/eagerEventExecution';
133
133
  import { convertInjectedMessages } from '@/messages/injected';
134
+ import { resolveClientOptionsModel } from '@/llm/request';
134
135
  import { composeAbortSignals } from '@/utils/misc';
135
136
  import { HandlerRegistry } from '@/events';
136
137
 
@@ -1091,8 +1092,7 @@ export class SubagentExecutor {
1091
1092
  return JSON.stringify({
1092
1093
  status: 'rejected',
1093
1094
  tool: Constants.SUBAGENT,
1094
- message:
1095
- 'Child-thread continuation is not enabled by this host.',
1095
+ message: 'Child-thread continuation is not enabled by this host.',
1096
1096
  });
1097
1097
  }
1098
1098
  const detachedHandlers = new HandlerRegistry();
@@ -2261,9 +2261,7 @@ export class SubagentExecutor {
2261
2261
  }
2262
2262
  if (this.maxDepth <= 0) {
2263
2263
  return Promise.resolve(
2264
- createSubagentFailure(
2265
- 'Error: Maximum subagent nesting depth exceeded.'
2266
- )
2264
+ createSubagentFailure('Error: Maximum subagent nesting depth exceeded.')
2267
2265
  );
2268
2266
  }
2269
2267
  if (
@@ -3300,7 +3298,7 @@ function createUsageCaptureHandler(args: {
3300
3298
  defaultMember?.[1];
3301
3299
  const model =
3302
3300
  callInfo?.model ??
3303
- (memberInput == null ? undefined : extractConfiguredModel(memberInput));
3301
+ resolveClientOptionsModel(memberInput?.clientOptions);
3304
3302
  const callProvider = callInfo?.provider ?? memberInput?.provider;
3305
3303
  for (const generationGroup of output.generations) {
3306
3304
  /**
@@ -3423,27 +3421,6 @@ function getGraphEventMemberAgentId({
3423
3421
  : undefined;
3424
3422
  }
3425
3423
 
3426
- /**
3427
- * Best-effort read of the configured model from a subagent's client
3428
- * options. Providers disagree on the key (`model` vs `modelName`), and the
3429
- * value is only a fallback for calls that carry no `ls_model_name`.
3430
- */
3431
- function extractConfiguredModel(agentInputs: AgentInputs): string | undefined {
3432
- const clientOptions = agentInputs.clientOptions as
3433
- | { model?: unknown; modelName?: unknown }
3434
- | undefined;
3435
- if (typeof clientOptions?.model === 'string' && clientOptions.model !== '') {
3436
- return clientOptions.model;
3437
- }
3438
- if (
3439
- typeof clientOptions?.modelName === 'string' &&
3440
- clientOptions.modelName !== ''
3441
- ) {
3442
- return clientOptions.modelName;
3443
- }
3444
- return undefined;
3445
- }
3446
-
3447
3424
  function sanitizeResolverConfigurable(
3448
3425
  parentConfigurable: Record<string, unknown>
3449
3426
  ): SubagentResolveConfigurable | undefined {
@@ -600,6 +600,9 @@ function prepareChildInputs({
600
600
  initialSummary: undefined,
601
601
  discoveredTools: undefined,
602
602
  graphTools: self ? undefined : agentInputs.graphTools,
603
+ compactionSemanticIndex: self
604
+ ? undefined
605
+ : agentInputs.compactionSemanticIndex,
603
606
  };
604
607
  if (allowNested) {
605
608
  childInputs.maxSubagentDepth = Math.max(0, parentMaxDepth - 1);
@@ -13,6 +13,7 @@ import type { GoogleAIToolType } from '@langchain/google-common';
13
13
  import type {
14
14
  SummarizationNodeInput,
15
15
  SummarizeCompleteEvent,
16
+ CompactionSemanticIndex,
16
17
  SummarizationConfig,
17
18
  SummarizeStartEvent,
18
19
  SummarizeDeltaEvent,
@@ -871,6 +872,14 @@ interface AgentInputFields {
871
872
  discoveredTools?: string[];
872
873
  summarizationEnabled?: boolean;
873
874
  summarizationConfig?: SummarizationConfig;
875
+ /**
876
+ * Optional host-supplied, user-visible guidance for compaction. The SDK
877
+ * validates, bounds, and scopes entries to the messages being compacted;
878
+ * raw conversation messages remain authoritative. Captured when the
879
+ * AgentContext is constructed; labels committed later in the same run are
880
+ * outside this construction-time interface.
881
+ */
882
+ compactionSemanticIndex?: CompactionSemanticIndex;
874
883
  /** Cross-run summary from a previous run, forwarded from formatAgentMessages.
875
884
  * Injected into the dynamic system tail via AgentContext. */
876
885
  initialSummary?: { text: string; tokenCount: number };
package/src/types/run.ts CHANGED
@@ -252,6 +252,14 @@ export type RunConfig = {
252
252
  * block to prevent leaks.
253
253
  */
254
254
  hooks?: HookRegistry;
255
+ /**
256
+ * Maximum number of times a `Stop` hook may keep this Run warm by returning
257
+ * `decision: 'block'` with messages to inject. Defaults to
258
+ * `DEFAULT_MAX_STOP_CONTINUATIONS`; non-finite values use the default and
259
+ * values at or below zero disable terminal continuation while preserving the
260
+ * final Stop notification.
261
+ */
262
+ maxStopContinuations?: number;
255
263
  /**
256
264
  * Opt-in cooperative preemption for this run. Requires a `hooks` registry
257
265
  * with a `PreemptBoundary` matcher — the seal only stops the stream, the
@@ -116,6 +116,12 @@ export interface RunStepResumeState {
116
116
  version: 1;
117
117
  revision: number;
118
118
  nextIndex: number;
119
+ /** Warm terminal continuations already admitted for this execution. */
120
+ stopContinuationCount?: number;
121
+ /** Identifies the fresh execution that owns this continuation lifecycle. */
122
+ stopContinuationExecutionId?: string;
123
+ /** Distinguishes LangGraph event keys when a warm continuation restarts steps. */
124
+ streamSegment?: number;
119
125
  toolCallSteps: Array<{ toolCallId: string; stepId: string }>;
120
126
  steps: RunStepResumeEntry[];
121
127
  }
@@ -36,6 +36,59 @@ export type RetainRecentConfig = {
36
36
  tokens?: number;
37
37
  };
38
38
 
39
+ export type CompactionSemanticIndexStatus = 'committed' | 'pending';
40
+
41
+ type CompactionSemanticIndexEntryBase = {
42
+ /** Persisted message that owns the indexed content. */
43
+ sourceMessageId: string;
44
+ /** Zero-based content-part index within the persisted source message. */
45
+ sourceContentIndex: number;
46
+ /** Monotonic host revision for this logical entry. */
47
+ revision: number;
48
+ /** Only committed entries may guide compaction. */
49
+ status: CompactionSemanticIndexStatus;
50
+ /** User-visible semantic guidance. Hidden reasoning must never be supplied. */
51
+ text: string;
52
+ /** Omits the entry entirely when host policy redacts its source. */
53
+ redacted?: boolean;
54
+ };
55
+
56
+ export type CompactionToolSemanticIndexEntry =
57
+ CompactionSemanticIndexEntryBase & {
58
+ type: 'tool_intent' | 'tool_outcome';
59
+ toolCallId: string;
60
+ };
61
+
62
+ export type CompactionActivitySemanticIndexEntry =
63
+ CompactionSemanticIndexEntryBase & {
64
+ type: 'activity_phase';
65
+ };
66
+
67
+ export type CompactionReasoningSemanticIndexEntry =
68
+ CompactionSemanticIndexEntryBase & {
69
+ type: 'reasoning_label';
70
+ /** Stable identity shared by every user-visible label revision. */
71
+ reasoningStepId: string;
72
+ };
73
+
74
+ /**
75
+ * Source-addressed navigation hints for the compaction model. Entries remain
76
+ * advisory: raw messages are always sent and remain authoritative.
77
+ */
78
+ export type CompactionSemanticIndexEntry =
79
+ | CompactionToolSemanticIndexEntry
80
+ | CompactionActivitySemanticIndexEntry
81
+ | CompactionReasoningSemanticIndexEntry;
82
+
83
+ export type CompactionSemanticIndex = readonly CompactionSemanticIndexEntry[];
84
+
85
+ /** Serializable continuation state for a bounded compaction semantic index. */
86
+ export type CompactionSemanticIndexSnapshot = {
87
+ entries: CompactionSemanticIndex;
88
+ /** Cumulative entries supplied before validation and bounded retention. */
89
+ providedEntryCount: number;
90
+ };
91
+
39
92
  export type SummarizationConfig = {
40
93
  provider?: ProviderName;
41
94
  model?: string;
@@ -96,6 +149,10 @@ export interface SummarizeStartEvent {
96
149
  messagesToRefineCount: number;
97
150
  /** Which summarization cycle this is (1-based, increments each time summarization fires) */
98
151
  summaryVersion: number;
152
+ /** Committed, source-valid semantic hints included in the request. */
153
+ semanticIndexEntryCount?: number;
154
+ /** Serialized semantic-index characters included in the request. */
155
+ semanticIndexCharCount?: number;
99
156
  }
100
157
 
101
158
  export interface SummarizeDeltaEvent {
@@ -6,8 +6,8 @@ import {
6
6
  isAtomicToolContentBlock,
7
7
  serializeStructuredValueBounded,
8
8
  } from './toolContent';
9
- import { ContentTypes } from '@/common/enum';
10
9
  import { markTokenCounterCacheCompatible } from '@/llm/tokenCounterCacheCompatibility';
10
+ import { ContentTypes } from '@/common/enum';
11
11
 
12
12
  export type EncodingName = 'o200k_base' | 'claude';
13
13
 
@@ -1257,6 +1257,25 @@ export function apportionTokenCounts(
1257
1257
  */
1258
1258
  const CLAUDE_TOKEN_CORRECTION = 1.1;
1259
1259
 
1260
+ const tokenCounterEncodings = new WeakMap<
1261
+ (message: BaseMessage) => number,
1262
+ EncodingName
1263
+ >();
1264
+
1265
+ /**
1266
+ * Encoding a counter measures in, for counters built here.
1267
+ *
1268
+ * `undefined` for a counter the host supplied itself: unknown, not wrong. A
1269
+ * caller that needs a count in a specific encoding can therefore tell "counts
1270
+ * in the encoding I need" from "counts in a different one" without treating
1271
+ * every foreign counter as suspect.
1272
+ */
1273
+ export function encodingOfTokenCounter(
1274
+ tokenCounter: (message: BaseMessage) => number
1275
+ ): EncodingName | undefined {
1276
+ return tokenCounterEncodings.get(tokenCounter);
1277
+ }
1278
+
1260
1279
  /**
1261
1280
  * Creates a token counter function using the specified encoding.
1262
1281
  * Lazily loads the encoding data on first use via dynamic import.
@@ -1267,13 +1286,17 @@ export const createTokenCounter = async (
1267
1286
  const tok = await getTokenizer(encoding);
1268
1287
  const countTokens = (text: string): number => tok.count(text);
1269
1288
  const isClaude = encoding === 'claude';
1270
- return markTokenCounterCacheCompatible((message: BaseMessage): number => {
1271
- const count = getTokenCountForMessage(message, countTokens, encoding);
1272
- const correctedCount = isClaude
1273
- ? Math.ceil(count * CLAUDE_TOKEN_CORRECTION)
1274
- : count;
1275
- return ensureSafeTokenMeasurement(correctedCount, 'message');
1276
- });
1289
+ const counter = markTokenCounterCacheCompatible(
1290
+ (message: BaseMessage): number => {
1291
+ const count = getTokenCountForMessage(message, countTokens, encoding);
1292
+ const correctedCount = isClaude
1293
+ ? Math.ceil(count * CLAUDE_TOKEN_CORRECTION)
1294
+ : count;
1295
+ return ensureSafeTokenMeasurement(correctedCount, 'message');
1296
+ }
1297
+ );
1298
+ tokenCounterEncodings.set(counter, encoding);
1299
+ return counter;
1277
1300
  };
1278
1301
 
1279
1302
  /** Utility to manage the token encoder lifecycle explicitly. */