@librechat/agents 3.3.12 → 3.4.0
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/cjs/graphs/Graph.cjs +10 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +1 -0
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuseSpanRegistry.cjs +6 -3
- package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +35 -206
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +121 -241
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +19 -12
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/mistral/index.cjs +26 -0
- package/dist/cjs/llm/mistral/index.cjs.map +1 -0
- package/dist/cjs/llm/openai/index.cjs +82 -80
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/providers.cjs +19 -19
- package/dist/cjs/llm/providers.cjs.map +1 -1
- package/dist/cjs/llm/stream/chunkAdapters.cjs +198 -0
- package/dist/cjs/llm/stream/chunkAdapters.cjs.map +1 -0
- package/dist/cjs/llm/stream/smoother.cjs +369 -0
- package/dist/cjs/llm/stream/smoother.cjs.map +1 -0
- package/dist/cjs/llm/vertexai/index.cjs +13 -1
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +18 -10
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +34 -2
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +76 -29
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +10 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +1 -0
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuseSpanRegistry.mjs +6 -3
- package/dist/esm/langfuseSpanRegistry.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +34 -205
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +120 -240
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +19 -12
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/mistral/index.mjs +26 -0
- package/dist/esm/llm/mistral/index.mjs.map +1 -0
- package/dist/esm/llm/openai/index.mjs +82 -80
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/providers.mjs +3 -3
- package/dist/esm/llm/providers.mjs.map +1 -1
- package/dist/esm/llm/stream/chunkAdapters.mjs +195 -0
- package/dist/esm/llm/stream/chunkAdapters.mjs.map +1 -0
- package/dist/esm/llm/stream/smoother.mjs +365 -0
- package/dist/esm/llm/stream/smoother.mjs.map +1 -0
- package/dist/esm/llm/vertexai/index.mjs +13 -1
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -2
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +34 -2
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +76 -30
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +3 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/llm/google/index.d.ts +2 -0
- package/dist/types/llm/mistral/index.d.ts +11 -0
- package/dist/types/llm/openai/index.d.ts +20 -4
- package/dist/types/llm/openrouter/index.d.ts +4 -1
- package/dist/types/llm/stream/chunkAdapters.d.ts +48 -0
- package/dist/types/llm/stream/smoother.d.ts +95 -0
- package/dist/types/llm/vertexai/index.d.ts +2 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +3 -0
- package/dist/types/types/graph.d.ts +5 -0
- package/dist/types/types/llm.d.ts +21 -14
- package/dist/types/utils/tokens.d.ts +10 -0
- package/package.json +2 -2
- package/src/graphs/Graph.ts +11 -0
- package/src/index.ts +8 -0
- package/src/instrumentation.ts +1 -0
- package/src/langfuseSpanRegistry.ts +9 -0
- package/src/llm/anthropic/index.ts +85 -354
- package/src/llm/bedrock/index.ts +174 -390
- package/src/llm/bedrock/llm.spec.ts +2 -0
- package/src/llm/bedrock/streamSealDispatch.test.ts +76 -0
- package/src/llm/custom-chat-models.smoke.test.ts +16 -1
- package/src/llm/google/index.ts +17 -18
- package/src/llm/google/streamSmoothing.test.ts +121 -0
- package/src/llm/mistral/index.ts +33 -0
- package/src/llm/mistral/streamSmoothing.test.ts +97 -0
- package/src/llm/openai/deepseek.test.ts +56 -0
- package/src/llm/openai/index.ts +119 -126
- package/src/llm/openrouter/index.ts +4 -1
- package/src/llm/providers.ts +3 -3
- package/src/llm/stream/chunkAdapters.test.ts +202 -0
- package/src/llm/stream/chunkAdapters.ts +317 -0
- package/src/llm/stream/reassembly.test.ts +241 -0
- package/src/llm/stream/smoother.bench.test.ts +155 -0
- package/src/llm/stream/smoother.test.ts +519 -0
- package/src/llm/stream/smoother.ts +574 -0
- package/src/llm/vertexai/index.ts +17 -1
- package/src/llm/vertexai/streamSmoothing.test.ts +109 -0
- package/src/specs/langfuse-instrumentation.test.ts +26 -0
- package/src/specs/langfuse-span-registry.test.ts +17 -0
- package/src/specs/subagent.test.ts +44 -0
- package/src/specs/summarization.test.ts +5 -2
- package/src/specs/tokens.test.ts +159 -14
- package/src/tools/__tests__/SubagentExecutor.test.ts +48 -1
- package/src/tools/subagent/SubagentExecutor.ts +69 -5
- package/src/types/graph.ts +5 -0
- package/src/types/llm.ts +53 -36
- package/src/utils/tokens.ts +115 -30
|
@@ -27,17 +27,24 @@ import type { AggregatedHookResult, HookRegistry } from '@/hooks';
|
|
|
27
27
|
import type { AgentContext } from '@/agents/AgentContext';
|
|
28
28
|
import type { StandardGraph } from '@/graphs/Graph';
|
|
29
29
|
import type { HandlerRegistry } from '@/events';
|
|
30
|
-
import { Constants, GraphEvents, Callback, StepTypes } from '@/common';
|
|
31
30
|
import {
|
|
32
31
|
StreamLimitExceededError,
|
|
33
32
|
RUN_BREAKER_SCOPE_CONFIG_KEY,
|
|
34
33
|
} from '@/llm/streamLimits';
|
|
34
|
+
import {
|
|
35
|
+
ContentTypes,
|
|
36
|
+
Constants,
|
|
37
|
+
GraphEvents,
|
|
38
|
+
Callback,
|
|
39
|
+
StepTypes,
|
|
40
|
+
} from '@/common';
|
|
35
41
|
import { executeHooks } from '@/hooks';
|
|
36
42
|
|
|
37
43
|
const DEFAULT_MAX_TURNS = 25;
|
|
38
44
|
const RECURSION_MULTIPLIER = 3;
|
|
39
45
|
const ERROR_MESSAGE_MAX_CHARS = 200;
|
|
40
46
|
const MAX_PENDING_SUBAGENT_UPDATES = 64;
|
|
47
|
+
const TEXT_DELTA_CONTENT_TYPE = `${ContentTypes.TEXT}_delta`;
|
|
41
48
|
|
|
42
49
|
const HOOK_FALLBACK: AggregatedHookResult = Object.freeze({
|
|
43
50
|
additionalContexts: [] as string[],
|
|
@@ -119,7 +126,10 @@ type SanitizedStepCompleted =
|
|
|
119
126
|
};
|
|
120
127
|
|
|
121
128
|
type SanitizedProcessedToolCall = Partial<
|
|
122
|
-
Pick<
|
|
129
|
+
Pick<
|
|
130
|
+
ProcessedToolCall,
|
|
131
|
+
'args' | 'id' | 'name' | 'output' | 'progress' | 'outcome'
|
|
132
|
+
>
|
|
123
133
|
>;
|
|
124
134
|
|
|
125
135
|
type SanitizedRunStepCompleted = {
|
|
@@ -1401,6 +1411,9 @@ export function summarizeEvent(eventName: string, data: unknown): string {
|
|
|
1401
1411
|
* pure tool_use (e.g. the subagent hit `maxTurns` mid-tool-call), the walk
|
|
1402
1412
|
* continues to earlier AIMessages so partial progress is salvaged — this
|
|
1403
1413
|
* matches Claude Code's behavior in `agentToolUtils.finalizeAgentTool`.
|
|
1414
|
+
* Consecutive streamed text-delta blocks with the same provider index are
|
|
1415
|
+
* coalesced without adding whitespace. Annotation-only text blocks are
|
|
1416
|
+
* ignored; complete text blocks and distinct delta indexes remain separated.
|
|
1404
1417
|
* Returns "Task completed" only when no AIMessage in the history contains
|
|
1405
1418
|
* any text.
|
|
1406
1419
|
*/
|
|
@@ -1422,13 +1435,64 @@ export function filterSubagentResult(messages: BaseMessage[]): string {
|
|
|
1422
1435
|
}
|
|
1423
1436
|
|
|
1424
1437
|
const textParts: string[] = [];
|
|
1438
|
+
let textDeltaParts: string[] = [];
|
|
1439
|
+
let textDeltaIndex: string | number | undefined;
|
|
1440
|
+
const flushTextDeltaParts = (): void => {
|
|
1441
|
+
if (textDeltaParts.length === 0) {
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
textParts.push(textDeltaParts.join(''));
|
|
1445
|
+
textDeltaParts = [];
|
|
1446
|
+
textDeltaIndex = undefined;
|
|
1447
|
+
};
|
|
1425
1448
|
for (const block of content) {
|
|
1426
1449
|
if (typeof block === 'string') {
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1450
|
+
flushTextDeltaParts();
|
|
1451
|
+
if (block !== '') {
|
|
1452
|
+
textParts.push(block);
|
|
1453
|
+
}
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
const type =
|
|
1458
|
+
'type' in block && typeof block.type === 'string' ? block.type : '';
|
|
1459
|
+
const isTextDelta = type === TEXT_DELTA_CONTENT_TYPE;
|
|
1460
|
+
const isText = type === ContentTypes.TEXT || isTextDelta;
|
|
1461
|
+
const text =
|
|
1462
|
+
isText && 'text' in block && typeof block.text === 'string'
|
|
1463
|
+
? block.text
|
|
1464
|
+
: '';
|
|
1465
|
+
if (isTextDelta) {
|
|
1466
|
+
if (text === '') {
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
const index =
|
|
1470
|
+
'index' in block &&
|
|
1471
|
+
(typeof block.index === 'string' || typeof block.index === 'number')
|
|
1472
|
+
? block.index
|
|
1473
|
+
: undefined;
|
|
1474
|
+
if (
|
|
1475
|
+
textDeltaIndex != null &&
|
|
1476
|
+
index != null &&
|
|
1477
|
+
index !== textDeltaIndex
|
|
1478
|
+
) {
|
|
1479
|
+
flushTextDeltaParts();
|
|
1480
|
+
}
|
|
1481
|
+
textDeltaIndex ??= index;
|
|
1482
|
+
textDeltaParts.push(text);
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
if (type === ContentTypes.TEXT && text === '') {
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
flushTextDeltaParts();
|
|
1491
|
+
if (text !== '') {
|
|
1492
|
+
textParts.push(text);
|
|
1430
1493
|
}
|
|
1431
1494
|
}
|
|
1495
|
+
flushTextDeltaParts();
|
|
1432
1496
|
|
|
1433
1497
|
if (textParts.length > 0) {
|
|
1434
1498
|
return textParts.join('\n');
|
package/src/types/graph.ts
CHANGED
|
@@ -571,6 +571,11 @@ export interface LangfuseConfig {
|
|
|
571
571
|
* `default` environment.
|
|
572
572
|
*/
|
|
573
573
|
environment?: string;
|
|
574
|
+
/**
|
|
575
|
+
* Whether the Langfuse span processor should detect and upload base64 media
|
|
576
|
+
* payloads. Defaults to the Langfuse SDK behavior.
|
|
577
|
+
*/
|
|
578
|
+
mediaUploadEnabled?: boolean;
|
|
574
579
|
metadata?: Record<string, string | number | boolean | null | undefined>;
|
|
575
580
|
/**
|
|
576
581
|
* Internal OTLP span attributes to attach to Langfuse observations before
|
package/src/types/llm.ts
CHANGED
|
@@ -45,7 +45,8 @@ export type AzureClientOptions = Partial<OpenAIChatInput> &
|
|
|
45
45
|
deploymentName?: string;
|
|
46
46
|
} & BaseChatModelParams & {
|
|
47
47
|
configuration?: OAIClientOptions;
|
|
48
|
-
} & ManagedRequestOptions
|
|
48
|
+
} & ManagedRequestOptions &
|
|
49
|
+
StreamSmoothingOptions;
|
|
49
50
|
/**
|
|
50
51
|
* Controls whether Claude's reasoning content is returned in adaptive
|
|
51
52
|
* thinking responses. Added for Claude Opus 4.7, which omits thinking by
|
|
@@ -79,47 +80,63 @@ export type ManagedRequestOptions = {
|
|
|
79
80
|
promptCacheExplicit?: boolean;
|
|
80
81
|
safety_identifier?: string;
|
|
81
82
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Prompt-cache breakpoint TTL. Defaults to `'1h'` (extended cache) when
|
|
88
|
-
* `promptCache` is enabled; set `'5m'` to opt back into the legacy
|
|
89
|
-
* 5-minute behavior.
|
|
90
|
-
*/
|
|
91
|
-
promptCacheTtl?: PromptCacheTtl;
|
|
92
|
-
};
|
|
93
|
-
export type MistralAIClientOptions = ChatMistralAIInput;
|
|
94
|
-
export type VertexAIClientOptions = ChatVertexAIInput & {
|
|
95
|
-
includeThoughts?: boolean;
|
|
96
|
-
thinkingConfig?: GoogleThinkingConfig;
|
|
97
|
-
};
|
|
98
|
-
export type BedrockAnthropicInput = ChatBedrockConverseInput & {
|
|
99
|
-
additionalModelRequestFields?: ChatBedrockConverseInput['additionalModelRequestFields'] &
|
|
100
|
-
AnthropicReasoning;
|
|
101
|
-
promptCache?: boolean;
|
|
102
|
-
/**
|
|
103
|
-
* Prompt-cache checkpoint TTL. Defaults to `'1h'` (extended cache) when
|
|
104
|
-
* `promptCache` is enabled; set `'5m'` to opt into the legacy 5-minute
|
|
105
|
-
* behavior. Bedrock models that don't support the 1-hour TTL downgrade to 5m
|
|
106
|
-
* server-side, so the default is safe to leave on.
|
|
107
|
-
*/
|
|
108
|
-
promptCacheTtl?: PromptCacheTtl;
|
|
83
|
+
/**
|
|
84
|
+
* Adaptive stream-smoothing configuration shared by every provider client.
|
|
85
|
+
*/
|
|
86
|
+
export type StreamSmoothingOptions = {
|
|
109
87
|
/**
|
|
110
88
|
* Minimum delay in milliseconds between visible streamed content deltas.
|
|
89
|
+
* Defaults to 25; piece sizes adapt to the backlog so render lag stays
|
|
90
|
+
* bounded regardless of provider chunk size. Set 0 to disable smoothing.
|
|
111
91
|
*/
|
|
112
92
|
_lc_stream_delay?: number;
|
|
113
93
|
};
|
|
94
|
+
|
|
95
|
+
export type OpenAIClientOptions = ChatOpenAIFields &
|
|
96
|
+
ManagedRequestOptions &
|
|
97
|
+
StreamSmoothingOptions;
|
|
98
|
+
export type AnthropicClientOptions = Omit<AnthropicInput, 'thinking'> &
|
|
99
|
+
StreamSmoothingOptions & {
|
|
100
|
+
thinking?: ThinkingConfig;
|
|
101
|
+
promptCache?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Prompt-cache breakpoint TTL. Defaults to `'1h'` (extended cache) when
|
|
104
|
+
* `promptCache` is enabled; set `'5m'` to opt back into the legacy
|
|
105
|
+
* 5-minute behavior.
|
|
106
|
+
*/
|
|
107
|
+
promptCacheTtl?: PromptCacheTtl;
|
|
108
|
+
};
|
|
109
|
+
export type MistralAIClientOptions = ChatMistralAIInput &
|
|
110
|
+
StreamSmoothingOptions;
|
|
111
|
+
export type VertexAIClientOptions = ChatVertexAIInput &
|
|
112
|
+
StreamSmoothingOptions & {
|
|
113
|
+
includeThoughts?: boolean;
|
|
114
|
+
thinkingConfig?: GoogleThinkingConfig;
|
|
115
|
+
};
|
|
116
|
+
export type BedrockAnthropicInput = ChatBedrockConverseInput &
|
|
117
|
+
StreamSmoothingOptions & {
|
|
118
|
+
additionalModelRequestFields?: ChatBedrockConverseInput['additionalModelRequestFields'] &
|
|
119
|
+
AnthropicReasoning;
|
|
120
|
+
promptCache?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Prompt-cache checkpoint TTL. Defaults to `'1h'` (extended cache) when
|
|
123
|
+
* `promptCache` is enabled; set `'5m'` to opt into the legacy 5-minute
|
|
124
|
+
* behavior. Bedrock models that don't support the 1-hour TTL downgrade to 5m
|
|
125
|
+
* server-side, so the default is safe to leave on.
|
|
126
|
+
*/
|
|
127
|
+
promptCacheTtl?: PromptCacheTtl;
|
|
128
|
+
};
|
|
114
129
|
export type BedrockConverseClientOptions = BedrockAnthropicInput;
|
|
115
130
|
export type BedrockAnthropicClientOptions = BedrockAnthropicInput;
|
|
116
|
-
export type GoogleClientOptions = GoogleGenerativeAIChatInput &
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
export type
|
|
131
|
+
export type GoogleClientOptions = GoogleGenerativeAIChatInput &
|
|
132
|
+
StreamSmoothingOptions & {
|
|
133
|
+
customHeaders?: RequestOptions['customHeaders'];
|
|
134
|
+
thinkingConfig?: GoogleThinkingConfig;
|
|
135
|
+
includeServerSideToolInvocations?: boolean;
|
|
136
|
+
};
|
|
137
|
+
export type DeepSeekClientOptions = Partial<ChatDeepSeekInput> &
|
|
138
|
+
StreamSmoothingOptions;
|
|
139
|
+
export type XAIClientOptions = ChatXAIInput & StreamSmoothingOptions;
|
|
123
140
|
|
|
124
141
|
export type ClientOptions =
|
|
125
142
|
| OpenAIClientOptions
|
|
@@ -159,7 +176,7 @@ export type ProviderOptionsMap = {
|
|
|
159
176
|
[Providers.ANTHROPIC]: AnthropicClientOptions;
|
|
160
177
|
[Providers.MISTRALAI]: MistralAIClientOptions;
|
|
161
178
|
[Providers.MISTRAL]: MistralAIClientOptions;
|
|
162
|
-
[Providers.OPENROUTER]: ChatOpenRouterCallOptions;
|
|
179
|
+
[Providers.OPENROUTER]: ChatOpenRouterCallOptions & StreamSmoothingOptions;
|
|
163
180
|
[Providers.BEDROCK]: BedrockAnthropicClientOptions;
|
|
164
181
|
[Providers.XAI]: XAIClientOptions;
|
|
165
182
|
[Providers.MOONSHOT]: OpenAIClientOptions;
|
package/src/utils/tokens.ts
CHANGED
|
@@ -10,6 +10,52 @@ import { ContentTypes } from '@/common/enum';
|
|
|
10
10
|
|
|
11
11
|
export type EncodingName = 'o200k_base' | 'claude';
|
|
12
12
|
|
|
13
|
+
export type UnsafeTokenMeasurementReason =
|
|
14
|
+
| 'message_proxy'
|
|
15
|
+
| 'content_proxy'
|
|
16
|
+
| 'metadata_proxy'
|
|
17
|
+
| 'metadata_accessor'
|
|
18
|
+
| 'invalid_count';
|
|
19
|
+
|
|
20
|
+
export class UnsafeTokenMeasurementError extends Error {
|
|
21
|
+
readonly type = 'unsafe_token_measurement';
|
|
22
|
+
readonly reason: UnsafeTokenMeasurementReason;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
|
|
25
|
+
constructor({
|
|
26
|
+
reason,
|
|
27
|
+
path,
|
|
28
|
+
}: {
|
|
29
|
+
reason: UnsafeTokenMeasurementReason;
|
|
30
|
+
path: string;
|
|
31
|
+
}) {
|
|
32
|
+
super(
|
|
33
|
+
JSON.stringify({
|
|
34
|
+
type: 'unsafe_token_measurement',
|
|
35
|
+
reason,
|
|
36
|
+
path,
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
this.name = 'UnsafeTokenMeasurementError';
|
|
40
|
+
this.reason = reason;
|
|
41
|
+
this.path = path;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ensureSafeTokenMeasurement(value: number, path: string): number {
|
|
46
|
+
if (
|
|
47
|
+
!Number.isSafeInteger(value) ||
|
|
48
|
+
value < 0 ||
|
|
49
|
+
value >= Number.MAX_SAFE_INTEGER
|
|
50
|
+
) {
|
|
51
|
+
throw new UnsafeTokenMeasurementError({
|
|
52
|
+
reason: 'invalid_count',
|
|
53
|
+
path,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
13
59
|
/** Anthropic minimum image token cost. */
|
|
14
60
|
const ANTHROPIC_IMAGE_MIN_TOKENS = 1024;
|
|
15
61
|
/** Anthropic divisor: tokens = width × height / 750. */
|
|
@@ -844,7 +890,10 @@ function getBoundedTextTokenCount(
|
|
|
844
890
|
value.length > MAX_STRUCTURED_TOKENIZATION_CHARS
|
|
845
891
|
? value.slice(0, MAX_STRUCTURED_TOKENIZATION_CHARS)
|
|
846
892
|
: value;
|
|
847
|
-
const previewTokens =
|
|
893
|
+
const previewTokens = ensureSafeTokenMeasurement(
|
|
894
|
+
getTokenCount(preview),
|
|
895
|
+
'tokenizer'
|
|
896
|
+
);
|
|
848
897
|
const omittedChars = value.length - preview.length;
|
|
849
898
|
if (omittedChars <= 0) {
|
|
850
899
|
return previewTokens;
|
|
@@ -859,19 +908,25 @@ function getBoundedStructuredTokenCount(
|
|
|
859
908
|
value: unknown,
|
|
860
909
|
getTokenCount: (text: string) => number
|
|
861
910
|
): number {
|
|
862
|
-
if (hasUnsafeStructuredSerialization(value)) {
|
|
863
|
-
return Number.MAX_SAFE_INTEGER;
|
|
864
|
-
}
|
|
865
911
|
const serialized = serializeStructuredValueBounded(
|
|
866
912
|
value,
|
|
867
913
|
MAX_STRUCTURED_TOKENIZATION_CHARS
|
|
868
914
|
);
|
|
869
|
-
const previewTokens =
|
|
915
|
+
const previewTokens = ensureSafeTokenMeasurement(
|
|
916
|
+
getTokenCount(serialized.content),
|
|
917
|
+
'tokenizer'
|
|
918
|
+
);
|
|
870
919
|
if (!serialized.truncated) {
|
|
871
920
|
return previewTokens;
|
|
872
921
|
}
|
|
873
|
-
|
|
874
|
-
|
|
922
|
+
// The bounded serializer uses MAX_SAFE_INTEGER as its unknown-size sentinel.
|
|
923
|
+
// Do not extrapolate from it: use its bounded preview and let the provider
|
|
924
|
+
// input projection preserve the same bounded behavior before invocation.
|
|
925
|
+
if (
|
|
926
|
+
!Number.isSafeInteger(serialized.originalChars) ||
|
|
927
|
+
serialized.originalChars >= Number.MAX_SAFE_INTEGER
|
|
928
|
+
) {
|
|
929
|
+
return previewTokens;
|
|
875
930
|
}
|
|
876
931
|
|
|
877
932
|
const omittedChars = Math.max(
|
|
@@ -893,7 +948,10 @@ export function getTokenCountForMessage(
|
|
|
893
948
|
const countText = (text: string): number =>
|
|
894
949
|
getBoundedTextTokenCount(text, getTokenCount);
|
|
895
950
|
if (isProxy(message)) {
|
|
896
|
-
|
|
951
|
+
throw new UnsafeTokenMeasurementError({
|
|
952
|
+
reason: 'message_proxy',
|
|
953
|
+
path: 'message',
|
|
954
|
+
});
|
|
897
955
|
}
|
|
898
956
|
|
|
899
957
|
type ContentBlock = Record<string, unknown> & {
|
|
@@ -907,19 +965,22 @@ export function getTokenCountForMessage(
|
|
|
907
965
|
};
|
|
908
966
|
const representedToolCallIds = new Set<string>();
|
|
909
967
|
|
|
910
|
-
const processValue = (value: unknown): void => {
|
|
968
|
+
const processValue = (value: unknown, path: string): void => {
|
|
911
969
|
if (value != null && typeof value === 'object' && isProxy(value)) {
|
|
912
|
-
|
|
913
|
-
|
|
970
|
+
throw new UnsafeTokenMeasurementError({
|
|
971
|
+
reason: 'content_proxy',
|
|
972
|
+
path,
|
|
973
|
+
});
|
|
914
974
|
}
|
|
915
975
|
if (Array.isArray(value)) {
|
|
916
|
-
for (
|
|
976
|
+
for (let index = 0; index < value.length; index++) {
|
|
977
|
+
const raw = value[index];
|
|
917
978
|
if (
|
|
918
979
|
typeof raw === 'string' ||
|
|
919
980
|
typeof raw === 'number' ||
|
|
920
981
|
typeof raw === 'boolean'
|
|
921
982
|
) {
|
|
922
|
-
processValue(raw);
|
|
983
|
+
processValue(raw, `${path}[${index}]`);
|
|
923
984
|
continue;
|
|
924
985
|
}
|
|
925
986
|
const item = raw as ContentBlock | null | undefined;
|
|
@@ -927,8 +988,10 @@ export function getTokenCountForMessage(
|
|
|
927
988
|
continue;
|
|
928
989
|
}
|
|
929
990
|
if (isProxy(item)) {
|
|
930
|
-
|
|
931
|
-
|
|
991
|
+
throw new UnsafeTokenMeasurementError({
|
|
992
|
+
reason: 'content_proxy',
|
|
993
|
+
path: `${path}[${index}]`,
|
|
994
|
+
});
|
|
932
995
|
}
|
|
933
996
|
if (typeof item.type !== 'string') {
|
|
934
997
|
numTokens += getBoundedStructuredTokenCount(item, countText);
|
|
@@ -1010,7 +1073,7 @@ export function getTokenCountForMessage(
|
|
|
1010
1073
|
}
|
|
1011
1074
|
const output = item.tool_call.output;
|
|
1012
1075
|
if (output != null) {
|
|
1013
|
-
processValue(output);
|
|
1076
|
+
processValue(output, `${path}[${index}].tool_call.output`);
|
|
1014
1077
|
}
|
|
1015
1078
|
continue;
|
|
1016
1079
|
}
|
|
@@ -1021,7 +1084,7 @@ export function getTokenCountForMessage(
|
|
|
1021
1084
|
continue;
|
|
1022
1085
|
}
|
|
1023
1086
|
|
|
1024
|
-
processValue(nestedValue);
|
|
1087
|
+
processValue(nestedValue, `${path}[${index}].${item.type}`);
|
|
1025
1088
|
}
|
|
1026
1089
|
} else if (typeof value === 'string') {
|
|
1027
1090
|
numTokens += countText(value);
|
|
@@ -1040,7 +1103,10 @@ export function getTokenCountForMessage(
|
|
|
1040
1103
|
? rawAdditionalKwargs
|
|
1041
1104
|
: undefined;
|
|
1042
1105
|
if (additionalKwargs != null && isProxy(additionalKwargs)) {
|
|
1043
|
-
|
|
1106
|
+
throw new UnsafeTokenMeasurementError({
|
|
1107
|
+
reason: 'metadata_proxy',
|
|
1108
|
+
path: 'additional_kwargs',
|
|
1109
|
+
});
|
|
1044
1110
|
}
|
|
1045
1111
|
let additionalType: PropertyDescriptor | undefined;
|
|
1046
1112
|
try {
|
|
@@ -1049,10 +1115,16 @@ export function getTokenCountForMessage(
|
|
|
1049
1115
|
? Object.getOwnPropertyDescriptor(additionalKwargs, 'type')
|
|
1050
1116
|
: undefined;
|
|
1051
1117
|
} catch {
|
|
1052
|
-
|
|
1118
|
+
throw new UnsafeTokenMeasurementError({
|
|
1119
|
+
reason: 'metadata_accessor',
|
|
1120
|
+
path: 'additional_kwargs.type',
|
|
1121
|
+
});
|
|
1053
1122
|
}
|
|
1054
1123
|
if (additionalType != null && !('value' in additionalType)) {
|
|
1055
|
-
|
|
1124
|
+
throw new UnsafeTokenMeasurementError({
|
|
1125
|
+
reason: 'metadata_accessor',
|
|
1126
|
+
path: 'additional_kwargs.type',
|
|
1127
|
+
});
|
|
1056
1128
|
}
|
|
1057
1129
|
|
|
1058
1130
|
let numTokens = tokensPerMessage;
|
|
@@ -1072,20 +1144,24 @@ export function getTokenCountForMessage(
|
|
|
1072
1144
|
) * IMAGE_TOKEN_SAFETY_MARGIN
|
|
1073
1145
|
);
|
|
1074
1146
|
} else {
|
|
1075
|
-
processValue(message.content);
|
|
1076
|
-
}
|
|
1077
|
-
if (numTokens >= Number.MAX_SAFE_INTEGER) {
|
|
1078
|
-
return Number.MAX_SAFE_INTEGER;
|
|
1147
|
+
processValue(message.content, 'content');
|
|
1079
1148
|
}
|
|
1149
|
+
ensureSafeTokenMeasurement(numTokens, 'content');
|
|
1080
1150
|
const messageRole = (message as BaseMessage & { role?: unknown }).role;
|
|
1081
1151
|
if (messageType === 'ai' || messageRole === 'assistant') {
|
|
1082
1152
|
const toolCalls = (message as AIMessage).tool_calls ?? [];
|
|
1083
1153
|
if (isProxy(toolCalls)) {
|
|
1084
|
-
|
|
1154
|
+
throw new UnsafeTokenMeasurementError({
|
|
1155
|
+
reason: 'metadata_proxy',
|
|
1156
|
+
path: 'tool_calls',
|
|
1157
|
+
});
|
|
1085
1158
|
}
|
|
1086
1159
|
for (const toolCall of toolCalls) {
|
|
1087
1160
|
if (isProxy(toolCall)) {
|
|
1088
|
-
|
|
1161
|
+
throw new UnsafeTokenMeasurementError({
|
|
1162
|
+
reason: 'metadata_proxy',
|
|
1163
|
+
path: 'tool_calls',
|
|
1164
|
+
});
|
|
1089
1165
|
}
|
|
1090
1166
|
if (
|
|
1091
1167
|
typeof toolCall.id === 'string' &&
|
|
@@ -1111,11 +1187,17 @@ export function getTokenCountForMessage(
|
|
|
1111
1187
|
? Object.getOwnPropertyDescriptor(additionalKwargs, 'function_call')
|
|
1112
1188
|
: undefined;
|
|
1113
1189
|
} catch {
|
|
1114
|
-
|
|
1190
|
+
throw new UnsafeTokenMeasurementError({
|
|
1191
|
+
reason: 'metadata_accessor',
|
|
1192
|
+
path: 'additional_kwargs.function_call',
|
|
1193
|
+
});
|
|
1115
1194
|
}
|
|
1116
1195
|
if (legacyFunctionCall != null) {
|
|
1117
1196
|
if (!('value' in legacyFunctionCall)) {
|
|
1118
|
-
|
|
1197
|
+
throw new UnsafeTokenMeasurementError({
|
|
1198
|
+
reason: 'metadata_accessor',
|
|
1199
|
+
path: 'additional_kwargs.function_call',
|
|
1200
|
+
});
|
|
1119
1201
|
}
|
|
1120
1202
|
if (legacyFunctionCall.value != null) {
|
|
1121
1203
|
numTokens += getBoundedStructuredTokenCount(
|
|
@@ -1125,7 +1207,7 @@ export function getTokenCountForMessage(
|
|
|
1125
1207
|
}
|
|
1126
1208
|
}
|
|
1127
1209
|
}
|
|
1128
|
-
return
|
|
1210
|
+
return ensureSafeTokenMeasurement(numTokens, 'message');
|
|
1129
1211
|
}
|
|
1130
1212
|
|
|
1131
1213
|
/**
|
|
@@ -1180,7 +1262,10 @@ export const createTokenCounter = async (
|
|
|
1180
1262
|
const isClaude = encoding === 'claude';
|
|
1181
1263
|
return (message: BaseMessage): number => {
|
|
1182
1264
|
const count = getTokenCountForMessage(message, countTokens, encoding);
|
|
1183
|
-
|
|
1265
|
+
const correctedCount = isClaude
|
|
1266
|
+
? Math.ceil(count * CLAUDE_TOKEN_CORRECTION)
|
|
1267
|
+
: count;
|
|
1268
|
+
return ensureSafeTokenMeasurement(correctedCount, 'message');
|
|
1184
1269
|
};
|
|
1185
1270
|
};
|
|
1186
1271
|
|