@librechat/agents 3.2.64 → 3.2.66
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 +18 -2
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +15 -3
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +1 -2
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/langfuseTraceShaping.cjs +51 -24
- package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +73 -9
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +49 -9
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +3 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs +337 -0
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs.map +1 -0
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +8 -0
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +9 -8
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +9 -7
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-scraper.cjs +165 -0
- package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/crw-search.cjs +105 -0
- package/dist/cjs/tools/search/crw-search.cjs.map +1 -0
- package/dist/cjs/tools/search/search.cjs +4 -2
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +15 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +18 -2
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +15 -3
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +1 -2
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/langfuseTraceShaping.mjs +51 -24
- package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +73 -9
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +49 -9
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +3 -2
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/stream_events.mjs +337 -0
- package/dist/esm/llm/anthropic/utils/stream_events.mjs.map +1 -0
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +8 -0
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +9 -8
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +9 -7
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/search/crw-scraper.mjs +163 -0
- package/dist/esm/tools/search/crw-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/crw-search.mjs +103 -0
- package/dist/esm/tools/search/crw-search.mjs.map +1 -0
- package/dist/esm/tools/search/search.mjs +4 -2
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +15 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +2 -0
- package/dist/types/langfuseTraceShaping.d.ts +12 -6
- package/dist/types/llm/anthropic/index.d.ts +2 -0
- package/dist/types/llm/anthropic/types.d.ts +2 -0
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +1 -2
- package/dist/types/llm/anthropic/utils/stream_events.d.ts +25 -0
- package/dist/types/tools/BashExecutor.d.ts +7 -6
- package/dist/types/tools/CodeExecutor.d.ts +7 -5
- package/dist/types/tools/search/crw-scraper.d.ts +41 -0
- package/dist/types/tools/search/crw-search.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +88 -3
- package/dist/types/types/graph.d.ts +10 -3
- package/package.json +1 -1
- package/src/graphs/Graph.ts +36 -3
- package/src/instrumentation.ts +20 -0
- package/src/langfuseToolOutputTracing.ts +2 -4
- package/src/langfuseTraceShaping.ts +73 -20
- package/src/llm/anthropic/index.ts +134 -10
- package/src/llm/anthropic/inherited-content-utils.spec.ts +10 -5
- package/src/llm/anthropic/inherited-stream-events.spec.ts +513 -9
- package/src/llm/anthropic/llm.spec.ts +100 -16
- package/src/llm/anthropic/types.ts +3 -0
- package/src/llm/anthropic/utils/cross-provider-server-tools.test.ts +110 -0
- package/src/llm/anthropic/utils/message_inputs.ts +75 -3
- package/src/llm/anthropic/utils/message_outputs.ts +10 -2
- package/src/llm/anthropic/utils/stream_events.ts +471 -0
- package/src/llm/bedrock/utils/cross-provider-server-tools.test.ts +122 -0
- package/src/llm/bedrock/utils/message_inputs.ts +13 -0
- package/src/specs/langfuse-instrumentation.test.ts +64 -0
- package/src/specs/langfuse-routing.integration.test.ts +49 -4
- package/src/specs/langfuse-tool-output-tracing.test.ts +5 -4
- package/src/specs/langfuse-trace-shaping.test.ts +80 -9
- package/src/specs/subagent.test.ts +180 -0
- package/src/tools/BashExecutor.ts +9 -8
- package/src/tools/CodeExecutor.ts +9 -7
- package/src/tools/__tests__/BashExecutor.test.ts +16 -5
- package/src/tools/__tests__/CodeExecutor.stateful.test.ts +17 -6
- package/src/tools/search/crw-scraper.ts +244 -0
- package/src/tools/search/crw-search.ts +167 -0
- package/src/tools/search/crw.test.ts +836 -0
- package/src/tools/search/search.ts +7 -1
- package/src/tools/search/tool.ts +23 -3
- package/src/tools/search/types.ts +103 -3
- package/src/types/graph.ts +10 -3
|
@@ -961,7 +961,7 @@ class RecordingStreamingAnthropic extends ChatAnthropic {
|
|
|
961
961
|
}
|
|
962
962
|
}
|
|
963
963
|
|
|
964
|
-
test('Anthropic message_delta usage
|
|
964
|
+
test('Anthropic message_delta usage preserves cumulative input totals', () => {
|
|
965
965
|
const event: AnthropicStreamEvent = {
|
|
966
966
|
type: 'message_delta',
|
|
967
967
|
context_management: null,
|
|
@@ -978,6 +978,7 @@ test('Anthropic message_delta usage emits only output token totals', () => {
|
|
|
978
978
|
cache_read_input_tokens: 13,
|
|
979
979
|
server_tool_use: null,
|
|
980
980
|
iterations: null,
|
|
981
|
+
output_tokens_details: null,
|
|
981
982
|
},
|
|
982
983
|
};
|
|
983
984
|
|
|
@@ -987,9 +988,13 @@ test('Anthropic message_delta usage emits only output token totals', () => {
|
|
|
987
988
|
});
|
|
988
989
|
|
|
989
990
|
expect(result?.chunk.usage_metadata).toEqual({
|
|
990
|
-
input_tokens:
|
|
991
|
+
input_tokens: 267,
|
|
991
992
|
output_tokens: 375,
|
|
992
|
-
total_tokens:
|
|
993
|
+
total_tokens: 642,
|
|
994
|
+
input_token_details: {
|
|
995
|
+
cache_creation: 11,
|
|
996
|
+
cache_read: 13,
|
|
997
|
+
},
|
|
993
998
|
});
|
|
994
999
|
});
|
|
995
1000
|
|
|
@@ -1002,6 +1007,7 @@ test('Anthropic stream usage does not double-count cumulative input tokens', asy
|
|
|
1002
1007
|
container: null,
|
|
1003
1008
|
context_management: null,
|
|
1004
1009
|
content: [],
|
|
1010
|
+
diagnostics: null,
|
|
1005
1011
|
model: modelName,
|
|
1006
1012
|
role: 'assistant',
|
|
1007
1013
|
stop_details: null,
|
|
@@ -1010,15 +1016,16 @@ test('Anthropic stream usage does not double-count cumulative input tokens', asy
|
|
|
1010
1016
|
type: 'message',
|
|
1011
1017
|
usage: {
|
|
1012
1018
|
cache_creation: null,
|
|
1013
|
-
cache_creation_input_tokens:
|
|
1014
|
-
cache_read_input_tokens:
|
|
1019
|
+
cache_creation_input_tokens: 20,
|
|
1020
|
+
cache_read_input_tokens: 30,
|
|
1015
1021
|
inference_geo: null,
|
|
1016
1022
|
input_tokens: 243,
|
|
1017
1023
|
iterations: null,
|
|
1018
|
-
output_tokens:
|
|
1024
|
+
output_tokens: 7,
|
|
1019
1025
|
server_tool_use: null,
|
|
1020
1026
|
service_tier: null,
|
|
1021
1027
|
speed: null,
|
|
1028
|
+
output_tokens_details: null,
|
|
1022
1029
|
},
|
|
1023
1030
|
},
|
|
1024
1031
|
},
|
|
@@ -1034,10 +1041,11 @@ test('Anthropic stream usage does not double-count cumulative input tokens', asy
|
|
|
1034
1041
|
usage: {
|
|
1035
1042
|
input_tokens: 243,
|
|
1036
1043
|
output_tokens: 375,
|
|
1037
|
-
cache_creation_input_tokens:
|
|
1038
|
-
cache_read_input_tokens:
|
|
1044
|
+
cache_creation_input_tokens: 20,
|
|
1045
|
+
cache_read_input_tokens: 30,
|
|
1039
1046
|
server_tool_use: null,
|
|
1040
1047
|
iterations: null,
|
|
1048
|
+
output_tokens_details: null,
|
|
1041
1049
|
},
|
|
1042
1050
|
},
|
|
1043
1051
|
{ type: 'message_stop' },
|
|
@@ -1050,12 +1058,83 @@ test('Anthropic stream usage does not double-count cumulative input tokens', asy
|
|
|
1050
1058
|
}
|
|
1051
1059
|
|
|
1052
1060
|
expect(full?.usage_metadata).toEqual({
|
|
1053
|
-
input_tokens:
|
|
1061
|
+
input_tokens: 293,
|
|
1054
1062
|
output_tokens: 375,
|
|
1055
|
-
total_tokens:
|
|
1063
|
+
total_tokens: 668,
|
|
1056
1064
|
input_token_details: {
|
|
1057
|
-
cache_creation:
|
|
1058
|
-
cache_read:
|
|
1065
|
+
cache_creation: 20,
|
|
1066
|
+
cache_read: 30,
|
|
1067
|
+
},
|
|
1068
|
+
output_token_details: {},
|
|
1069
|
+
});
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
test('Anthropic stream usage accepts input first reported at message_delta', async () => {
|
|
1073
|
+
const events: AnthropicStreamEvent[] = [
|
|
1074
|
+
{
|
|
1075
|
+
type: 'message_start',
|
|
1076
|
+
message: {
|
|
1077
|
+
id: 'msg_late_input_usage',
|
|
1078
|
+
container: null,
|
|
1079
|
+
context_management: null,
|
|
1080
|
+
content: [],
|
|
1081
|
+
diagnostics: null,
|
|
1082
|
+
model: modelName,
|
|
1083
|
+
role: 'assistant',
|
|
1084
|
+
stop_details: null,
|
|
1085
|
+
stop_reason: null,
|
|
1086
|
+
stop_sequence: null,
|
|
1087
|
+
type: 'message',
|
|
1088
|
+
usage: {
|
|
1089
|
+
cache_creation: null,
|
|
1090
|
+
cache_creation_input_tokens: 0,
|
|
1091
|
+
cache_read_input_tokens: 0,
|
|
1092
|
+
inference_geo: null,
|
|
1093
|
+
input_tokens: 0,
|
|
1094
|
+
iterations: null,
|
|
1095
|
+
output_tokens: 0,
|
|
1096
|
+
server_tool_use: null,
|
|
1097
|
+
service_tier: null,
|
|
1098
|
+
speed: null,
|
|
1099
|
+
output_tokens_details: null,
|
|
1100
|
+
},
|
|
1101
|
+
},
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
type: 'message_delta',
|
|
1105
|
+
context_management: null,
|
|
1106
|
+
delta: {
|
|
1107
|
+
container: null,
|
|
1108
|
+
stop_details: null,
|
|
1109
|
+
stop_reason: 'end_turn',
|
|
1110
|
+
stop_sequence: null,
|
|
1111
|
+
},
|
|
1112
|
+
usage: {
|
|
1113
|
+
input_tokens: 100,
|
|
1114
|
+
output_tokens: 42,
|
|
1115
|
+
cache_creation_input_tokens: 500,
|
|
1116
|
+
cache_read_input_tokens: 200,
|
|
1117
|
+
server_tool_use: null,
|
|
1118
|
+
iterations: null,
|
|
1119
|
+
output_tokens_details: null,
|
|
1120
|
+
},
|
|
1121
|
+
},
|
|
1122
|
+
{ type: 'message_stop' },
|
|
1123
|
+
];
|
|
1124
|
+
const model = new MockStreamingAnthropic(events);
|
|
1125
|
+
|
|
1126
|
+
let full: AIMessageChunk | undefined;
|
|
1127
|
+
for await (const chunk of await model.stream('hello')) {
|
|
1128
|
+
full = !full ? chunk : concat(full, chunk);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
expect(full?.usage_metadata).toEqual({
|
|
1132
|
+
input_tokens: 800,
|
|
1133
|
+
output_tokens: 42,
|
|
1134
|
+
total_tokens: 842,
|
|
1135
|
+
input_token_details: {
|
|
1136
|
+
cache_creation: 500,
|
|
1137
|
+
cache_read: 200,
|
|
1059
1138
|
},
|
|
1060
1139
|
output_token_details: {},
|
|
1061
1140
|
});
|
|
@@ -1070,6 +1149,7 @@ test('Anthropic stream usage handles multiple cumulative message_delta events',
|
|
|
1070
1149
|
container: null,
|
|
1071
1150
|
context_management: null,
|
|
1072
1151
|
content: [],
|
|
1152
|
+
diagnostics: null,
|
|
1073
1153
|
model: modelName,
|
|
1074
1154
|
role: 'assistant',
|
|
1075
1155
|
stop_details: null,
|
|
@@ -1083,10 +1163,11 @@ test('Anthropic stream usage handles multiple cumulative message_delta events',
|
|
|
1083
1163
|
inference_geo: null,
|
|
1084
1164
|
input_tokens: 243,
|
|
1085
1165
|
iterations: null,
|
|
1086
|
-
output_tokens:
|
|
1166
|
+
output_tokens: 7,
|
|
1087
1167
|
server_tool_use: null,
|
|
1088
1168
|
service_tier: null,
|
|
1089
1169
|
speed: null,
|
|
1170
|
+
output_tokens_details: null,
|
|
1090
1171
|
},
|
|
1091
1172
|
},
|
|
1092
1173
|
},
|
|
@@ -1106,6 +1187,7 @@ test('Anthropic stream usage handles multiple cumulative message_delta events',
|
|
|
1106
1187
|
cache_read_input_tokens: 0,
|
|
1107
1188
|
server_tool_use: null,
|
|
1108
1189
|
iterations: null,
|
|
1190
|
+
output_tokens_details: null,
|
|
1109
1191
|
},
|
|
1110
1192
|
},
|
|
1111
1193
|
{
|
|
@@ -1124,6 +1206,7 @@ test('Anthropic stream usage handles multiple cumulative message_delta events',
|
|
|
1124
1206
|
cache_read_input_tokens: 0,
|
|
1125
1207
|
server_tool_use: null,
|
|
1126
1208
|
iterations: null,
|
|
1209
|
+
output_tokens_details: null,
|
|
1127
1210
|
},
|
|
1128
1211
|
},
|
|
1129
1212
|
{ type: 'message_stop' },
|
|
@@ -1230,9 +1313,10 @@ test('Anthropic live stream usage matches raw cumulative output snapshots', asyn
|
|
|
1230
1313
|
expect(model.messageDeltaOutputTokens.length).toBeGreaterThan(0);
|
|
1231
1314
|
const rawOutputTokens =
|
|
1232
1315
|
model.messageDeltaOutputTokens[model.messageDeltaOutputTokens.length - 1];
|
|
1233
|
-
expect(
|
|
1234
|
-
model.messageStartOutputTokens
|
|
1316
|
+
expect(rawOutputTokens).toBeGreaterThanOrEqual(
|
|
1317
|
+
model.messageStartOutputTokens
|
|
1235
1318
|
);
|
|
1319
|
+
expect(full?.usage_metadata?.output_tokens).toBe(rawOutputTokens);
|
|
1236
1320
|
expect(full?.usage_metadata?.total_tokens).toBe(
|
|
1237
1321
|
(full?.usage_metadata?.input_tokens ?? 0) +
|
|
1238
1322
|
(full?.usage_metadata?.output_tokens ?? 0)
|
|
@@ -1244,7 +1328,7 @@ test('Anthropic live stream usage matches raw cumulative output snapshots', asyn
|
|
|
1244
1328
|
0
|
|
1245
1329
|
);
|
|
1246
1330
|
expect(full?.usage_metadata?.output_tokens).toBeLessThan(
|
|
1247
|
-
|
|
1331
|
+
summedOutputTokens
|
|
1248
1332
|
);
|
|
1249
1333
|
}
|
|
1250
1334
|
});
|
|
@@ -61,6 +61,9 @@ export type AnthropicSearchResultBlockParam =
|
|
|
61
61
|
Anthropic.Beta.BetaSearchResultBlockParam;
|
|
62
62
|
export type AnthropicCompactionBlockParam =
|
|
63
63
|
Anthropic.Beta.BetaCompactionBlockParam;
|
|
64
|
+
export type AnthropicCompactionBlock = Anthropic.Beta.BetaCompactionBlock;
|
|
65
|
+
export type AnthropicCompactionContentBlockDelta =
|
|
66
|
+
Anthropic.Beta.BetaCompactionContentBlockDelta;
|
|
64
67
|
export type AnthropicOutputConfig = Anthropic.Messages.OutputConfig;
|
|
65
68
|
export type ChatAnthropicOutputFormat = Anthropic.Messages.JSONOutputFormat;
|
|
66
69
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { AIMessage, HumanMessage } from '@langchain/core/messages';
|
|
2
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
3
|
+
import { _convertMessagesToAnthropicPayload } from './message_inputs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Regression for cross-provider agent handoffs (Google → Anthropic): a Gemini
|
|
7
|
+
* turn that used a server-side tool (URL context, Google Search) leaves
|
|
8
|
+
* `toolCall`/`toolResponse` content blocks in history. The Anthropic converter
|
|
9
|
+
* has no branch for them and previously threw
|
|
10
|
+
* "Unsupported message content format", crashing the handoff. Only Google can
|
|
11
|
+
* execute these blocks or validate their thought signatures, so they are
|
|
12
|
+
* dropped on assistant turns; any other unknown block still throws.
|
|
13
|
+
*/
|
|
14
|
+
type AnthropicPayload = ReturnType<typeof _convertMessagesToAnthropicPayload>;
|
|
15
|
+
|
|
16
|
+
/** Minimal view of a converted Anthropic content block the assertions read. */
|
|
17
|
+
interface TestBlock {
|
|
18
|
+
type?: string;
|
|
19
|
+
text?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const assistantBlocks = (payload: AnthropicPayload): TestBlock[] => {
|
|
23
|
+
const content = payload.messages.find((m) => m.role === 'assistant')?.content;
|
|
24
|
+
return Array.isArray(content) ? (content as TestBlock[]) : [];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('_convertMessagesToAnthropicPayload — Google server-side tool blocks', () => {
|
|
28
|
+
it('drops toolCall/toolResponse on an assistant turn, keeping text', () => {
|
|
29
|
+
const messages: BaseMessage[] = [
|
|
30
|
+
new HumanMessage('summarize this article'),
|
|
31
|
+
new AIMessage({
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: 'toolCall',
|
|
35
|
+
thoughtSignature: 'google-signature-not-valid-for-anthropic',
|
|
36
|
+
toolCall: {
|
|
37
|
+
toolType: 'URL_CONTEXT',
|
|
38
|
+
args: { urls: ['https://example.com/report'] },
|
|
39
|
+
id: 'j7pfyr6k',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'toolResponse',
|
|
44
|
+
toolResponse: {
|
|
45
|
+
toolType: 'URL_CONTEXT',
|
|
46
|
+
id: 'j7pfyr6k',
|
|
47
|
+
result: { status: 'SUCCESS' },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: 'The article argues for dollar-cost averaging.',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
}),
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
expect(() => _convertMessagesToAnthropicPayload(messages)).not.toThrow();
|
|
59
|
+
const blocks = assistantBlocks(
|
|
60
|
+
_convertMessagesToAnthropicPayload(messages)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const serialized = JSON.stringify(blocks);
|
|
64
|
+
expect(serialized).not.toContain('URL_CONTEXT');
|
|
65
|
+
expect(serialized).not.toContain(
|
|
66
|
+
'google-signature-not-valid-for-anthropic'
|
|
67
|
+
);
|
|
68
|
+
expect(
|
|
69
|
+
blocks.some(
|
|
70
|
+
(b) =>
|
|
71
|
+
b.type === 'text' &&
|
|
72
|
+
b.text === 'The article argues for dollar-cost averaging.'
|
|
73
|
+
)
|
|
74
|
+
).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('emits a placeholder (not empty content) when a server-tool-only turn is fully dropped', () => {
|
|
78
|
+
const messages: BaseMessage[] = [
|
|
79
|
+
new HumanMessage('hi'),
|
|
80
|
+
new AIMessage({
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: 'toolCall',
|
|
84
|
+
toolCall: { toolType: 'URL_CONTEXT', args: { urls: [] }, id: 'x1' },
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
}),
|
|
88
|
+
];
|
|
89
|
+
expect(() => _convertMessagesToAnthropicPayload(messages)).not.toThrow();
|
|
90
|
+
const blocks = assistantBlocks(
|
|
91
|
+
_convertMessagesToAnthropicPayload(messages)
|
|
92
|
+
);
|
|
93
|
+
expect(blocks.length).toBeGreaterThan(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('still throws on a genuinely unknown assistant block', () => {
|
|
97
|
+
const messages: BaseMessage[] = [
|
|
98
|
+
new HumanMessage('run code'),
|
|
99
|
+
new AIMessage({
|
|
100
|
+
content: [
|
|
101
|
+
{ type: 'some_future_block_type', foo: 'bar' },
|
|
102
|
+
{ type: 'text', text: 'done' },
|
|
103
|
+
],
|
|
104
|
+
}),
|
|
105
|
+
];
|
|
106
|
+
expect(() => _convertMessagesToAnthropicPayload(messages)).toThrow(
|
|
107
|
+
'Unsupported message content format'
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -473,6 +473,13 @@ function _formatContent(message: BaseMessage) {
|
|
|
473
473
|
* forwarding an unusable block. The receiving model produces its own thinking.
|
|
474
474
|
*/
|
|
475
475
|
const foreignReasoningTypes = ['reasoning_content', 'reasoning', 'think'];
|
|
476
|
+
/**
|
|
477
|
+
* Google server-side tool blocks (`toolCall`/`toolResponse` parts from e.g.
|
|
478
|
+
* URL context or Google Search). Only Google can execute these and validate
|
|
479
|
+
* their thought signatures, so they are dropped on a cross-provider handoff
|
|
480
|
+
* (e.g. Google → Anthropic); the assistant's answer text is kept.
|
|
481
|
+
*/
|
|
482
|
+
const foreignServerToolTypes = ['toolCall', 'toolResponse'];
|
|
476
483
|
const { content } = message;
|
|
477
484
|
|
|
478
485
|
if (typeof content === 'string') {
|
|
@@ -482,8 +489,8 @@ function _formatContent(message: BaseMessage) {
|
|
|
482
489
|
const contentBlocks = contentParts.map((contentPart) => {
|
|
483
490
|
/**
|
|
484
491
|
* Normalize server_tool_use blocks into a clean shape the API accepts.
|
|
485
|
-
* These blocks may arrive with the correct type (server_tool_use)
|
|
486
|
-
*
|
|
492
|
+
* These blocks may arrive with the correct type (server_tool_use), as a
|
|
493
|
+
* standardized server_tool_call, or mislabeled after state serialization.
|
|
487
494
|
* Regardless of current type, if the id starts with 'srvtoolu_' we rebuild
|
|
488
495
|
* a clean block with only the properties the API expects.
|
|
489
496
|
*/
|
|
@@ -496,7 +503,7 @@ function _formatContent(message: BaseMessage) {
|
|
|
496
503
|
'name' in contentPart
|
|
497
504
|
) {
|
|
498
505
|
const rawPart = contentPart as Record<string, unknown>;
|
|
499
|
-
let input = rawPart.input;
|
|
506
|
+
let input = rawPart.input ?? rawPart.args;
|
|
500
507
|
if (typeof input === 'string') {
|
|
501
508
|
try {
|
|
502
509
|
input = JSON.parse(input);
|
|
@@ -549,6 +556,60 @@ function _formatContent(message: BaseMessage) {
|
|
|
549
556
|
return null;
|
|
550
557
|
}
|
|
551
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Native ChatModelStream output standardizes client tool uses as
|
|
561
|
+
* `tool_call` blocks. Convert those blocks back to Anthropic's wire shape
|
|
562
|
+
* before the generic content formatter sees them.
|
|
563
|
+
*/
|
|
564
|
+
if (
|
|
565
|
+
contentPart.type === 'tool_call' &&
|
|
566
|
+
'id' in contentPart &&
|
|
567
|
+
typeof contentPart.id === 'string' &&
|
|
568
|
+
'name' in contentPart &&
|
|
569
|
+
typeof contentPart.name === 'string' &&
|
|
570
|
+
'args' in contentPart
|
|
571
|
+
) {
|
|
572
|
+
let args = contentPart.args;
|
|
573
|
+
if (typeof args === 'string') {
|
|
574
|
+
try {
|
|
575
|
+
args = JSON.parse(args);
|
|
576
|
+
} catch {
|
|
577
|
+
args = {};
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (args == null || typeof args !== 'object' || Array.isArray(args)) {
|
|
581
|
+
args = {};
|
|
582
|
+
}
|
|
583
|
+
return _convertLangChainToolCallToAnthropic({
|
|
584
|
+
id: contentPart.id,
|
|
585
|
+
name: contentPart.name,
|
|
586
|
+
args: args as Record<string, unknown>,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Native Anthropic thinking is exposed through the standard reasoning
|
|
592
|
+
* stream, but its signature makes it safe and necessary to round-trip.
|
|
593
|
+
* Keep unsigned or cross-provider reasoning on the existing drop path.
|
|
594
|
+
*/
|
|
595
|
+
if (
|
|
596
|
+
contentPart.type === 'reasoning' &&
|
|
597
|
+
isAIMessage(message) &&
|
|
598
|
+
message.response_metadata.model_provider === 'anthropic' &&
|
|
599
|
+
'reasoning' in contentPart &&
|
|
600
|
+
typeof contentPart.reasoning === 'string' &&
|
|
601
|
+
'signature' in contentPart &&
|
|
602
|
+
typeof contentPart.signature === 'string' &&
|
|
603
|
+
contentPart.signature !== ''
|
|
604
|
+
) {
|
|
605
|
+
const block: AnthropicThinkingBlockParam = {
|
|
606
|
+
type: 'thinking',
|
|
607
|
+
thinking: contentPart.reasoning,
|
|
608
|
+
signature: contentPart.signature,
|
|
609
|
+
};
|
|
610
|
+
return block;
|
|
611
|
+
}
|
|
612
|
+
|
|
552
613
|
/**
|
|
553
614
|
* Skip non-server malformed blocks that have tool fields mixed with text type.
|
|
554
615
|
*/
|
|
@@ -665,6 +726,9 @@ function _formatContent(message: BaseMessage) {
|
|
|
665
726
|
const block: AnthropicCompactionBlockParam = {
|
|
666
727
|
type: 'compaction' as const,
|
|
667
728
|
content: compactionPart.content,
|
|
729
|
+
...('encrypted_content' in compactionPart
|
|
730
|
+
? { encrypted_content: compactionPart.encrypted_content }
|
|
731
|
+
: {}),
|
|
668
732
|
...(cacheControl != null ? { cache_control: cacheControl } : {}),
|
|
669
733
|
};
|
|
670
734
|
return block;
|
|
@@ -798,6 +862,14 @@ function _formatContent(message: BaseMessage) {
|
|
|
798
862
|
// dropped — as does any other unknown block (user media, Google
|
|
799
863
|
// code-execution), which must be surfaced, not discarded.
|
|
800
864
|
return null;
|
|
865
|
+
} else if (
|
|
866
|
+
isAIMessage(message) &&
|
|
867
|
+
foreignServerToolTypes.some((t) => t === contentPart.type)
|
|
868
|
+
) {
|
|
869
|
+
// Google server-side tool call/response (e.g. URL context) — only
|
|
870
|
+
// Google can execute it or validate its thought signature; drop it
|
|
871
|
+
// on a cross-provider handoff rather than crash.
|
|
872
|
+
return null;
|
|
801
873
|
} else {
|
|
802
874
|
console.error(
|
|
803
875
|
'Unsupported content part:',
|
|
@@ -9,7 +9,7 @@ import type { MessageContentComplex } from '@/types';
|
|
|
9
9
|
import { toLangChainContent } from '@/messages/langchain';
|
|
10
10
|
import { extractToolCalls } from './output_parsers';
|
|
11
11
|
|
|
12
|
-
interface AnthropicUsageData {
|
|
12
|
+
export interface AnthropicUsageData {
|
|
13
13
|
input_tokens?: number | null;
|
|
14
14
|
output_tokens?: number | null;
|
|
15
15
|
cache_creation_input_tokens?: number | null;
|
|
@@ -92,7 +92,15 @@ export function _makeMessageChunkFromAnthropicEvent(
|
|
|
92
92
|
context_management: data.delta.context_management,
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
-
const
|
|
95
|
+
const deltaUsage: AnthropicUsageData = data.usage;
|
|
96
|
+
const hasInputUsage =
|
|
97
|
+
deltaUsage.input_tokens != null ||
|
|
98
|
+
deltaUsage.cache_creation_input_tokens != null ||
|
|
99
|
+
deltaUsage.cache_read_input_tokens != null;
|
|
100
|
+
const cumulativeUsageMetadata = hasInputUsage
|
|
101
|
+
? getAnthropicUsageMetadata(deltaUsage)
|
|
102
|
+
: undefined;
|
|
103
|
+
const usageMetadata: UsageMetadata = cumulativeUsageMetadata ?? {
|
|
96
104
|
input_tokens: 0,
|
|
97
105
|
output_tokens: data.usage.output_tokens,
|
|
98
106
|
total_tokens: data.usage.output_tokens,
|