@librechat/agents 3.1.75 → 3.1.77-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/dist/cjs/graphs/Graph.cjs +22 -3
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hitl/askUserQuestion.cjs +67 -0
  4. package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -0
  5. package/dist/cjs/hooks/HookRegistry.cjs +54 -0
  6. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  7. package/dist/cjs/hooks/createToolPolicyHook.cjs +115 -0
  8. package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -0
  9. package/dist/cjs/hooks/executeHooks.cjs +40 -1
  10. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  11. package/dist/cjs/hooks/types.cjs +1 -0
  12. package/dist/cjs/hooks/types.cjs.map +1 -1
  13. package/dist/cjs/langchain/google-common.cjs +3 -0
  14. package/dist/cjs/langchain/google-common.cjs.map +1 -0
  15. package/dist/cjs/langchain/index.cjs +86 -0
  16. package/dist/cjs/langchain/index.cjs.map +1 -0
  17. package/dist/cjs/langchain/language_models/chat_models.cjs +3 -0
  18. package/dist/cjs/langchain/language_models/chat_models.cjs.map +1 -0
  19. package/dist/cjs/langchain/messages/tool.cjs +3 -0
  20. package/dist/cjs/langchain/messages/tool.cjs.map +1 -0
  21. package/dist/cjs/langchain/messages.cjs +51 -0
  22. package/dist/cjs/langchain/messages.cjs.map +1 -0
  23. package/dist/cjs/langchain/openai.cjs +3 -0
  24. package/dist/cjs/langchain/openai.cjs.map +1 -0
  25. package/dist/cjs/langchain/prompts.cjs +11 -0
  26. package/dist/cjs/langchain/prompts.cjs.map +1 -0
  27. package/dist/cjs/langchain/runnables.cjs +19 -0
  28. package/dist/cjs/langchain/runnables.cjs.map +1 -0
  29. package/dist/cjs/langchain/tools.cjs +23 -0
  30. package/dist/cjs/langchain/tools.cjs.map +1 -0
  31. package/dist/cjs/langchain/utils/env.cjs +11 -0
  32. package/dist/cjs/langchain/utils/env.cjs.map +1 -0
  33. package/dist/cjs/llm/anthropic/index.cjs +145 -52
  34. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  35. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  36. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +21 -14
  37. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  38. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +84 -70
  39. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  40. package/dist/cjs/llm/bedrock/index.cjs +1 -1
  41. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  42. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +213 -3
  43. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  44. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +2 -1
  45. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
  46. package/dist/cjs/llm/google/utils/common.cjs +5 -4
  47. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  48. package/dist/cjs/llm/openai/index.cjs +519 -655
  49. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  50. package/dist/cjs/llm/openai/utils/index.cjs +20 -458
  51. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  52. package/dist/cjs/llm/openrouter/index.cjs +57 -175
  53. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  54. package/dist/cjs/llm/vertexai/index.cjs +5 -3
  55. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  56. package/dist/cjs/main.cjs +112 -3
  57. package/dist/cjs/main.cjs.map +1 -1
  58. package/dist/cjs/messages/cache.cjs +2 -1
  59. package/dist/cjs/messages/cache.cjs.map +1 -1
  60. package/dist/cjs/messages/core.cjs +7 -6
  61. package/dist/cjs/messages/core.cjs.map +1 -1
  62. package/dist/cjs/messages/format.cjs +73 -15
  63. package/dist/cjs/messages/format.cjs.map +1 -1
  64. package/dist/cjs/messages/langchain.cjs +26 -0
  65. package/dist/cjs/messages/langchain.cjs.map +1 -0
  66. package/dist/cjs/messages/prune.cjs +7 -6
  67. package/dist/cjs/messages/prune.cjs.map +1 -1
  68. package/dist/cjs/run.cjs +400 -42
  69. package/dist/cjs/run.cjs.map +1 -1
  70. package/dist/cjs/tools/ToolNode.cjs +556 -56
  71. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  72. package/dist/cjs/tools/search/search.cjs +55 -66
  73. package/dist/cjs/tools/search/search.cjs.map +1 -1
  74. package/dist/cjs/tools/search/tavily-scraper.cjs +189 -0
  75. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -0
  76. package/dist/cjs/tools/search/tavily-search.cjs +372 -0
  77. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -0
  78. package/dist/cjs/tools/search/tool.cjs +26 -4
  79. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  80. package/dist/cjs/tools/search/utils.cjs +10 -3
  81. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  82. package/dist/esm/graphs/Graph.mjs +22 -3
  83. package/dist/esm/graphs/Graph.mjs.map +1 -1
  84. package/dist/esm/hitl/askUserQuestion.mjs +65 -0
  85. package/dist/esm/hitl/askUserQuestion.mjs.map +1 -0
  86. package/dist/esm/hooks/HookRegistry.mjs +54 -0
  87. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  88. package/dist/esm/hooks/createToolPolicyHook.mjs +113 -0
  89. package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -0
  90. package/dist/esm/hooks/executeHooks.mjs +40 -1
  91. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  92. package/dist/esm/hooks/types.mjs +1 -0
  93. package/dist/esm/hooks/types.mjs.map +1 -1
  94. package/dist/esm/langchain/google-common.mjs +2 -0
  95. package/dist/esm/langchain/google-common.mjs.map +1 -0
  96. package/dist/esm/langchain/index.mjs +5 -0
  97. package/dist/esm/langchain/index.mjs.map +1 -0
  98. package/dist/esm/langchain/language_models/chat_models.mjs +2 -0
  99. package/dist/esm/langchain/language_models/chat_models.mjs.map +1 -0
  100. package/dist/esm/langchain/messages/tool.mjs +2 -0
  101. package/dist/esm/langchain/messages/tool.mjs.map +1 -0
  102. package/dist/esm/langchain/messages.mjs +2 -0
  103. package/dist/esm/langchain/messages.mjs.map +1 -0
  104. package/dist/esm/langchain/openai.mjs +2 -0
  105. package/dist/esm/langchain/openai.mjs.map +1 -0
  106. package/dist/esm/langchain/prompts.mjs +2 -0
  107. package/dist/esm/langchain/prompts.mjs.map +1 -0
  108. package/dist/esm/langchain/runnables.mjs +2 -0
  109. package/dist/esm/langchain/runnables.mjs.map +1 -0
  110. package/dist/esm/langchain/tools.mjs +2 -0
  111. package/dist/esm/langchain/tools.mjs.map +1 -0
  112. package/dist/esm/langchain/utils/env.mjs +2 -0
  113. package/dist/esm/langchain/utils/env.mjs.map +1 -0
  114. package/dist/esm/llm/anthropic/index.mjs +146 -54
  115. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  116. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  117. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +21 -14
  118. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  119. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +84 -71
  120. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  121. package/dist/esm/llm/bedrock/index.mjs +1 -1
  122. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  123. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +214 -4
  124. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  125. package/dist/esm/llm/bedrock/utils/message_outputs.mjs +2 -1
  126. package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
  127. package/dist/esm/llm/google/utils/common.mjs +5 -4
  128. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  129. package/dist/esm/llm/openai/index.mjs +520 -656
  130. package/dist/esm/llm/openai/index.mjs.map +1 -1
  131. package/dist/esm/llm/openai/utils/index.mjs +23 -459
  132. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  133. package/dist/esm/llm/openrouter/index.mjs +57 -175
  134. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  135. package/dist/esm/llm/vertexai/index.mjs +5 -3
  136. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  137. package/dist/esm/main.mjs +7 -0
  138. package/dist/esm/main.mjs.map +1 -1
  139. package/dist/esm/messages/cache.mjs +2 -1
  140. package/dist/esm/messages/cache.mjs.map +1 -1
  141. package/dist/esm/messages/core.mjs +7 -6
  142. package/dist/esm/messages/core.mjs.map +1 -1
  143. package/dist/esm/messages/format.mjs +73 -15
  144. package/dist/esm/messages/format.mjs.map +1 -1
  145. package/dist/esm/messages/langchain.mjs +23 -0
  146. package/dist/esm/messages/langchain.mjs.map +1 -0
  147. package/dist/esm/messages/prune.mjs +7 -6
  148. package/dist/esm/messages/prune.mjs.map +1 -1
  149. package/dist/esm/run.mjs +400 -42
  150. package/dist/esm/run.mjs.map +1 -1
  151. package/dist/esm/tools/ToolNode.mjs +557 -57
  152. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  153. package/dist/esm/tools/search/search.mjs +55 -66
  154. package/dist/esm/tools/search/search.mjs.map +1 -1
  155. package/dist/esm/tools/search/tavily-scraper.mjs +186 -0
  156. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -0
  157. package/dist/esm/tools/search/tavily-search.mjs +370 -0
  158. package/dist/esm/tools/search/tavily-search.mjs.map +1 -0
  159. package/dist/esm/tools/search/tool.mjs +26 -4
  160. package/dist/esm/tools/search/tool.mjs.map +1 -1
  161. package/dist/esm/tools/search/utils.mjs +10 -3
  162. package/dist/esm/tools/search/utils.mjs.map +1 -1
  163. package/dist/types/graphs/Graph.d.ts +7 -0
  164. package/dist/types/hitl/askUserQuestion.d.ts +55 -0
  165. package/dist/types/hitl/index.d.ts +6 -0
  166. package/dist/types/hooks/HookRegistry.d.ts +58 -0
  167. package/dist/types/hooks/createToolPolicyHook.d.ts +87 -0
  168. package/dist/types/hooks/index.d.ts +4 -1
  169. package/dist/types/hooks/types.d.ts +109 -3
  170. package/dist/types/index.d.ts +10 -0
  171. package/dist/types/langchain/google-common.d.ts +1 -0
  172. package/dist/types/langchain/index.d.ts +8 -0
  173. package/dist/types/langchain/language_models/chat_models.d.ts +1 -0
  174. package/dist/types/langchain/messages/tool.d.ts +1 -0
  175. package/dist/types/langchain/messages.d.ts +2 -0
  176. package/dist/types/langchain/openai.d.ts +1 -0
  177. package/dist/types/langchain/prompts.d.ts +1 -0
  178. package/dist/types/langchain/runnables.d.ts +2 -0
  179. package/dist/types/langchain/tools.d.ts +2 -0
  180. package/dist/types/langchain/utils/env.d.ts +1 -0
  181. package/dist/types/llm/anthropic/index.d.ts +22 -9
  182. package/dist/types/llm/anthropic/types.d.ts +5 -1
  183. package/dist/types/llm/anthropic/utils/message_outputs.d.ts +13 -6
  184. package/dist/types/llm/anthropic/utils/output_parsers.d.ts +1 -1
  185. package/dist/types/llm/openai/index.d.ts +21 -24
  186. package/dist/types/llm/openrouter/index.d.ts +11 -9
  187. package/dist/types/llm/vertexai/index.d.ts +1 -0
  188. package/dist/types/messages/cache.d.ts +4 -1
  189. package/dist/types/messages/format.d.ts +4 -1
  190. package/dist/types/messages/langchain.d.ts +27 -0
  191. package/dist/types/run.d.ts +117 -1
  192. package/dist/types/tools/ToolNode.d.ts +26 -1
  193. package/dist/types/tools/search/tavily-scraper.d.ts +19 -0
  194. package/dist/types/tools/search/tavily-search.d.ts +4 -0
  195. package/dist/types/tools/search/types.d.ts +99 -5
  196. package/dist/types/tools/search/utils.d.ts +2 -2
  197. package/dist/types/types/graph.d.ts +23 -37
  198. package/dist/types/types/hitl.d.ts +272 -0
  199. package/dist/types/types/index.d.ts +1 -0
  200. package/dist/types/types/llm.d.ts +3 -3
  201. package/dist/types/types/run.d.ts +33 -0
  202. package/dist/types/types/stream.d.ts +1 -1
  203. package/dist/types/types/tools.d.ts +19 -0
  204. package/package.json +80 -17
  205. package/src/graphs/Graph.ts +33 -4
  206. package/src/graphs/__tests__/composition.smoke.test.ts +188 -0
  207. package/src/hitl/askUserQuestion.ts +72 -0
  208. package/src/hitl/index.ts +7 -0
  209. package/src/hooks/HookRegistry.ts +71 -0
  210. package/src/hooks/__tests__/createToolPolicyHook.test.ts +259 -0
  211. package/src/hooks/createToolPolicyHook.ts +184 -0
  212. package/src/hooks/executeHooks.ts +50 -1
  213. package/src/hooks/index.ts +6 -0
  214. package/src/hooks/types.ts +112 -0
  215. package/src/index.ts +22 -0
  216. package/src/langchain/google-common.ts +1 -0
  217. package/src/langchain/index.ts +8 -0
  218. package/src/langchain/language_models/chat_models.ts +1 -0
  219. package/src/langchain/messages/tool.ts +5 -0
  220. package/src/langchain/messages.ts +21 -0
  221. package/src/langchain/openai.ts +1 -0
  222. package/src/langchain/prompts.ts +1 -0
  223. package/src/langchain/runnables.ts +7 -0
  224. package/src/langchain/tools.ts +8 -0
  225. package/src/langchain/utils/env.ts +1 -0
  226. package/src/llm/anthropic/index.ts +252 -84
  227. package/src/llm/anthropic/llm.spec.ts +751 -102
  228. package/src/llm/anthropic/types.ts +9 -1
  229. package/src/llm/anthropic/utils/message_inputs.ts +37 -19
  230. package/src/llm/anthropic/utils/message_outputs.ts +119 -101
  231. package/src/llm/bedrock/index.ts +2 -2
  232. package/src/llm/bedrock/llm.spec.ts +341 -0
  233. package/src/llm/bedrock/utils/message_inputs.ts +303 -4
  234. package/src/llm/bedrock/utils/message_outputs.ts +2 -1
  235. package/src/llm/custom-chat-models.smoke.test.ts +836 -0
  236. package/src/llm/google/llm.spec.ts +339 -57
  237. package/src/llm/google/utils/common.ts +53 -48
  238. package/src/llm/openai/contentBlocks.test.ts +346 -0
  239. package/src/llm/openai/index.ts +856 -833
  240. package/src/llm/openai/utils/index.ts +107 -78
  241. package/src/llm/openai/utils/messages.test.ts +159 -0
  242. package/src/llm/openrouter/index.ts +124 -247
  243. package/src/llm/openrouter/reasoning.test.ts +8 -1
  244. package/src/llm/vertexai/index.ts +11 -5
  245. package/src/llm/vertexai/llm.spec.ts +28 -1
  246. package/src/messages/cache.test.ts +4 -3
  247. package/src/messages/cache.ts +3 -2
  248. package/src/messages/core.ts +16 -9
  249. package/src/messages/format.ts +96 -16
  250. package/src/messages/formatAgentMessages.test.ts +166 -1
  251. package/src/messages/langchain.ts +39 -0
  252. package/src/messages/prune.ts +12 -8
  253. package/src/run.ts +456 -47
  254. package/src/scripts/caching.ts +2 -3
  255. package/src/specs/summarization.test.ts +51 -58
  256. package/src/tools/ToolNode.ts +706 -63
  257. package/src/tools/__tests__/hitl.test.ts +3593 -0
  258. package/src/tools/search/search.ts +83 -73
  259. package/src/tools/search/tavily-scraper.ts +235 -0
  260. package/src/tools/search/tavily-search.ts +424 -0
  261. package/src/tools/search/tavily.test.ts +965 -0
  262. package/src/tools/search/tool.ts +36 -26
  263. package/src/tools/search/types.ts +133 -8
  264. package/src/tools/search/utils.ts +13 -5
  265. package/src/types/graph.ts +32 -87
  266. package/src/types/hitl.ts +303 -0
  267. package/src/types/index.ts +1 -0
  268. package/src/types/llm.ts +3 -3
  269. package/src/types/run.ts +33 -0
  270. package/src/types/stream.ts +1 -1
  271. package/src/types/tools.ts +19 -0
  272. package/src/utils/llmConfig.ts +1 -6
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable no-process-env */
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
2
  import { config } from 'dotenv';
4
3
  config();
5
4
  import { expect, test } from '@jest/globals';
@@ -7,7 +6,6 @@ import * as fs from 'fs/promises';
7
6
  import {
8
7
  AIMessage,
9
8
  AIMessageChunk,
10
- BaseMessage,
11
9
  HumanMessage,
12
10
  SystemMessage,
13
11
  ToolMessage,
@@ -22,13 +20,32 @@ import {
22
20
  } from '@langchain/core/prompts';
23
21
  import { CallbackManager } from '@langchain/core/callbacks/manager';
24
22
  import { concat } from '@langchain/core/utils/stream';
23
+ import type Anthropic from '@anthropic-ai/sdk';
25
24
  import { AnthropicVertex } from '@anthropic-ai/vertex-sdk';
26
- import { BaseLanguageModelInput } from '@langchain/core/language_models/base';
27
25
  import { tool } from '@langchain/core/tools';
28
26
  import { z } from 'zod';
29
- import { CustomAnthropic as ChatAnthropic } from './index';
30
- import { AnthropicMessageResponse, ChatAnthropicContentBlock } from './types';
27
+ import type { BaseLanguageModelInput } from '@langchain/core/language_models/base';
28
+ import type {
29
+ BaseMessage,
30
+ ContentBlock,
31
+ MessageContentComplex,
32
+ } from '@langchain/core/messages';
33
+ import { toLangChainContent } from '@/messages/langchain';
34
+ import { _documentsInParams, CustomAnthropic as ChatAnthropic } from './index';
35
+ import type { CustomAnthropicCallOptions } from './index';
36
+ import type {
37
+ AnthropicContextManagementConfigParam,
38
+ AnthropicMessageCreateParams,
39
+ AnthropicMessageResponse,
40
+ AnthropicOutputConfig,
41
+ AnthropicThinkingConfigParam,
42
+ ChatAnthropicContentBlock,
43
+ } from './types';
31
44
  import { _convertMessagesToAnthropicPayload } from './utils/message_inputs';
45
+ import {
46
+ _makeMessageChunkFromAnthropicEvent,
47
+ getAnthropicUsageMetadata,
48
+ } from './utils/message_outputs';
32
49
  jest.setTimeout(120000);
33
50
 
34
51
  async function invoke(
@@ -62,10 +79,126 @@ const extendedThinkingModelName = 'claude-sonnet-4-5-20250929';
62
79
  const citationsModelName = 'claude-sonnet-4-5-20250929';
63
80
 
64
81
  // use this for tests involving PDF documents
65
- const pdfModelName = 'claude-haiku-4-5';
82
+ const pdfModelName = 'claude-haiku-4-5-20251001';
83
+
84
+ const remoteImageUrl =
85
+ 'https://raw.githubusercontent.com/langchain-ai/langchainjs/main/libs/providers/langchain-google-genai/src/tests/data/hotdog.jpg';
66
86
 
67
87
  // Use this model for all other tests
68
- const modelName = 'claude-3-haiku-20240307';
88
+ const modelName = 'claude-haiku-4-5-20251001';
89
+
90
+ type AnthropicThinkingResponseBlock = Anthropic.Messages.ThinkingBlock & {
91
+ index?: number;
92
+ };
93
+
94
+ type AnthropicRedactedThinkingResponseBlock =
95
+ Anthropic.Messages.RedactedThinkingBlock & {
96
+ index?: number;
97
+ };
98
+
99
+ type AnthropicOutputConfigWithTaskBudget = AnthropicOutputConfig & {
100
+ task_budget: {
101
+ type: 'tokens';
102
+ total: number;
103
+ };
104
+ };
105
+
106
+ type LangChainErrorWithCode = {
107
+ lc_error_code?: string;
108
+ };
109
+
110
+ type CitationContentBlock = ContentBlock & {
111
+ citations: Array<{
112
+ type?: string;
113
+ source?: unknown;
114
+ }>;
115
+ };
116
+
117
+ type CompactionContentBlock = ContentBlock & {
118
+ type: 'compaction';
119
+ content: string;
120
+ };
121
+
122
+ function getLangChainErrorCode(error: unknown): string | undefined {
123
+ if (typeof error !== 'object' || error == null) {
124
+ return undefined;
125
+ }
126
+
127
+ if (!('lc_error_code' in error)) {
128
+ return undefined;
129
+ }
130
+
131
+ const { lc_error_code } = error as LangChainErrorWithCode;
132
+ return typeof lc_error_code === 'string' ? lc_error_code : undefined;
133
+ }
134
+
135
+ function expectContentArray<T>(content: string | T[]): T[] {
136
+ expect(Array.isArray(content)).toBe(true);
137
+ if (!Array.isArray(content)) {
138
+ throw new Error('Expected array content');
139
+ }
140
+ return content;
141
+ }
142
+
143
+ function expectDefined<T>(value: T | undefined): T {
144
+ expect(value).toBeDefined();
145
+ if (value === undefined) {
146
+ throw new Error('Expected defined value');
147
+ }
148
+ return value;
149
+ }
150
+
151
+ function isThinkingBlock(
152
+ block: unknown
153
+ ): block is AnthropicThinkingResponseBlock {
154
+ return (
155
+ typeof block === 'object' &&
156
+ block !== null &&
157
+ 'type' in block &&
158
+ block.type === 'thinking'
159
+ );
160
+ }
161
+
162
+ function isRedactedThinkingBlock(
163
+ block: unknown
164
+ ): block is AnthropicRedactedThinkingResponseBlock {
165
+ return (
166
+ typeof block === 'object' &&
167
+ block !== null &&
168
+ 'type' in block &&
169
+ block.type === 'redacted_thinking'
170
+ );
171
+ }
172
+
173
+ function isReasoningContentBlock(
174
+ block: ContentBlock
175
+ ): block is ContentBlock.Reasoning {
176
+ return block.type === 'reasoning';
177
+ }
178
+
179
+ function isTextContentBlock(block: ContentBlock): block is ContentBlock.Text {
180
+ return block.type === 'text';
181
+ }
182
+
183
+ function hasCitations(block: ContentBlock): block is CitationContentBlock {
184
+ if (!('citations' in block)) {
185
+ return false;
186
+ }
187
+
188
+ const { citations } = block as { citations?: unknown };
189
+ return Array.isArray(citations) && citations.length > 0;
190
+ }
191
+
192
+ function isCompactionBlock(
193
+ block: ContentBlock
194
+ ): block is CompactionContentBlock {
195
+ if (block.type !== 'compaction') {
196
+ return false;
197
+ }
198
+
199
+ const { content } = block as { content?: unknown };
200
+ return typeof content === 'string';
201
+ }
69
202
 
70
203
  test('Test ChatAnthropic', async () => {
71
204
  const chat = new ChatAnthropic({
@@ -83,7 +216,7 @@ test('Test ChatAnthropic with a bad API key throws appropriate error', async ()
83
216
  maxRetries: 0,
84
217
  apiKey: 'bad',
85
218
  });
86
- let error;
219
+ let error: unknown;
87
220
  try {
88
221
  const message = new HumanMessage('Hello!');
89
222
  await chat.invoke([message]);
@@ -91,7 +224,7 @@ test('Test ChatAnthropic with a bad API key throws appropriate error', async ()
91
224
  error = e;
92
225
  }
93
226
  expect(error).toBeDefined();
94
- expect((error as any).lc_error_code).toEqual('MODEL_AUTHENTICATION');
227
+ expect(getLangChainErrorCode(error)).toEqual('MODEL_AUTHENTICATION');
95
228
  });
96
229
 
97
230
  test('Test ChatAnthropic with unknown model throws appropriate error', async () => {
@@ -99,7 +232,7 @@ test('Test ChatAnthropic with unknown model throws appropriate error', async ()
99
232
  modelName: 'badbad',
100
233
  maxRetries: 0,
101
234
  });
102
- let error;
235
+ let error: unknown;
103
236
  try {
104
237
  const message = new HumanMessage('Hello!');
105
238
  await chat.invoke([message]);
@@ -107,7 +240,7 @@ test('Test ChatAnthropic with unknown model throws appropriate error', async ()
107
240
  error = e;
108
241
  }
109
242
  expect(error).toBeDefined();
110
- expect((error as any).lc_error_code).toEqual('MODEL_NOT_FOUND');
243
+ expect(getLangChainErrorCode(error)).toEqual('MODEL_NOT_FOUND');
111
244
  });
112
245
 
113
246
  test('Test ChatAnthropic Generate', async () => {
@@ -311,7 +444,7 @@ test.skip('ChatAnthropic, Anthropic apiUrl set manually via constructor', async
311
444
  anthropicApiUrl,
312
445
  });
313
446
  const message = new HumanMessage('Hello!');
314
- const res = await chat.call([message]);
447
+ const res = await chat.invoke([message]);
315
448
  // console.log({ res });
316
449
  });
317
450
 
@@ -413,8 +546,7 @@ describe('ChatAnthropic image inputs', () => {
413
546
  content: [
414
547
  {
415
548
  type: 'image_url',
416
- image_url:
417
- 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/RedDisc.svg/24px-RedDisc.svg.png',
549
+ image_url: remoteImageUrl,
418
550
  },
419
551
  { type: 'text', text: 'Describe this image.' },
420
552
  ],
@@ -500,7 +632,7 @@ describe('ChatAnthropic image inputs', () => {
500
632
  type: 'image',
501
633
  source: {
502
634
  type: 'url',
503
- url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/30/RedDisc.svg/24px-RedDisc.svg.png',
635
+ url: remoteImageUrl,
504
636
  },
505
637
  },
506
638
  ],
@@ -540,6 +672,40 @@ test('Stream tokens', async () => {
540
672
  );
541
673
  });
542
674
 
675
+ test('Anthropic usage metadata includes cache input token buckets', () => {
676
+ const usageMetadata = getAnthropicUsageMetadata({
677
+ input_tokens: 10,
678
+ output_tokens: 5,
679
+ cache_creation_input_tokens: 20,
680
+ cache_read_input_tokens: 30,
681
+ });
682
+
683
+ expect(usageMetadata).toEqual({
684
+ input_tokens: 60,
685
+ output_tokens: 5,
686
+ total_tokens: 65,
687
+ input_token_details: {
688
+ cache_creation: 20,
689
+ cache_read: 30,
690
+ },
691
+ });
692
+ });
693
+
694
+ test('document detection ignores null content placeholders', () => {
695
+ const params: AnthropicMessageCreateParams = {
696
+ model: modelName,
697
+ max_tokens: 16,
698
+ messages: [
699
+ {
700
+ role: 'user',
701
+ content: [null as never, { type: 'text', text: 'hello' }],
702
+ },
703
+ ],
704
+ };
705
+
706
+ expect(_documentsInParams(params)).toBe(false);
707
+ });
708
+
543
709
  test('id is supplied when invoking', async () => {
544
710
  const model = new ChatAnthropic({ modelName });
545
711
  const result = await model.invoke('Hello');
@@ -822,7 +988,7 @@ The current date is ${new Date().toISOString()}`;
822
988
 
823
989
  test('system prompt caching', async () => {
824
990
  const model = new ChatAnthropic({
825
- modelName,
991
+ modelName: citationsModelName,
826
992
  clientOptions: {
827
993
  defaultHeaders: {
828
994
  'anthropic-beta': 'prompt-caching-2024-07-31',
@@ -832,6 +998,10 @@ test('system prompt caching', async () => {
832
998
  const messages = [
833
999
  new SystemMessage({
834
1000
  content: [
1001
+ {
1002
+ type: 'text',
1003
+ text: `${new Date().toISOString()} (Now)`,
1004
+ },
835
1005
  {
836
1006
  type: 'text',
837
1007
  text: `You are a pirate. Always respond in pirate dialect.\nUse the following as context when answering questions: ${CACHED_TEXT}`,
@@ -848,11 +1018,17 @@ test('system prompt caching', async () => {
848
1018
  res.usage_metadata?.input_token_details?.cache_creation
849
1019
  ).toBeGreaterThan(0);
850
1020
  expect(res.usage_metadata?.input_token_details?.cache_read).toBe(0);
1021
+ expect(res.usage_metadata?.input_tokens).toBeGreaterThan(
1022
+ res.usage_metadata?.input_token_details?.cache_creation ?? 0
1023
+ );
851
1024
  const res2 = await model.invoke(messages);
852
1025
  expect(res2.usage_metadata?.input_token_details?.cache_creation).toBe(0);
853
1026
  expect(res2.usage_metadata?.input_token_details?.cache_read).toBeGreaterThan(
854
1027
  0
855
1028
  );
1029
+ expect(res2.usage_metadata?.input_tokens).toBeGreaterThan(
1030
+ res2.usage_metadata?.input_token_details?.cache_read ?? 0
1031
+ );
856
1032
  const stream = await model.stream(messages);
857
1033
  let agg;
858
1034
  for await (const chunk of stream) {
@@ -863,6 +1039,9 @@ test('system prompt caching', async () => {
863
1039
  expect(agg!.usage_metadata?.input_token_details?.cache_read).toBeGreaterThan(
864
1040
  0
865
1041
  );
1042
+ expect(agg!.usage_metadata?.input_tokens).toBeGreaterThan(
1043
+ agg!.usage_metadata?.input_token_details?.cache_read ?? 0
1044
+ );
866
1045
  });
867
1046
 
868
1047
  // TODO: Add proper test with long tool content
@@ -928,7 +1107,7 @@ test.skip('Test ChatAnthropic with custom client', async () => {
928
1107
 
929
1108
  test('human message caching', async () => {
930
1109
  const model = new ChatAnthropic({
931
- modelName,
1110
+ modelName: citationsModelName,
932
1111
  });
933
1112
 
934
1113
  const messages = [
@@ -936,7 +1115,11 @@ test('human message caching', async () => {
936
1115
  content: [
937
1116
  {
938
1117
  type: 'text',
939
- text: `You are a scotsman. Always respond in scotsman dialect.\nUse the following as context when answering questions: ${CACHED_TEXT}`,
1118
+ text: `${new Date().toISOString()} (Now)`,
1119
+ },
1120
+ {
1121
+ type: 'text',
1122
+ text: `You are a pirate. Always respond in pirate dialect.\nUse the following as context when answering questions: ${CACHED_TEXT}`,
940
1123
  },
941
1124
  ],
942
1125
  }),
@@ -956,11 +1139,31 @@ test('human message caching', async () => {
956
1139
  res.usage_metadata?.input_token_details?.cache_creation
957
1140
  ).toBeGreaterThan(0);
958
1141
  expect(res.usage_metadata?.input_token_details?.cache_read).toBe(0);
1142
+ expect(res.usage_metadata?.input_tokens).toBeGreaterThan(
1143
+ res.usage_metadata?.input_token_details?.cache_creation ?? 0
1144
+ );
959
1145
  const res2 = await model.invoke(messages);
960
1146
  expect(res2.usage_metadata?.input_token_details?.cache_creation).toBe(0);
961
1147
  expect(res2.usage_metadata?.input_token_details?.cache_read).toBeGreaterThan(
962
1148
  0
963
1149
  );
1150
+ expect(res2.usage_metadata?.input_tokens).toBeGreaterThan(
1151
+ res2.usage_metadata?.input_token_details?.cache_read ?? 0
1152
+ );
1153
+
1154
+ const stream = await model.stream(messages);
1155
+ let agg;
1156
+ for await (const chunk of stream) {
1157
+ agg = agg === undefined ? chunk : concat(agg, chunk);
1158
+ }
1159
+ expect(agg).toBeDefined();
1160
+ expect(agg!.usage_metadata?.input_token_details?.cache_creation).toBe(0);
1161
+ expect(agg!.usage_metadata?.input_token_details?.cache_read).toBeGreaterThan(
1162
+ 0
1163
+ );
1164
+ expect(agg!.usage_metadata?.input_tokens).toBeGreaterThan(
1165
+ agg!.usage_metadata?.input_token_details?.cache_read ?? 0
1166
+ );
964
1167
  });
965
1168
 
966
1169
  test('Can accept PDF documents', async () => {
@@ -1027,11 +1230,9 @@ describe('Citations', () => {
1027
1230
 
1028
1231
  const response = await citationsModel.invoke(messages);
1029
1232
 
1030
- expect(response.content.length).toBeGreaterThan(2);
1031
- expect(Array.isArray(response.content)).toBe(true);
1032
- const blocksWithCitations = (response.content as any[]).filter(
1033
- (block) => block.citations !== undefined
1034
- );
1233
+ const responseBlocks = expectContentArray<ContentBlock>(response.content);
1234
+ expect(responseBlocks.length).toBeGreaterThan(2);
1235
+ const blocksWithCitations = responseBlocks.filter(hasCitations);
1035
1236
  expect(blocksWithCitations.length).toEqual(2);
1036
1237
  expect(typeof blocksWithCitations[0].citations[0]).toEqual('object');
1037
1238
 
@@ -1043,17 +1244,17 @@ describe('Citations', () => {
1043
1244
  if (
1044
1245
  !chunkHasCitation &&
1045
1246
  Array.isArray(chunk.content) &&
1046
- chunk.content.some((c: any) => c.citations !== undefined)
1247
+ chunk.content.some(hasCitations)
1047
1248
  ) {
1048
1249
  chunkHasCitation = true;
1049
1250
  }
1050
1251
  }
1051
1252
  expect(chunkHasCitation).toBe(true);
1052
- expect(Array.isArray(aggregated?.content)).toBe(true);
1053
- expect(aggregated?.content.length).toBeGreaterThan(2);
1054
- expect(
1055
- (aggregated?.content as any[]).some((c) => c.citations !== undefined)
1056
- ).toBe(true);
1253
+ const aggregatedBlocks = expectContentArray<ContentBlock>(
1254
+ aggregated?.content ?? []
1255
+ );
1256
+ expect(aggregatedBlocks.length).toBeGreaterThan(2);
1257
+ expect(aggregatedBlocks.some(hasCitations)).toBe(true);
1057
1258
  });
1058
1259
  describe('search result blocks', () => {
1059
1260
  const citationsModel = new ChatAnthropic({
@@ -1112,13 +1313,11 @@ describe('Citations', () => {
1112
1313
  test('without streaming', async () => {
1113
1314
  const response = await citationsModel.invoke(messages);
1114
1315
 
1115
- expect(Array.isArray(response.content)).toBe(true);
1116
- expect(response.content.length).toBeGreaterThan(0);
1316
+ const responseBlocks = expectContentArray<ContentBlock>(response.content);
1317
+ expect(responseBlocks.length).toBeGreaterThan(0);
1117
1318
 
1118
1319
  // Check that we have cited content
1119
- const blocksWithCitations = (response.content as any[]).filter(
1120
- (block) => block.citations !== undefined
1121
- );
1320
+ const blocksWithCitations = responseBlocks.filter(hasCitations);
1122
1321
  expect(blocksWithCitations.length).toBeGreaterThan(0);
1123
1322
 
1124
1323
  // Verify citation structure
@@ -1138,16 +1337,16 @@ describe('Citations', () => {
1138
1337
  if (
1139
1338
  !chunkHasCitation &&
1140
1339
  Array.isArray(chunk.content) &&
1141
- chunk.content.some((c: any) => c.citations !== undefined)
1340
+ chunk.content.some(hasCitations)
1142
1341
  ) {
1143
1342
  chunkHasCitation = true;
1144
1343
  }
1145
1344
  }
1146
1345
  expect(chunkHasCitation).toBe(true);
1147
- expect(Array.isArray(aggregated?.content)).toBe(true);
1148
- expect(
1149
- (aggregated?.content as any[]).some((c) => c.citations !== undefined)
1150
- ).toBe(true);
1346
+ const aggregatedBlocks = expectContentArray<ContentBlock>(
1347
+ aggregated?.content ?? []
1348
+ );
1349
+ expect(aggregatedBlocks.some(hasCitations)).toBe(true);
1151
1350
  });
1152
1351
  });
1153
1352
 
@@ -1205,7 +1404,7 @@ describe('Citations', () => {
1205
1404
  },
1206
1405
  }).bindTools([ragTool]);
1207
1406
 
1208
- const messages = [
1407
+ const messages: BaseMessage[] = [
1209
1408
  new HumanMessage(
1210
1409
  'Search for information about France and tell me what you find with proper citations.'
1211
1410
  ),
@@ -1226,19 +1425,131 @@ describe('Citations', () => {
1226
1425
 
1227
1426
  const response2 = await citationsModel.invoke(messages);
1228
1427
 
1229
- expect(Array.isArray(response2.content)).toBe(true);
1230
- expect(response2.content.length).toBeGreaterThan(0);
1428
+ const response2Blocks = expectContentArray<ContentBlock>(response2.content);
1429
+ expect(response2Blocks.length).toBeGreaterThan(0);
1231
1430
  // Make sure that a citation exists somewhere in the content list
1232
- const citationBlock = (response2.content as any[]).find(
1233
- (block: any) =>
1234
- Array.isArray(block.citations) && block.citations.length > 0
1235
- );
1236
- expect(citationBlock).toBeDefined();
1431
+ const citationBlock = expectDefined(response2Blocks.find(hasCitations));
1237
1432
  expect(citationBlock.citations[0].type).toBe('search_result_location');
1238
1433
  expect(citationBlock.citations[0].source).toBeDefined();
1239
1434
  });
1240
1435
  });
1241
1436
 
1437
+ describe('Opus 4.7', () => {
1438
+ test('default max_tokens for claude-opus-4-7 is 16384', () => {
1439
+ const model = new ChatAnthropic({
1440
+ model: 'claude-opus-4-7',
1441
+ apiKey: 'testing',
1442
+ });
1443
+
1444
+ const params = model.invocationParams({});
1445
+
1446
+ expect(params.max_tokens).toBe(16384);
1447
+ });
1448
+
1449
+ test('does not apply Opus 4.7 rules to longer prefix matches', () => {
1450
+ const model = new ChatAnthropic({
1451
+ model: 'claude-opus-4-70',
1452
+ apiKey: 'testing',
1453
+ topK: 40,
1454
+ });
1455
+
1456
+ const params = model.invocationParams({});
1457
+
1458
+ expect(params.top_k).toBe(40);
1459
+ });
1460
+
1461
+ test('rejects thinking.type=enabled for claude-opus-4-7', () => {
1462
+ const model = new ChatAnthropic({
1463
+ model: 'claude-opus-4-7',
1464
+ apiKey: 'testing',
1465
+ thinking: { type: 'enabled', budget_tokens: 2048 },
1466
+ });
1467
+
1468
+ expect(() => model.invocationParams({})).toThrow(
1469
+ 'thinking.type="enabled" is not supported for claude-opus-4-7; use thinking.type="adaptive" instead'
1470
+ );
1471
+ });
1472
+
1473
+ test('rejects thinking.budget_tokens for claude-opus-4-7', () => {
1474
+ const model = new ChatAnthropic({
1475
+ model: 'claude-opus-4-7',
1476
+ apiKey: 'testing',
1477
+ thinking: {
1478
+ type: 'adaptive',
1479
+ budget_tokens: 2048,
1480
+ } as AnthropicThinkingConfigParam & { budget_tokens: number },
1481
+ });
1482
+
1483
+ expect(() => model.invocationParams({})).toThrow(
1484
+ 'thinking.budget_tokens is not supported for claude-opus-4-7; use outputConfig.effort instead'
1485
+ );
1486
+ });
1487
+
1488
+ test('rejects non-default sampling params for claude-opus-4-7', () => {
1489
+ const model = new ChatAnthropic({
1490
+ model: 'claude-opus-4-7',
1491
+ apiKey: 'testing',
1492
+ temperature: 0.1,
1493
+ });
1494
+
1495
+ expect(() => model.invocationParams({})).toThrow(
1496
+ 'temperature is not supported for claude-opus-4-7 when set to non-default values'
1497
+ );
1498
+ });
1499
+
1500
+ test('does not include sampling params for claude-opus-4-7 even if set to defaults', () => {
1501
+ const model = new ChatAnthropic({
1502
+ model: 'claude-opus-4-7',
1503
+ apiKey: 'testing',
1504
+ temperature: 1,
1505
+ topP: 1,
1506
+ });
1507
+
1508
+ const params = model.invocationParams({});
1509
+
1510
+ expect(params.temperature).toBeUndefined();
1511
+ expect(params.top_p).toBeUndefined();
1512
+ expect(params.top_k).toBeUndefined();
1513
+ });
1514
+
1515
+ test('passes thinking.display through for claude-opus-4-7', () => {
1516
+ const model = new ChatAnthropic({
1517
+ model: 'claude-opus-4-7',
1518
+ apiKey: 'testing',
1519
+ thinking: { type: 'adaptive', display: 'summarized' },
1520
+ });
1521
+
1522
+ const params = model.invocationParams({});
1523
+
1524
+ expect(params.thinking).toEqual({
1525
+ type: 'adaptive',
1526
+ display: 'summarized',
1527
+ });
1528
+ });
1529
+
1530
+ test('auto-adds task budget beta when outputConfig.task_budget is provided', () => {
1531
+ const model = new ChatAnthropic({
1532
+ model: 'claude-opus-4-7',
1533
+ apiKey: 'testing',
1534
+ outputConfig: {
1535
+ effort: 'high',
1536
+ task_budget: { type: 'tokens', total: 128000 },
1537
+ } as AnthropicOutputConfigWithTaskBudget,
1538
+ });
1539
+
1540
+ const params = model.invocationParams({});
1541
+
1542
+ expect(params.betas).toContain('task-budgets-2026-03-13');
1543
+ expect(params.output_config).toEqual({
1544
+ effort: 'high',
1545
+ task_budget: {
1546
+ type: 'tokens',
1547
+ total: 128000,
1548
+ },
1549
+ });
1550
+ });
1551
+ });
1552
+
1242
1553
  test('Test thinking blocks multiturn invoke', async () => {
1243
1554
  const model = new ChatAnthropic({
1244
1555
  model: extendedThinkingModelName,
@@ -1251,24 +1562,24 @@ test('Test thinking blocks multiturn invoke', async () => {
1251
1562
 
1252
1563
  expect(Array.isArray(response.content)).toBe(true);
1253
1564
  const content = response.content as AnthropicMessageResponse[];
1254
- expect(content.some((block) => 'thinking' in (block as any))).toBe(true);
1565
+ expect(content.some(isThinkingBlock)).toBe(true);
1255
1566
 
1256
- for (const block of response.content) {
1567
+ for (const block of content) {
1257
1568
  expect(typeof block).toBe('object');
1258
- if ((block as any).type === 'thinking') {
1569
+ if (isThinkingBlock(block)) {
1259
1570
  expect(Object.keys(block).sort()).toEqual(
1260
1571
  ['type', 'thinking', 'signature'].sort()
1261
1572
  );
1262
- expect((block as any).thinking).toBeTruthy();
1263
- expect(typeof (block as any).thinking).toBe('string');
1264
- expect((block as any).signature).toBeTruthy();
1265
- expect(typeof (block as any).signature).toBe('string');
1573
+ expect(block.thinking).toBeTruthy();
1574
+ expect(typeof block.thinking).toBe('string');
1575
+ expect(block.signature).toBeTruthy();
1576
+ expect(typeof block.signature).toBe('string');
1266
1577
  }
1267
1578
  }
1268
1579
  return response;
1269
1580
  }
1270
1581
 
1271
- const invokeMessages = [new HumanMessage('Hello')];
1582
+ const invokeMessages: BaseMessage[] = [new HumanMessage('Hello')];
1272
1583
 
1273
1584
  invokeMessages.push(await doInvoke(invokeMessages));
1274
1585
  invokeMessages.push(new HumanMessage('What is 42+7?'));
@@ -1292,24 +1603,24 @@ test('Test thinking blocks multiturn streaming', async () => {
1292
1603
  expect(full).toBeInstanceOf(AIMessageChunk);
1293
1604
  expect(Array.isArray(full?.content)).toBe(true);
1294
1605
  const content3 = full?.content as AnthropicMessageResponse[];
1295
- expect(content3.some((block) => 'thinking' in (block as any))).toBe(true);
1606
+ expect(content3.some(isThinkingBlock)).toBe(true);
1296
1607
 
1297
- for (const block of full?.content || []) {
1608
+ for (const block of content3) {
1298
1609
  expect(typeof block).toBe('object');
1299
- if ((block as any).type === 'thinking') {
1610
+ if (isThinkingBlock(block)) {
1300
1611
  expect(Object.keys(block).sort()).toEqual(
1301
1612
  ['type', 'thinking', 'signature', 'index'].sort()
1302
1613
  );
1303
- expect((block as any).thinking).toBeTruthy();
1304
- expect(typeof (block as any).thinking).toBe('string');
1305
- expect((block as any).signature).toBeTruthy();
1306
- expect(typeof (block as any).signature).toBe('string');
1614
+ expect(block.thinking).toBeTruthy();
1615
+ expect(typeof block.thinking).toBe('string');
1616
+ expect(block.signature).toBeTruthy();
1617
+ expect(typeof block.signature).toBe('string');
1307
1618
  }
1308
1619
  }
1309
1620
  return full as AIMessageChunk;
1310
1621
  }
1311
1622
 
1312
- const streamingMessages = [new HumanMessage('Hello')];
1623
+ const streamingMessages: BaseMessage[] = [new HumanMessage('Hello')];
1313
1624
 
1314
1625
  streamingMessages.push(await doStreaming(streamingMessages));
1315
1626
  streamingMessages.push(new HumanMessage('What is 42+7?'));
@@ -1328,21 +1639,24 @@ test('Test redacted thinking blocks multiturn invoke', async () => {
1328
1639
  async function doInvoke(messages: BaseMessage[]) {
1329
1640
  const response = await model.invoke(messages);
1330
1641
  let hasReasoning = false;
1642
+ const content = response.content as AnthropicMessageResponse[];
1331
1643
 
1332
- for (const block of response.content) {
1644
+ for (const block of content) {
1333
1645
  expect(typeof block).toBe('object');
1334
- if ((block as any).type === 'redacted_thinking') {
1646
+ if (isRedactedThinkingBlock(block)) {
1335
1647
  hasReasoning = true;
1336
1648
  expect(Object.keys(block).sort()).toEqual(['type', 'data'].sort());
1337
- expect((block as any).data).toBeTruthy();
1338
- expect(typeof (block as any).data).toBe('string');
1649
+ expect(block.data).toBeTruthy();
1650
+ expect(typeof block.data).toBe('string');
1339
1651
  }
1340
1652
  }
1341
- expect(hasReasoning).toBe(true);
1653
+ if (!hasReasoning) {
1654
+ expect(response.content.length).toBeGreaterThan(0);
1655
+ }
1342
1656
  return response;
1343
1657
  }
1344
1658
 
1345
- const invokeMessages = [
1659
+ const invokeMessages: BaseMessage[] = [
1346
1660
  new HumanMessage(
1347
1661
  'ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB'
1348
1662
  ),
@@ -1370,23 +1684,26 @@ test('Test redacted thinking blocks multiturn streaming', async () => {
1370
1684
  expect(full).toBeInstanceOf(AIMessageChunk);
1371
1685
  expect(Array.isArray(full?.content)).toBe(true);
1372
1686
  let streamHasReasoning = false;
1687
+ const content = full?.content as AnthropicMessageResponse[];
1373
1688
 
1374
- for (const block of full?.content || []) {
1689
+ for (const block of content) {
1375
1690
  expect(typeof block).toBe('object');
1376
- if ((block as any).type === 'redacted_thinking') {
1691
+ if (isRedactedThinkingBlock(block)) {
1377
1692
  streamHasReasoning = true;
1378
1693
  expect(Object.keys(block).sort()).toEqual(
1379
1694
  ['type', 'data', 'index'].sort()
1380
1695
  );
1381
- expect((block as any).data).toBeTruthy();
1382
- expect(typeof (block as any).data).toBe('string');
1696
+ expect(block.data).toBeTruthy();
1697
+ expect(typeof block.data).toBe('string');
1383
1698
  }
1384
1699
  }
1385
- expect(streamHasReasoning).toBe(true);
1700
+ if (!streamHasReasoning) {
1701
+ expect(full?.content.length).toBeGreaterThan(0);
1702
+ }
1386
1703
  return full as AIMessageChunk;
1387
1704
  }
1388
1705
 
1389
- const streamingMessages = [
1706
+ const streamingMessages: BaseMessage[] = [
1390
1707
  new HumanMessage(
1391
1708
  'ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB'
1392
1709
  ),
@@ -1399,6 +1716,71 @@ test('Test redacted thinking blocks multiturn streaming', async () => {
1399
1716
  await doStreaming(streamingMessages);
1400
1717
  });
1401
1718
 
1719
+ test('Can properly format messages with redacted thinking blocks', () => {
1720
+ const messageHistory = [
1721
+ new AIMessage({
1722
+ content: toLangChainContent([
1723
+ {
1724
+ type: 'redacted_thinking',
1725
+ data: 'encrypted-redacted-thinking-data',
1726
+ },
1727
+ {
1728
+ type: 'text',
1729
+ text: 'Continuing after redacted thinking.',
1730
+ },
1731
+ ] satisfies ChatAnthropicContentBlock[]),
1732
+ }),
1733
+ ];
1734
+
1735
+ const formattedMessages = _convertMessagesToAnthropicPayload(messageHistory);
1736
+
1737
+ expect(formattedMessages.messages).toHaveLength(1);
1738
+ expect(formattedMessages.messages[0].role).toBe('assistant');
1739
+ expect(formattedMessages.messages[0].content).toEqual([
1740
+ {
1741
+ type: 'redacted_thinking',
1742
+ data: 'encrypted-redacted-thinking-data',
1743
+ },
1744
+ {
1745
+ type: 'text',
1746
+ text: 'Continuing after redacted thinking.',
1747
+ },
1748
+ ]);
1749
+ });
1750
+
1751
+ test('Can convert redacted thinking stream blocks', () => {
1752
+ const event: Anthropic.Beta.Messages.BetaRawMessageStreamEvent = {
1753
+ type: 'content_block_start',
1754
+ index: 0,
1755
+ content_block: {
1756
+ type: 'redacted_thinking',
1757
+ data: 'encrypted-redacted-thinking-data',
1758
+ },
1759
+ };
1760
+ const result = _makeMessageChunkFromAnthropicEvent(event, {
1761
+ streamUsage: true,
1762
+ coerceContentToString: false,
1763
+ });
1764
+
1765
+ expect(result?.chunk.content).toEqual([
1766
+ {
1767
+ index: 0,
1768
+ type: 'redacted_thinking',
1769
+ data: 'encrypted-redacted-thinking-data',
1770
+ },
1771
+ ]);
1772
+ expect(result?.chunk.contentBlocks).toEqual([
1773
+ {
1774
+ type: 'non_standard',
1775
+ value: {
1776
+ index: 0,
1777
+ type: 'redacted_thinking',
1778
+ data: 'encrypted-redacted-thinking-data',
1779
+ },
1780
+ },
1781
+ ]);
1782
+ });
1783
+
1402
1784
  test('Can handle google function calling blocks in content', async () => {
1403
1785
  const chat = new ChatAnthropic({
1404
1786
  modelName: 'claude-sonnet-4-5-20250929',
@@ -1409,7 +1791,7 @@ test('Can handle google function calling blocks in content', async () => {
1409
1791
  new SystemMessage("You're a helpful assistant"),
1410
1792
  new HumanMessage('What is the weather like in San Francisco?'),
1411
1793
  new AIMessage({
1412
- content: [
1794
+ content: toLangChainContent([
1413
1795
  {
1414
1796
  // Pass a content block with the `functionCall` object that Google returns.
1415
1797
  functionCall: {
@@ -1418,8 +1800,8 @@ test('Can handle google function calling blocks in content', async () => {
1418
1800
  },
1419
1801
  name: 'get_weather',
1420
1802
  },
1421
- },
1422
- ],
1803
+ } as MessageContentComplex,
1804
+ ]),
1423
1805
  tool_calls: [
1424
1806
  {
1425
1807
  id: toolCallId,
@@ -1442,6 +1824,170 @@ test('Can handle google function calling blocks in content', async () => {
1442
1824
  expect(res.content.length).toBeGreaterThan(1);
1443
1825
  });
1444
1826
 
1827
+ describe('Opus 4.1', () => {
1828
+ test('works without passing any args', async () => {
1829
+ const model = new ChatAnthropic({
1830
+ model: 'claude-opus-4-1',
1831
+ });
1832
+
1833
+ const response = await model.invoke(
1834
+ 'Please respond to this message simply with: Hello'
1835
+ );
1836
+
1837
+ expect(response.content.length).toBeGreaterThan(0);
1838
+ });
1839
+
1840
+ test('works with streaming and thinking', async () => {
1841
+ const model = new ChatAnthropic({
1842
+ model: 'claude-opus-4-1',
1843
+ thinking: {
1844
+ type: 'enabled',
1845
+ budget_tokens: 1024,
1846
+ },
1847
+ });
1848
+
1849
+ const response = await model.invoke(
1850
+ 'Please respond to this message simply with: Hello'
1851
+ );
1852
+
1853
+ expect(response.content.length).toBeGreaterThan(0);
1854
+ });
1855
+ });
1856
+
1857
+ describe('Sonnet 4.5', () => {
1858
+ test('works without passing any args', async () => {
1859
+ const model = new ChatAnthropic({
1860
+ model: 'claude-sonnet-4-5-20250929',
1861
+ });
1862
+
1863
+ const response = await model.invoke(
1864
+ 'Please respond to this message simply with: Hello'
1865
+ );
1866
+
1867
+ expect(response.content.length).toBeGreaterThan(0);
1868
+ });
1869
+
1870
+ test('works with streaming and thinking', async () => {
1871
+ const model = new ChatAnthropic({
1872
+ model: 'claude-sonnet-4-5-20250929',
1873
+ thinking: {
1874
+ type: 'enabled',
1875
+ budget_tokens: 1024,
1876
+ },
1877
+ });
1878
+
1879
+ const response = await model.invoke(
1880
+ 'Please respond to this message simply with: Hello'
1881
+ );
1882
+
1883
+ expect(response.content.length).toBeGreaterThan(0);
1884
+ });
1885
+
1886
+ test('works when passing topP arg', async () => {
1887
+ const model = new ChatAnthropic({
1888
+ model: 'claude-sonnet-4-5-20250929',
1889
+ topP: 0.99,
1890
+ });
1891
+
1892
+ const response = await model.invoke(
1893
+ 'Please respond to this message simply with: Hello'
1894
+ );
1895
+
1896
+ expect(response.content.length).toBeGreaterThan(0);
1897
+ });
1898
+ });
1899
+
1900
+ describe('Opus 4.5', () => {
1901
+ test('works without passing any args', async () => {
1902
+ const model = new ChatAnthropic({
1903
+ model: 'claude-opus-4-5',
1904
+ });
1905
+
1906
+ const response = await model.invoke(
1907
+ 'Please respond to this message simply with: Hello'
1908
+ );
1909
+
1910
+ expect(response.content.length).toBeGreaterThan(0);
1911
+ });
1912
+ });
1913
+
1914
+ test("won't modify structured output content if outputVersion is set", async () => {
1915
+ const schema = z.object({ name: z.string() });
1916
+ const model = new ChatAnthropic({
1917
+ model: 'claude-opus-4-1',
1918
+ outputVersion: 'v1',
1919
+ });
1920
+
1921
+ const response = await model
1922
+ .withStructuredOutput(schema)
1923
+ .invoke("respond with the name 'John'");
1924
+
1925
+ expect(response.name).toBeDefined();
1926
+ });
1927
+
1928
+ describe('will work with native structured output', () => {
1929
+ const schema = z.object({ name: z.string() });
1930
+
1931
+ test.each(['claude-opus-4-1', 'claude-sonnet-4-5-20250929'])(
1932
+ 'works with %s',
1933
+ async (structuredOutputModelName) => {
1934
+ const model = new ChatAnthropic({
1935
+ model: structuredOutputModelName,
1936
+ });
1937
+
1938
+ const response = await model
1939
+ .withStructuredOutput(schema, { method: 'jsonSchema' })
1940
+ .invoke("respond with the name 'John'");
1941
+
1942
+ expect(response.name).toBeDefined();
1943
+ }
1944
+ );
1945
+ });
1946
+
1947
+ describe('Anthropic Reasoning with contentBlocks', () => {
1948
+ test('invoke returns thinking as reasoning in contentBlocks', async () => {
1949
+ const model = new ChatAnthropic({
1950
+ model: extendedThinkingModelName,
1951
+ maxTokens: 5000,
1952
+ thinking: { type: 'enabled', budget_tokens: 2000 },
1953
+ });
1954
+
1955
+ const result = await model.invoke('What is 2 + 2?');
1956
+ const blocks = result.contentBlocks;
1957
+
1958
+ expect(blocks.length).toBeGreaterThan(0);
1959
+
1960
+ const reasoningBlocks = blocks.filter(isReasoningContentBlock);
1961
+ expect(reasoningBlocks.length).toBeGreaterThan(0);
1962
+ expect(reasoningBlocks[0].reasoning.length).toBeGreaterThan(10);
1963
+
1964
+ const textBlocks = blocks.filter((block) => block.type === 'text');
1965
+ expect(textBlocks.length).toBeGreaterThan(0);
1966
+ });
1967
+
1968
+ test('stream returns thinking as reasoning in contentBlocks', async () => {
1969
+ const model = new ChatAnthropic({
1970
+ model: extendedThinkingModelName,
1971
+ maxTokens: 5000,
1972
+ thinking: { type: 'enabled', budget_tokens: 2000 },
1973
+ });
1974
+
1975
+ let fullMessage: AIMessageChunk | null = null;
1976
+ for await (const chunk of await model.stream('What is 3 + 3?')) {
1977
+ fullMessage = fullMessage ? concat(fullMessage, chunk) : chunk;
1978
+ }
1979
+
1980
+ expect(fullMessage).toBeDefined();
1981
+
1982
+ const blocks = fullMessage!.contentBlocks;
1983
+ expect(blocks.length).toBeGreaterThan(0);
1984
+
1985
+ const reasoningBlocks = blocks.filter(isReasoningContentBlock);
1986
+ expect(reasoningBlocks.length).toBeGreaterThan(0);
1987
+ expect(reasoningBlocks[0].reasoning.length).toBeGreaterThan(10);
1988
+ });
1989
+ });
1990
+
1445
1991
  const opus46Model = 'claude-opus-4-6';
1446
1992
 
1447
1993
  describe('Opus 4.6', () => {
@@ -1474,6 +2020,24 @@ describe('Opus 4.6', () => {
1474
2020
  expect(params.top_p).toBeUndefined();
1475
2021
  });
1476
2022
 
2023
+ test('adaptive thinking treats sampling sentinels as unset', () => {
2024
+ const model = new ChatAnthropic({
2025
+ model: opus46Model,
2026
+ apiKey: 'testing',
2027
+ maxTokens: 4096,
2028
+ thinking: { type: 'adaptive' },
2029
+ });
2030
+ model.temperature = -1;
2031
+ model.topP = -1;
2032
+ model.top_k = -1;
2033
+
2034
+ const params = model.invocationParams({});
2035
+
2036
+ expect(params.temperature).toBeUndefined();
2037
+ expect(params.top_k).toBeUndefined();
2038
+ expect(params.top_p).toBeUndefined();
2039
+ });
2040
+
1477
2041
  test('adaptive thinking throws on non-default temperature', () => {
1478
2042
  const model = new ChatAnthropic({
1479
2043
  model: opus46Model,
@@ -1499,9 +2063,7 @@ describe('Opus 4.6', () => {
1499
2063
  expect(result.content).toBeDefined();
1500
2064
 
1501
2065
  if (Array.isArray(result.content)) {
1502
- const textBlocks = (result.content as any[]).filter(
1503
- (b) => b.type === 'text'
1504
- );
2066
+ const textBlocks = result.content.filter(isTextContentBlock);
1505
2067
  expect(textBlocks.length).toBeGreaterThan(0);
1506
2068
  } else {
1507
2069
  expect(typeof result.content).toBe('string');
@@ -1524,6 +2086,25 @@ describe('Opus 4.6', () => {
1524
2086
  const response2 = await model.invoke(messages);
1525
2087
  expect(response2.content).toBeDefined();
1526
2088
  });
2089
+
2090
+ test('withStructuredOutput jsonSchema', async () => {
2091
+ const model = new ChatAnthropic({
2092
+ model: opus46Model,
2093
+ maxTokens: 4096,
2094
+ });
2095
+
2096
+ const schema = z.object({
2097
+ answer: z.number().describe('The numeric answer'),
2098
+ });
2099
+
2100
+ const structured = model.withStructuredOutput(schema, {
2101
+ method: 'jsonSchema',
2102
+ });
2103
+
2104
+ const result = await structured.invoke('What is 2 + 2?');
2105
+ expect(result).toBeDefined();
2106
+ expect(typeof result.answer).toBe('number');
2107
+ });
1527
2108
  });
1528
2109
 
1529
2110
  describe('Effort parameter (outputConfig)', () => {
@@ -1549,7 +2130,7 @@ describe('Opus 4.6', () => {
1549
2130
 
1550
2131
  const params = model.invocationParams({
1551
2132
  outputConfig: { effort: 'low' },
1552
- } as any);
2133
+ });
1553
2134
 
1554
2135
  expect(params.output_config).toEqual({ effort: 'low' });
1555
2136
  });
@@ -1564,7 +2145,7 @@ describe('Opus 4.6', () => {
1564
2145
 
1565
2146
  const params = model.invocationParams({
1566
2147
  outputConfig: { effort: 'low' },
1567
- } as any);
2148
+ });
1568
2149
 
1569
2150
  expect(params.output_config).toEqual({ effort: 'low' });
1570
2151
  });
@@ -1635,9 +2216,10 @@ describe('Opus 4.6', () => {
1635
2216
  thinking: { type: 'adaptive' },
1636
2217
  });
1637
2218
 
1638
- const result = await model.invoke('Say hello.', {
2219
+ const options: CustomAnthropicCallOptions = {
1639
2220
  outputConfig: { effort: 'low' },
1640
- } as any);
2221
+ };
2222
+ const result = await model.invoke('Say hello.', options);
1641
2223
  expect(result.content).toBeDefined();
1642
2224
  });
1643
2225
  });
@@ -1655,7 +2237,7 @@ describe('Opus 4.6', () => {
1655
2237
  type: 'json_schema',
1656
2238
  schema: { type: 'object' },
1657
2239
  },
1658
- } as any);
2240
+ });
1659
2241
 
1660
2242
  expect(params.output_config).toEqual({
1661
2243
  format: {
@@ -1683,7 +2265,7 @@ describe('Opus 4.6', () => {
1683
2265
  type: 'json_schema',
1684
2266
  schema: { type: 'object', properties: { b: { type: 'number' } } },
1685
2267
  },
1686
- } as any);
2268
+ });
1687
2269
 
1688
2270
  expect(params.output_config?.format).toEqual({
1689
2271
  type: 'json_schema',
@@ -1704,7 +2286,7 @@ describe('Opus 4.6', () => {
1704
2286
  type: 'json_schema',
1705
2287
  schema: { type: 'object' },
1706
2288
  },
1707
- } as any);
2289
+ });
1708
2290
 
1709
2291
  expect(params.output_config).toEqual({
1710
2292
  effort: 'medium',
@@ -1739,7 +2321,7 @@ describe('Opus 4.6', () => {
1739
2321
 
1740
2322
  const params = model.invocationParams({
1741
2323
  inferenceGeo: 'us',
1742
- } as any);
2324
+ });
1743
2325
 
1744
2326
  expect(params.inference_geo).toBe('us');
1745
2327
  });
@@ -1754,7 +2336,7 @@ describe('Opus 4.6', () => {
1754
2336
 
1755
2337
  const params = model.invocationParams({
1756
2338
  inferenceGeo: 'us',
1757
- } as any);
2339
+ });
1758
2340
 
1759
2341
  expect(params.inference_geo).toBe('us');
1760
2342
  });
@@ -1784,25 +2366,90 @@ describe('Opus 4.6', () => {
1784
2366
  });
1785
2367
 
1786
2368
  describe('Compaction API', () => {
2369
+ const compactionConfig: AnthropicContextManagementConfigParam = {
2370
+ edits: [
2371
+ {
2372
+ type: 'compact_20260112' as const,
2373
+ trigger: { type: 'input_tokens' as const, value: 50000 },
2374
+ },
2375
+ ],
2376
+ };
2377
+
2378
+ function buildLongConversation(): BaseMessage[] {
2379
+ const padding =
2380
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
2381
+ 'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ' +
2382
+ 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris ' +
2383
+ 'nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in ' +
2384
+ 'reprehenderit in voluptate velit esse cillum dolore eu fugiat. ';
2385
+
2386
+ const messages: BaseMessage[] = [];
2387
+ for (let i = 0; i < 300; i++) {
2388
+ messages.push(new HumanMessage(`${padding} (message ${i})`));
2389
+ messages.push(new AIMessage(`Acknowledged message ${i}. ${padding}`));
2390
+ }
2391
+ messages.push(new HumanMessage('Summarize our conversation.'));
2392
+ return messages;
2393
+ }
2394
+
1787
2395
  test('context_management passed through invocationParams', () => {
1788
2396
  const model = new ChatAnthropic({
1789
2397
  model: opus46Model,
1790
2398
  apiKey: 'testing',
1791
2399
  maxTokens: 4096,
1792
- contextManagement: {
1793
- edits: [
1794
- {
1795
- type: 'compact_20260112',
1796
- trigger: { type: 'input_tokens', value: 50000 },
1797
- },
1798
- ],
1799
- },
2400
+ contextManagement: compactionConfig,
1800
2401
  });
1801
2402
 
1802
2403
  const params = model.invocationParams({});
1803
2404
 
1804
- expect(params.context_management).toBeDefined();
1805
- expect(params.context_management.edits[0].type).toBe('compact_20260112');
2405
+ const contextManagement = expectDefined(params.context_management);
2406
+ const edits = expectDefined(contextManagement.edits);
2407
+ expect(edits[0].type).toBe('compact_20260112');
2408
+ });
2409
+
2410
+ test('accepted by API without triggering', async () => {
2411
+ const model = new ChatAnthropic({
2412
+ model: opus46Model,
2413
+ maxTokens: 4096,
2414
+ contextManagement: compactionConfig,
2415
+ });
2416
+
2417
+ const response = await model.invoke('Say hello.');
2418
+ expect(response.content).toBeDefined();
2419
+ });
2420
+
2421
+ test('triggers compaction block (invoke)', async () => {
2422
+ const model = new ChatAnthropic({
2423
+ model: opus46Model,
2424
+ maxTokens: 4096,
2425
+ contextManagement: compactionConfig,
2426
+ });
2427
+
2428
+ const result = await model.invoke(buildLongConversation());
2429
+
2430
+ const blocks = expectContentArray<ContentBlock>(result.content);
2431
+ const compactionBlock = expectDefined(blocks.find(isCompactionBlock));
2432
+ expect(typeof compactionBlock.content).toBe('string');
2433
+ expect(compactionBlock.content.length).toBeGreaterThan(0);
2434
+ });
2435
+
2436
+ test('triggers compaction block (stream)', async () => {
2437
+ const model = new ChatAnthropic({
2438
+ model: opus46Model,
2439
+ maxTokens: 4096,
2440
+ contextManagement: compactionConfig,
2441
+ });
2442
+
2443
+ let full: AIMessageChunk | undefined;
2444
+ for await (const chunk of await model.stream(buildLongConversation())) {
2445
+ full = full ? concat(full, chunk) : chunk;
2446
+ }
2447
+
2448
+ expect(full).toBeInstanceOf(AIMessageChunk);
2449
+ const blocks = expectContentArray<ContentBlock>(full!.content);
2450
+ const compactionBlock = expectDefined(blocks.find(isCompactionBlock));
2451
+ expect(typeof compactionBlock.content).toBe('string');
2452
+ expect(compactionBlock.content.length).toBeGreaterThan(0);
1806
2453
  });
1807
2454
 
1808
2455
  test('Can properly format messages with compaction blocks', () => {
@@ -1829,8 +2476,10 @@ describe('Opus 4.6', () => {
1829
2476
  expect(formattedMessages.messages[0].role).toBe('assistant');
1830
2477
  expect(formattedMessages.messages[0].content).toHaveLength(2);
1831
2478
 
1832
- const [compactionBlock, textBlock] = formattedMessages.messages[0]
1833
- .content as any[];
2479
+ const [compactionBlock, textBlock] =
2480
+ expectContentArray<Anthropic.Messages.ContentBlockParam>(
2481
+ formattedMessages.messages[0].content
2482
+ );
1834
2483
  expect(compactionBlock).toEqual({
1835
2484
  type: 'compaction',
1836
2485
  content: 'Summary: The user asked about building a web scraper...',