@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.
- package/dist/cjs/graphs/Graph.cjs +22 -3
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hitl/askUserQuestion.cjs +67 -0
- package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -0
- package/dist/cjs/hooks/HookRegistry.cjs +54 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/createToolPolicyHook.cjs +115 -0
- package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -0
- package/dist/cjs/hooks/executeHooks.cjs +40 -1
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/langchain/google-common.cjs +3 -0
- package/dist/cjs/langchain/google-common.cjs.map +1 -0
- package/dist/cjs/langchain/index.cjs +86 -0
- package/dist/cjs/langchain/index.cjs.map +1 -0
- package/dist/cjs/langchain/language_models/chat_models.cjs +3 -0
- package/dist/cjs/langchain/language_models/chat_models.cjs.map +1 -0
- package/dist/cjs/langchain/messages/tool.cjs +3 -0
- package/dist/cjs/langchain/messages/tool.cjs.map +1 -0
- package/dist/cjs/langchain/messages.cjs +51 -0
- package/dist/cjs/langchain/messages.cjs.map +1 -0
- package/dist/cjs/langchain/openai.cjs +3 -0
- package/dist/cjs/langchain/openai.cjs.map +1 -0
- package/dist/cjs/langchain/prompts.cjs +11 -0
- package/dist/cjs/langchain/prompts.cjs.map +1 -0
- package/dist/cjs/langchain/runnables.cjs +19 -0
- package/dist/cjs/langchain/runnables.cjs.map +1 -0
- package/dist/cjs/langchain/tools.cjs +23 -0
- package/dist/cjs/langchain/tools.cjs.map +1 -0
- package/dist/cjs/langchain/utils/env.cjs +11 -0
- package/dist/cjs/langchain/utils/env.cjs.map +1 -0
- package/dist/cjs/llm/anthropic/index.cjs +145 -52
- 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 +21 -14
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +84 -70
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +1 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +213 -3
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +2 -1
- package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +5 -4
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +519 -655
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +20 -458
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +57 -175
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/vertexai/index.cjs +5 -3
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +112 -3
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +2 -1
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +7 -6
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +73 -15
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/langchain.cjs +26 -0
- package/dist/cjs/messages/langchain.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +7 -6
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/run.cjs +400 -42
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +556 -56
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +55 -66
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs +189 -0
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/tavily-search.cjs +372 -0
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -0
- package/dist/cjs/tools/search/tool.cjs +26 -4
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/search/utils.cjs +10 -3
- package/dist/cjs/tools/search/utils.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +22 -3
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hitl/askUserQuestion.mjs +65 -0
- package/dist/esm/hitl/askUserQuestion.mjs.map +1 -0
- package/dist/esm/hooks/HookRegistry.mjs +54 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/createToolPolicyHook.mjs +113 -0
- package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -0
- package/dist/esm/hooks/executeHooks.mjs +40 -1
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/langchain/google-common.mjs +2 -0
- package/dist/esm/langchain/google-common.mjs.map +1 -0
- package/dist/esm/langchain/index.mjs +5 -0
- package/dist/esm/langchain/index.mjs.map +1 -0
- package/dist/esm/langchain/language_models/chat_models.mjs +2 -0
- package/dist/esm/langchain/language_models/chat_models.mjs.map +1 -0
- package/dist/esm/langchain/messages/tool.mjs +2 -0
- package/dist/esm/langchain/messages/tool.mjs.map +1 -0
- package/dist/esm/langchain/messages.mjs +2 -0
- package/dist/esm/langchain/messages.mjs.map +1 -0
- package/dist/esm/langchain/openai.mjs +2 -0
- package/dist/esm/langchain/openai.mjs.map +1 -0
- package/dist/esm/langchain/prompts.mjs +2 -0
- package/dist/esm/langchain/prompts.mjs.map +1 -0
- package/dist/esm/langchain/runnables.mjs +2 -0
- package/dist/esm/langchain/runnables.mjs.map +1 -0
- package/dist/esm/langchain/tools.mjs +2 -0
- package/dist/esm/langchain/tools.mjs.map +1 -0
- package/dist/esm/langchain/utils/env.mjs +2 -0
- package/dist/esm/langchain/utils/env.mjs.map +1 -0
- package/dist/esm/llm/anthropic/index.mjs +146 -54
- 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 +21 -14
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +84 -71
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +1 -1
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +214 -4
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs +2 -1
- package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +5 -4
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +520 -656
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +23 -459
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +57 -175
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/vertexai/index.mjs +5 -3
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +7 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +2 -1
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +7 -6
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +73 -15
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/langchain.mjs +23 -0
- package/dist/esm/messages/langchain.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +7 -6
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/run.mjs +400 -42
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +557 -57
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +55 -66
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-scraper.mjs +186 -0
- package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/tavily-search.mjs +370 -0
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -0
- package/dist/esm/tools/search/tool.mjs +26 -4
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/search/utils.mjs +10 -3
- package/dist/esm/tools/search/utils.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +7 -0
- package/dist/types/hitl/askUserQuestion.d.ts +55 -0
- package/dist/types/hitl/index.d.ts +6 -0
- package/dist/types/hooks/HookRegistry.d.ts +58 -0
- package/dist/types/hooks/createToolPolicyHook.d.ts +87 -0
- package/dist/types/hooks/index.d.ts +4 -1
- package/dist/types/hooks/types.d.ts +109 -3
- package/dist/types/index.d.ts +10 -0
- package/dist/types/langchain/google-common.d.ts +1 -0
- package/dist/types/langchain/index.d.ts +8 -0
- package/dist/types/langchain/language_models/chat_models.d.ts +1 -0
- package/dist/types/langchain/messages/tool.d.ts +1 -0
- package/dist/types/langchain/messages.d.ts +2 -0
- package/dist/types/langchain/openai.d.ts +1 -0
- package/dist/types/langchain/prompts.d.ts +1 -0
- package/dist/types/langchain/runnables.d.ts +2 -0
- package/dist/types/langchain/tools.d.ts +2 -0
- package/dist/types/langchain/utils/env.d.ts +1 -0
- package/dist/types/llm/anthropic/index.d.ts +22 -9
- package/dist/types/llm/anthropic/types.d.ts +5 -1
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +13 -6
- package/dist/types/llm/anthropic/utils/output_parsers.d.ts +1 -1
- package/dist/types/llm/openai/index.d.ts +21 -24
- package/dist/types/llm/openrouter/index.d.ts +11 -9
- package/dist/types/llm/vertexai/index.d.ts +1 -0
- package/dist/types/messages/cache.d.ts +4 -1
- package/dist/types/messages/format.d.ts +4 -1
- package/dist/types/messages/langchain.d.ts +27 -0
- package/dist/types/run.d.ts +117 -1
- package/dist/types/tools/ToolNode.d.ts +26 -1
- package/dist/types/tools/search/tavily-scraper.d.ts +19 -0
- package/dist/types/tools/search/tavily-search.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +99 -5
- package/dist/types/tools/search/utils.d.ts +2 -2
- package/dist/types/types/graph.d.ts +23 -37
- package/dist/types/types/hitl.d.ts +272 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/llm.d.ts +3 -3
- package/dist/types/types/run.d.ts +33 -0
- package/dist/types/types/stream.d.ts +1 -1
- package/dist/types/types/tools.d.ts +19 -0
- package/package.json +80 -17
- package/src/graphs/Graph.ts +33 -4
- package/src/graphs/__tests__/composition.smoke.test.ts +188 -0
- package/src/hitl/askUserQuestion.ts +72 -0
- package/src/hitl/index.ts +7 -0
- package/src/hooks/HookRegistry.ts +71 -0
- package/src/hooks/__tests__/createToolPolicyHook.test.ts +259 -0
- package/src/hooks/createToolPolicyHook.ts +184 -0
- package/src/hooks/executeHooks.ts +50 -1
- package/src/hooks/index.ts +6 -0
- package/src/hooks/types.ts +112 -0
- package/src/index.ts +22 -0
- package/src/langchain/google-common.ts +1 -0
- package/src/langchain/index.ts +8 -0
- package/src/langchain/language_models/chat_models.ts +1 -0
- package/src/langchain/messages/tool.ts +5 -0
- package/src/langchain/messages.ts +21 -0
- package/src/langchain/openai.ts +1 -0
- package/src/langchain/prompts.ts +1 -0
- package/src/langchain/runnables.ts +7 -0
- package/src/langchain/tools.ts +8 -0
- package/src/langchain/utils/env.ts +1 -0
- package/src/llm/anthropic/index.ts +252 -84
- package/src/llm/anthropic/llm.spec.ts +751 -102
- package/src/llm/anthropic/types.ts +9 -1
- package/src/llm/anthropic/utils/message_inputs.ts +37 -19
- package/src/llm/anthropic/utils/message_outputs.ts +119 -101
- package/src/llm/bedrock/index.ts +2 -2
- package/src/llm/bedrock/llm.spec.ts +341 -0
- package/src/llm/bedrock/utils/message_inputs.ts +303 -4
- package/src/llm/bedrock/utils/message_outputs.ts +2 -1
- package/src/llm/custom-chat-models.smoke.test.ts +836 -0
- package/src/llm/google/llm.spec.ts +339 -57
- package/src/llm/google/utils/common.ts +53 -48
- package/src/llm/openai/contentBlocks.test.ts +346 -0
- package/src/llm/openai/index.ts +856 -833
- package/src/llm/openai/utils/index.ts +107 -78
- package/src/llm/openai/utils/messages.test.ts +159 -0
- package/src/llm/openrouter/index.ts +124 -247
- package/src/llm/openrouter/reasoning.test.ts +8 -1
- package/src/llm/vertexai/index.ts +11 -5
- package/src/llm/vertexai/llm.spec.ts +28 -1
- package/src/messages/cache.test.ts +4 -3
- package/src/messages/cache.ts +3 -2
- package/src/messages/core.ts +16 -9
- package/src/messages/format.ts +96 -16
- package/src/messages/formatAgentMessages.test.ts +166 -1
- package/src/messages/langchain.ts +39 -0
- package/src/messages/prune.ts +12 -8
- package/src/run.ts +456 -47
- package/src/scripts/caching.ts +2 -3
- package/src/specs/summarization.test.ts +51 -58
- package/src/tools/ToolNode.ts +706 -63
- package/src/tools/__tests__/hitl.test.ts +3593 -0
- package/src/tools/search/search.ts +83 -73
- package/src/tools/search/tavily-scraper.ts +235 -0
- package/src/tools/search/tavily-search.ts +424 -0
- package/src/tools/search/tavily.test.ts +965 -0
- package/src/tools/search/tool.ts +36 -26
- package/src/tools/search/types.ts +133 -8
- package/src/tools/search/utils.ts +13 -5
- package/src/types/graph.ts +32 -87
- package/src/types/hitl.ts +303 -0
- package/src/types/index.ts +1 -0
- package/src/types/llm.ts +3 -3
- package/src/types/run.ts +33 -0
- package/src/types/stream.ts +1 -1
- package/src/types/tools.ts +19 -0
- package/src/utils/llmConfig.ts +1 -6
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AIMessage,
|
|
3
|
+
AIMessageChunk,
|
|
4
|
+
HumanMessage,
|
|
5
|
+
} from '@langchain/core/messages';
|
|
6
|
+
import type { OpenAIChatInput, OpenAIClient } from '@langchain/openai';
|
|
7
|
+
import type { ChatOpenRouterCallOptions } from '@/llm/openrouter';
|
|
8
|
+
import type { CustomAnthropicInput } from '@/llm/anthropic';
|
|
9
|
+
import type {
|
|
10
|
+
ChatAnthropicToolType,
|
|
11
|
+
AnthropicMCPServerURLDefinition,
|
|
12
|
+
} from '@/llm/anthropic/types';
|
|
13
|
+
import {
|
|
14
|
+
ChatXAI,
|
|
15
|
+
ChatOpenAI,
|
|
16
|
+
ChatDeepSeek,
|
|
17
|
+
ChatMoonshot,
|
|
18
|
+
AzureChatOpenAI,
|
|
19
|
+
CustomOpenAIClient,
|
|
20
|
+
CustomAzureOpenAIClient,
|
|
21
|
+
} from '@/llm/openai';
|
|
22
|
+
import { CustomChatGoogleGenerativeAI } from '@/llm/google';
|
|
23
|
+
import { CustomChatBedrockConverse } from '@/llm/bedrock';
|
|
24
|
+
import { ChatOpenRouter } from '@/llm/openrouter';
|
|
25
|
+
import { CustomAnthropic } from '@/llm/anthropic';
|
|
26
|
+
import { ChatVertexAI } from '@/llm/vertexai';
|
|
27
|
+
|
|
28
|
+
type OpenAIRequestOptions = Parameters<ChatOpenAI['_getClientOptions']>[0];
|
|
29
|
+
type OpenAIRequestOptionsWithBaseURL = ReturnType<
|
|
30
|
+
ChatXAI['_getClientOptions']
|
|
31
|
+
> & {
|
|
32
|
+
baseURL?: string;
|
|
33
|
+
};
|
|
34
|
+
type OpenAIResponsesDelegate = {
|
|
35
|
+
client?: unknown;
|
|
36
|
+
_getClientOptions: (options?: OpenAIRequestOptions) => OpenAIRequestOptions;
|
|
37
|
+
};
|
|
38
|
+
type AnthropicCallOptions = Parameters<
|
|
39
|
+
CustomAnthropic['invocationParams']
|
|
40
|
+
>[0] & {
|
|
41
|
+
outputConfig?: CustomAnthropicInput['outputConfig'] & {
|
|
42
|
+
task_budget?: { type: 'token_budget'; value: number };
|
|
43
|
+
};
|
|
44
|
+
inferenceGeo?: CustomAnthropicInput['inferenceGeo'];
|
|
45
|
+
betas?: CustomAnthropicInput['betas'];
|
|
46
|
+
container?: string;
|
|
47
|
+
mcp_servers?: AnthropicMCPServerURLDefinition[];
|
|
48
|
+
tools?: ChatAnthropicToolType[];
|
|
49
|
+
};
|
|
50
|
+
type AzureReasoningModel = AzureChatOpenAI & {
|
|
51
|
+
reasoning?: { effort: 'low' | 'high' };
|
|
52
|
+
};
|
|
53
|
+
type OpenRouterFields = Partial<
|
|
54
|
+
ChatOpenRouterCallOptions & Pick<OpenAIChatInput, 'model' | 'apiKey'>
|
|
55
|
+
>;
|
|
56
|
+
type CompletionDelegate = {
|
|
57
|
+
completionWithRetry: (request: { messages?: unknown }) => Promise<unknown>;
|
|
58
|
+
};
|
|
59
|
+
type CompletionBackedModel = {
|
|
60
|
+
completions: CompletionDelegate;
|
|
61
|
+
};
|
|
62
|
+
type StreamingCompletionRequest = {
|
|
63
|
+
messages?: unknown;
|
|
64
|
+
stream?: boolean;
|
|
65
|
+
};
|
|
66
|
+
type StreamingCompletionDelegate = {
|
|
67
|
+
completionWithRetry: (
|
|
68
|
+
request: StreamingCompletionRequest
|
|
69
|
+
) => Promise<
|
|
70
|
+
AsyncIterable<OpenAIClient.Chat.Completions.ChatCompletionChunk>
|
|
71
|
+
>;
|
|
72
|
+
};
|
|
73
|
+
type StreamingCompletionBackedModel = {
|
|
74
|
+
completions: StreamingCompletionDelegate;
|
|
75
|
+
};
|
|
76
|
+
type OpenAIStreamEvent = {
|
|
77
|
+
event: string;
|
|
78
|
+
data?: unknown;
|
|
79
|
+
};
|
|
80
|
+
type OpenAIStreamItem =
|
|
81
|
+
| OpenAIClient.Chat.Completions.ChatCompletionChunk
|
|
82
|
+
| OpenAIStreamEvent;
|
|
83
|
+
type MockableCompletionCreate = (
|
|
84
|
+
request: unknown,
|
|
85
|
+
options?: unknown
|
|
86
|
+
) => Promise<
|
|
87
|
+
AsyncIterable<OpenAIStreamItem> | OpenAIClient.Chat.Completions.ChatCompletion
|
|
88
|
+
>;
|
|
89
|
+
type MockableCompletionClient = {
|
|
90
|
+
chat: {
|
|
91
|
+
completions: {
|
|
92
|
+
create: MockableCompletionCreate;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
type MockableCompletionDelegate = OpenAIResponsesDelegate & {
|
|
97
|
+
client?: MockableCompletionClient;
|
|
98
|
+
};
|
|
99
|
+
type OpenRouterReasoningStreamDelta =
|
|
100
|
+
OpenAIClient.Chat.Completions.ChatCompletionChunk.Choice.Delta & {
|
|
101
|
+
reasoning_details?: Array<
|
|
102
|
+
| {
|
|
103
|
+
type: 'reasoning.text';
|
|
104
|
+
text?: string;
|
|
105
|
+
format?: string;
|
|
106
|
+
index?: number;
|
|
107
|
+
}
|
|
108
|
+
| {
|
|
109
|
+
type: 'reasoning.encrypted';
|
|
110
|
+
id?: string;
|
|
111
|
+
data?: string;
|
|
112
|
+
format?: string;
|
|
113
|
+
index?: number;
|
|
114
|
+
}
|
|
115
|
+
>;
|
|
116
|
+
};
|
|
117
|
+
type OpenRouterReasoningStreamChoice = Omit<
|
|
118
|
+
OpenAIClient.Chat.Completions.ChatCompletionChunk.Choice,
|
|
119
|
+
'delta'
|
|
120
|
+
> & {
|
|
121
|
+
delta: OpenRouterReasoningStreamDelta;
|
|
122
|
+
};
|
|
123
|
+
type OpenAIStreamModel = ChatOpenAI | AzureChatOpenAI;
|
|
124
|
+
|
|
125
|
+
const baseAzureFields = {
|
|
126
|
+
azureOpenAIApiKey: 'test-azure-key',
|
|
127
|
+
azureOpenAIApiVersion: '2024-10-21',
|
|
128
|
+
azureOpenAIApiInstanceName: 'test-instance',
|
|
129
|
+
azureOpenAIApiDeploymentName: 'test-deployment',
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const baseBedrockFields = {
|
|
133
|
+
region: 'us-east-1',
|
|
134
|
+
credentials: {
|
|
135
|
+
accessKeyId: 'test-access-key',
|
|
136
|
+
secretAccessKey: 'test-secret-key',
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const createOpenAIStreamChunk = (
|
|
141
|
+
content: string,
|
|
142
|
+
finishReason: OpenAIClient.Chat.Completions.ChatCompletionChunk.Choice['finish_reason'] = null
|
|
143
|
+
): OpenAIClient.Chat.Completions.ChatCompletionChunk => ({
|
|
144
|
+
id: 'chatcmpl-hermes-test',
|
|
145
|
+
object: 'chat.completion.chunk',
|
|
146
|
+
created: 0,
|
|
147
|
+
model: 'hermes-agent',
|
|
148
|
+
choices: [
|
|
149
|
+
{
|
|
150
|
+
index: 0,
|
|
151
|
+
delta: { content },
|
|
152
|
+
finish_reason: finishReason,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
async function* createOpenAIStreamWithCustomEvents(): AsyncGenerator<OpenAIStreamItem> {
|
|
158
|
+
yield createOpenAIStreamChunk('Hello ');
|
|
159
|
+
yield {
|
|
160
|
+
event: 'hermes.tool.progress',
|
|
161
|
+
data: {
|
|
162
|
+
tool: 'execute_code',
|
|
163
|
+
toolCallId: 'call_1',
|
|
164
|
+
status: 'running',
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
yield {
|
|
168
|
+
event: 'hermes.tool.progress',
|
|
169
|
+
data: null,
|
|
170
|
+
};
|
|
171
|
+
yield {
|
|
172
|
+
event: 'message',
|
|
173
|
+
data: createOpenAIStreamChunk('world', 'stop'),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function mockCompletionStream(
|
|
178
|
+
model: OpenAIStreamModel
|
|
179
|
+
): MockableCompletionCreate {
|
|
180
|
+
const completions = (
|
|
181
|
+
model as unknown as { completions: MockableCompletionDelegate }
|
|
182
|
+
).completions;
|
|
183
|
+
completions._getClientOptions(undefined);
|
|
184
|
+
const client = completions.client;
|
|
185
|
+
if (client == null) {
|
|
186
|
+
throw new Error('Expected OpenAI completions client');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const createMock = jest.fn(async () =>
|
|
190
|
+
createOpenAIStreamWithCustomEvents()
|
|
191
|
+
) as MockableCompletionCreate;
|
|
192
|
+
client.chat.completions.create = createMock;
|
|
193
|
+
return createMock;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function mockCompletion(
|
|
197
|
+
model: ChatOpenAI,
|
|
198
|
+
response: OpenAIClient.Chat.Completions.ChatCompletion
|
|
199
|
+
): MockableCompletionCreate {
|
|
200
|
+
const completions = (
|
|
201
|
+
model as unknown as { completions: MockableCompletionDelegate }
|
|
202
|
+
).completions;
|
|
203
|
+
completions._getClientOptions(undefined);
|
|
204
|
+
const client = completions.client;
|
|
205
|
+
if (client == null) {
|
|
206
|
+
throw new Error('Expected OpenAI completions client');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const createMock = jest.fn(async () => response) as MockableCompletionCreate;
|
|
210
|
+
client.chat.completions.create = createMock;
|
|
211
|
+
return createMock;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function expectCustomSSEEventsSkipped(
|
|
215
|
+
model: OpenAIStreamModel
|
|
216
|
+
): Promise<void> {
|
|
217
|
+
const createMock = mockCompletionStream(model);
|
|
218
|
+
const chunks: AIMessageChunk[] = [];
|
|
219
|
+
const stream = await model.stream([new HumanMessage('use a tool')]);
|
|
220
|
+
for await (const chunk of stream) {
|
|
221
|
+
chunks.push(chunk);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const text = chunks
|
|
225
|
+
.map((chunk) => (typeof chunk.content === 'string' ? chunk.content : ''))
|
|
226
|
+
.join('');
|
|
227
|
+
expect(chunks).toHaveLength(2);
|
|
228
|
+
expect(text).toBe('Hello world');
|
|
229
|
+
expect(createMock).toHaveBeenCalledWith(
|
|
230
|
+
expect.objectContaining({ stream: true }),
|
|
231
|
+
expect.any(Object)
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
describe('custom chat model class smoke tests', () => {
|
|
236
|
+
it('keeps the custom OpenAI client, stream delay, and reasoning precedence', () => {
|
|
237
|
+
const model = new ChatOpenAI({
|
|
238
|
+
model: 'gpt-5',
|
|
239
|
+
apiKey: 'test-key',
|
|
240
|
+
reasoning: { effort: 'low' },
|
|
241
|
+
_lc_stream_delay: 3,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const requestOptions = model._getClientOptions({
|
|
245
|
+
headers: { 'x-smoke': 'openai' },
|
|
246
|
+
} as OpenAIRequestOptions);
|
|
247
|
+
|
|
248
|
+
expect(ChatOpenAI.lc_name()).toBe('LibreChatOpenAI');
|
|
249
|
+
expect(model._lc_stream_delay).toBe(3);
|
|
250
|
+
expect(model.exposedClient).toBeInstanceOf(CustomOpenAIClient);
|
|
251
|
+
expect(requestOptions.headers).toEqual(
|
|
252
|
+
expect.objectContaining({ 'x-smoke': 'openai' })
|
|
253
|
+
);
|
|
254
|
+
expect(model.getReasoningParams({ reasoning: { effort: 'high' } })).toEqual(
|
|
255
|
+
{ effort: 'high' }
|
|
256
|
+
);
|
|
257
|
+
const params = model.invocationParams({ reasoningEffort: 'medium' }) as {
|
|
258
|
+
reasoning?: unknown;
|
|
259
|
+
reasoning_effort?: unknown;
|
|
260
|
+
};
|
|
261
|
+
expect(params.reasoning ?? { effort: params.reasoning_effort }).toEqual({
|
|
262
|
+
effort: 'low',
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('keeps the custom OpenAI client on the Responses API path', () => {
|
|
267
|
+
const model = new ChatOpenAI({
|
|
268
|
+
model: 'gpt-5',
|
|
269
|
+
apiKey: 'test-key',
|
|
270
|
+
useResponsesApi: true,
|
|
271
|
+
maxTokens: 32,
|
|
272
|
+
reasoning: { effort: 'low' },
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const params = model.invocationParams({
|
|
276
|
+
reasoningEffort: 'high',
|
|
277
|
+
}) as {
|
|
278
|
+
max_output_tokens?: number;
|
|
279
|
+
max_completion_tokens?: number;
|
|
280
|
+
reasoning?: unknown;
|
|
281
|
+
};
|
|
282
|
+
const responses = (
|
|
283
|
+
model as unknown as { responses: OpenAIResponsesDelegate }
|
|
284
|
+
).responses;
|
|
285
|
+
const requestOptions = responses._getClientOptions({
|
|
286
|
+
headers: { 'x-smoke': 'responses' },
|
|
287
|
+
} as OpenAIRequestOptions);
|
|
288
|
+
|
|
289
|
+
expect(params.max_output_tokens).toBe(32);
|
|
290
|
+
expect(params.max_completion_tokens).toBeUndefined();
|
|
291
|
+
expect(params.reasoning).toEqual({ effort: 'low' });
|
|
292
|
+
expect(responses.client).toBeInstanceOf(CustomOpenAIClient);
|
|
293
|
+
const responsesClient = responses.client as CustomOpenAIClient;
|
|
294
|
+
responsesClient.abortHandler = (): void => undefined;
|
|
295
|
+
expect(model.exposedClient).toBe(responsesClient);
|
|
296
|
+
expect(requestOptions?.headers).toEqual(
|
|
297
|
+
expect.objectContaining({ 'x-smoke': 'responses' })
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('keeps Azure client customization and gates reasoning to reasoning models', () => {
|
|
302
|
+
const model = new AzureChatOpenAI({
|
|
303
|
+
...baseAzureFields,
|
|
304
|
+
_lc_stream_delay: 4,
|
|
305
|
+
}) as AzureReasoningModel;
|
|
306
|
+
model.model = 'gpt-5';
|
|
307
|
+
model.reasoning = { effort: 'low' };
|
|
308
|
+
|
|
309
|
+
const requestOptions = model._getClientOptions({
|
|
310
|
+
headers: { 'x-smoke': 'azure' },
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
expect(AzureChatOpenAI.lc_name()).toBe('LibreChatAzureOpenAI');
|
|
314
|
+
expect(model._lc_stream_delay).toBe(4);
|
|
315
|
+
expect(model.exposedClient).toBeInstanceOf(CustomAzureOpenAIClient);
|
|
316
|
+
const azureResponses = (
|
|
317
|
+
model as unknown as { responses: OpenAIResponsesDelegate }
|
|
318
|
+
).responses;
|
|
319
|
+
azureResponses._getClientOptions(undefined);
|
|
320
|
+
expect(azureResponses.client).toBeInstanceOf(CustomAzureOpenAIClient);
|
|
321
|
+
const azureResponsesClient =
|
|
322
|
+
azureResponses.client as CustomAzureOpenAIClient;
|
|
323
|
+
azureResponsesClient.abortHandler = (): void => undefined;
|
|
324
|
+
expect(model.exposedClient).toBe(azureResponsesClient);
|
|
325
|
+
expect(requestOptions.headers).toEqual(
|
|
326
|
+
expect.objectContaining({
|
|
327
|
+
'api-key': 'test-azure-key',
|
|
328
|
+
'x-smoke': 'azure',
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
expect(requestOptions.query).toEqual(
|
|
332
|
+
expect.objectContaining({ 'api-version': '2024-10-21' })
|
|
333
|
+
);
|
|
334
|
+
expect(model.getReasoningParams()).toEqual({ effort: 'low' });
|
|
335
|
+
|
|
336
|
+
const nonReasoningModel = new AzureChatOpenAI({
|
|
337
|
+
...baseAzureFields,
|
|
338
|
+
}) as AzureReasoningModel;
|
|
339
|
+
nonReasoningModel.model = 'gpt-4o';
|
|
340
|
+
nonReasoningModel.reasoning = { effort: 'low' };
|
|
341
|
+
expect(nonReasoningModel.getReasoningParams()).toBeUndefined();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('keeps DeepSeek, Moonshot, and xAI on LibreChat wrapper semantics', () => {
|
|
345
|
+
const deepSeek = new ChatDeepSeek({
|
|
346
|
+
model: 'deepseek-chat',
|
|
347
|
+
apiKey: 'test-key',
|
|
348
|
+
_lc_stream_delay: 5,
|
|
349
|
+
});
|
|
350
|
+
deepSeek._getClientOptions();
|
|
351
|
+
|
|
352
|
+
const moonshot = new ChatMoonshot({
|
|
353
|
+
model: 'moonshot-v1-8k',
|
|
354
|
+
apiKey: 'test-key',
|
|
355
|
+
_lc_stream_delay: 6,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const xai = new ChatXAI({
|
|
359
|
+
model: 'grok-3-fast',
|
|
360
|
+
apiKey: 'test-key',
|
|
361
|
+
configuration: { baseURL: 'https://xai.test/v1' },
|
|
362
|
+
_lc_stream_delay: 7,
|
|
363
|
+
});
|
|
364
|
+
const xaiRequestOptions =
|
|
365
|
+
xai._getClientOptions() as OpenAIRequestOptionsWithBaseURL;
|
|
366
|
+
|
|
367
|
+
expect(ChatDeepSeek.lc_name()).toBe('LibreChatDeepSeek');
|
|
368
|
+
expect(deepSeek._lc_stream_delay).toBe(5);
|
|
369
|
+
expect(deepSeek.exposedClient).toBeInstanceOf(CustomOpenAIClient);
|
|
370
|
+
expect(ChatMoonshot.lc_name()).toBe('LibreChatMoonshot');
|
|
371
|
+
expect(moonshot._lc_stream_delay).toBe(6);
|
|
372
|
+
expect(ChatXAI.lc_name()).toBe('LibreChatXAI');
|
|
373
|
+
expect(xai._lc_stream_delay).toBe(7);
|
|
374
|
+
expect(xai.exposedClient).toBeInstanceOf(CustomOpenAIClient);
|
|
375
|
+
expect(xaiRequestOptions.baseURL).toBe('https://xai.test/v1');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('skips custom OpenAI-compatible SSE events during OpenAI streaming', async () => {
|
|
379
|
+
await expectCustomSSEEventsSkipped(
|
|
380
|
+
new ChatOpenAI({
|
|
381
|
+
model: 'hermes-agent',
|
|
382
|
+
apiKey: 'test-key',
|
|
383
|
+
streaming: true,
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('skips custom OpenAI-compatible SSE events during Azure streaming', async () => {
|
|
389
|
+
await expectCustomSSEEventsSkipped(
|
|
390
|
+
new AzureChatOpenAI({
|
|
391
|
+
...baseAzureFields,
|
|
392
|
+
})
|
|
393
|
+
);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('passes non-streaming OpenAI completions through unchanged', async () => {
|
|
397
|
+
const model = new ChatOpenAI({
|
|
398
|
+
model: 'hermes-agent',
|
|
399
|
+
apiKey: 'test-key',
|
|
400
|
+
});
|
|
401
|
+
const createMock = mockCompletion(model, {
|
|
402
|
+
id: 'chatcmpl-nonstream-test',
|
|
403
|
+
object: 'chat.completion',
|
|
404
|
+
created: 0,
|
|
405
|
+
model: 'hermes-agent',
|
|
406
|
+
choices: [
|
|
407
|
+
{
|
|
408
|
+
index: 0,
|
|
409
|
+
finish_reason: 'stop',
|
|
410
|
+
logprobs: null,
|
|
411
|
+
message: {
|
|
412
|
+
role: 'assistant',
|
|
413
|
+
content: 'plain response',
|
|
414
|
+
refusal: null,
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const response = await model.invoke([new HumanMessage('no stream')]);
|
|
421
|
+
|
|
422
|
+
expect(response.content).toBe('plain response');
|
|
423
|
+
expect(createMock).toHaveBeenCalledWith(
|
|
424
|
+
expect.objectContaining({ stream: false }),
|
|
425
|
+
expect.any(Object)
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('keeps Moonshot reasoning content in completion requests', async () => {
|
|
430
|
+
const moonshot = new ChatMoonshot({
|
|
431
|
+
model: 'moonshot-v1-8k',
|
|
432
|
+
apiKey: 'test-key',
|
|
433
|
+
streaming: false,
|
|
434
|
+
});
|
|
435
|
+
const completions = (moonshot as unknown as CompletionBackedModel)
|
|
436
|
+
.completions;
|
|
437
|
+
let requestMessages: unknown;
|
|
438
|
+
|
|
439
|
+
completions.completionWithRetry = async (request): Promise<unknown> => {
|
|
440
|
+
requestMessages = request.messages;
|
|
441
|
+
return {
|
|
442
|
+
id: 'chatcmpl-test',
|
|
443
|
+
object: 'chat.completion',
|
|
444
|
+
created: 0,
|
|
445
|
+
model: 'moonshot-v1-8k',
|
|
446
|
+
choices: [
|
|
447
|
+
{
|
|
448
|
+
index: 0,
|
|
449
|
+
finish_reason: 'stop',
|
|
450
|
+
message: {
|
|
451
|
+
role: 'assistant',
|
|
452
|
+
content: 'ok',
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
};
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
await moonshot.invoke([
|
|
460
|
+
new AIMessage({
|
|
461
|
+
content: '',
|
|
462
|
+
additional_kwargs: { reasoning_content: 'kept-thinking' },
|
|
463
|
+
tool_calls: [
|
|
464
|
+
{
|
|
465
|
+
id: 'call_1',
|
|
466
|
+
name: 'lookup',
|
|
467
|
+
args: { q: 'test' },
|
|
468
|
+
type: 'tool_call',
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
}),
|
|
472
|
+
]);
|
|
473
|
+
|
|
474
|
+
expect(requestMessages).toEqual([
|
|
475
|
+
expect.objectContaining({
|
|
476
|
+
role: 'assistant',
|
|
477
|
+
content: '',
|
|
478
|
+
reasoning_content: 'kept-thinking',
|
|
479
|
+
tool_calls: expect.any(Array),
|
|
480
|
+
}),
|
|
481
|
+
]);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('keeps OpenRouter reasoning isolated from OpenAI reasoning_effort', () => {
|
|
485
|
+
const fields: OpenRouterFields = {
|
|
486
|
+
model: 'openrouter/test-model',
|
|
487
|
+
apiKey: 'test-key',
|
|
488
|
+
reasoning: { effort: 'xhigh', max_tokens: 2048 },
|
|
489
|
+
};
|
|
490
|
+
const model = new ChatOpenRouter(fields);
|
|
491
|
+
|
|
492
|
+
const params = model.invocationParams();
|
|
493
|
+
|
|
494
|
+
expect(ChatOpenRouter.lc_name()).toBe('LibreChatOpenRouter');
|
|
495
|
+
expect(params.reasoning).toEqual({ effort: 'xhigh', max_tokens: 2048 });
|
|
496
|
+
expect(params.reasoning_effort).toBeUndefined();
|
|
497
|
+
|
|
498
|
+
const callParams = model.invocationParams({
|
|
499
|
+
reasoning: { effort: 'low', exclude: true },
|
|
500
|
+
} as ChatOpenRouterCallOptions);
|
|
501
|
+
expect(callParams.reasoning).toEqual({
|
|
502
|
+
effort: 'low',
|
|
503
|
+
max_tokens: 2048,
|
|
504
|
+
exclude: true,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const legacyModel = new ChatOpenRouter({
|
|
508
|
+
model: 'openrouter/test-model',
|
|
509
|
+
apiKey: 'test-key',
|
|
510
|
+
include_reasoning: true,
|
|
511
|
+
});
|
|
512
|
+
expect(legacyModel.invocationParams().reasoning).toEqual({
|
|
513
|
+
enabled: true,
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('keeps OpenRouter streaming reasoning details stable', async () => {
|
|
518
|
+
const model = new ChatOpenRouter({
|
|
519
|
+
model: 'anthropic/claude-sonnet-test',
|
|
520
|
+
apiKey: 'test-key',
|
|
521
|
+
});
|
|
522
|
+
const completions = (model as unknown as StreamingCompletionBackedModel)
|
|
523
|
+
.completions;
|
|
524
|
+
let requestMessages: unknown;
|
|
525
|
+
const createChunk = (
|
|
526
|
+
choice: OpenRouterReasoningStreamChoice
|
|
527
|
+
): OpenAIClient.Chat.Completions.ChatCompletionChunk => ({
|
|
528
|
+
id: 'chatcmpl-openrouter-test',
|
|
529
|
+
object: 'chat.completion.chunk',
|
|
530
|
+
created: 0,
|
|
531
|
+
model: 'anthropic/claude-sonnet-test',
|
|
532
|
+
choices: [choice],
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
async function* streamChunks(): AsyncGenerator<OpenAIClient.Chat.Completions.ChatCompletionChunk> {
|
|
536
|
+
yield createChunk({
|
|
537
|
+
index: 0,
|
|
538
|
+
delta: {
|
|
539
|
+
role: 'assistant',
|
|
540
|
+
content: '',
|
|
541
|
+
reasoning_details: [
|
|
542
|
+
{
|
|
543
|
+
type: 'reasoning.text',
|
|
544
|
+
text: 'Think ',
|
|
545
|
+
format: 'text',
|
|
546
|
+
index: 0,
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
},
|
|
550
|
+
finish_reason: null,
|
|
551
|
+
});
|
|
552
|
+
yield createChunk({
|
|
553
|
+
index: 0,
|
|
554
|
+
delta: {
|
|
555
|
+
content: 'answer',
|
|
556
|
+
reasoning_details: [
|
|
557
|
+
{ type: 'reasoning.text', text: 'hard', index: 0 },
|
|
558
|
+
{
|
|
559
|
+
type: 'reasoning.encrypted',
|
|
560
|
+
id: 'sig_1',
|
|
561
|
+
data: 'encrypted',
|
|
562
|
+
format: 'anthropic',
|
|
563
|
+
index: 1,
|
|
564
|
+
},
|
|
565
|
+
],
|
|
566
|
+
},
|
|
567
|
+
finish_reason: null,
|
|
568
|
+
});
|
|
569
|
+
yield createChunk({
|
|
570
|
+
index: 0,
|
|
571
|
+
delta: { content: '' },
|
|
572
|
+
finish_reason: 'stop',
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
completions.completionWithRetry = async (
|
|
577
|
+
request
|
|
578
|
+
): Promise<
|
|
579
|
+
AsyncIterable<OpenAIClient.Chat.Completions.ChatCompletionChunk>
|
|
580
|
+
> => {
|
|
581
|
+
requestMessages = request.messages;
|
|
582
|
+
return streamChunks();
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
const chunks: AIMessageChunk[] = [];
|
|
586
|
+
const stream = await model.stream([
|
|
587
|
+
new AIMessage({
|
|
588
|
+
content: '',
|
|
589
|
+
additional_kwargs: {
|
|
590
|
+
reasoning_details: [
|
|
591
|
+
{
|
|
592
|
+
type: 'reasoning.text',
|
|
593
|
+
text: 'previous thought',
|
|
594
|
+
index: 0,
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
type: 'reasoning.encrypted',
|
|
598
|
+
id: 'prev_sig',
|
|
599
|
+
data: 'previous encrypted',
|
|
600
|
+
index: 1,
|
|
601
|
+
},
|
|
602
|
+
],
|
|
603
|
+
},
|
|
604
|
+
tool_calls: [
|
|
605
|
+
{
|
|
606
|
+
id: 'call_1',
|
|
607
|
+
name: 'lookup',
|
|
608
|
+
args: { q: 'test' },
|
|
609
|
+
type: 'tool_call',
|
|
610
|
+
},
|
|
611
|
+
],
|
|
612
|
+
}),
|
|
613
|
+
]);
|
|
614
|
+
for await (const chunk of stream) {
|
|
615
|
+
chunks.push(chunk);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
expect(requestMessages).toEqual([
|
|
619
|
+
expect.objectContaining({
|
|
620
|
+
role: 'assistant',
|
|
621
|
+
tool_calls: expect.any(Array),
|
|
622
|
+
content: [
|
|
623
|
+
expect.objectContaining({
|
|
624
|
+
type: 'thinking',
|
|
625
|
+
thinking: 'previous thought',
|
|
626
|
+
}),
|
|
627
|
+
expect.objectContaining({
|
|
628
|
+
type: 'redacted_thinking',
|
|
629
|
+
data: 'previous encrypted',
|
|
630
|
+
id: 'prev_sig',
|
|
631
|
+
}),
|
|
632
|
+
],
|
|
633
|
+
}),
|
|
634
|
+
]);
|
|
635
|
+
expect(chunks).toHaveLength(3);
|
|
636
|
+
expect(chunks[0].additional_kwargs.reasoning).toBe('Think ');
|
|
637
|
+
expect(chunks[0].additional_kwargs.reasoning_details).toBeUndefined();
|
|
638
|
+
expect(chunks[1].additional_kwargs.reasoning).toBe('hard');
|
|
639
|
+
expect(chunks[1].additional_kwargs.reasoning_details).toBeUndefined();
|
|
640
|
+
expect(chunks[2].additional_kwargs.reasoning_details).toEqual([
|
|
641
|
+
{
|
|
642
|
+
type: 'reasoning.text',
|
|
643
|
+
text: 'Think hard',
|
|
644
|
+
format: 'text',
|
|
645
|
+
index: 0,
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
type: 'reasoning.encrypted',
|
|
649
|
+
id: 'sig_1',
|
|
650
|
+
data: 'encrypted',
|
|
651
|
+
format: 'anthropic',
|
|
652
|
+
index: 1,
|
|
653
|
+
},
|
|
654
|
+
]);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it('keeps Anthropic output, residency, compaction, and stream-delay options', () => {
|
|
658
|
+
const contextManagement = {
|
|
659
|
+
edits: [
|
|
660
|
+
{
|
|
661
|
+
type: 'compact_20260112' as const,
|
|
662
|
+
trigger: { type: 'input_tokens' as const, value: 50000 },
|
|
663
|
+
},
|
|
664
|
+
],
|
|
665
|
+
};
|
|
666
|
+
const model = new CustomAnthropic({
|
|
667
|
+
model: 'claude-sonnet-4-5-20250929',
|
|
668
|
+
apiKey: 'test-key',
|
|
669
|
+
maxTokens: 4096,
|
|
670
|
+
outputConfig: { effort: 'medium' },
|
|
671
|
+
inferenceGeo: 'us',
|
|
672
|
+
contextManagement,
|
|
673
|
+
_lc_stream_delay: 8,
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
const params = model.invocationParams({
|
|
677
|
+
outputConfig: { effort: 'low' },
|
|
678
|
+
inferenceGeo: 'eu',
|
|
679
|
+
} as AnthropicCallOptions);
|
|
680
|
+
|
|
681
|
+
expect(CustomAnthropic.lc_name()).toBe('LibreChatAnthropic');
|
|
682
|
+
expect(model._lc_stream_delay).toBe(8);
|
|
683
|
+
expect(params.output_config).toEqual({ effort: 'low' });
|
|
684
|
+
expect(params.inference_geo).toBe('eu');
|
|
685
|
+
expect(params.context_management).toEqual(contextManagement);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it('keeps Anthropic beta, MCP, and container request wiring current', () => {
|
|
689
|
+
const contextManagement = {
|
|
690
|
+
edits: [
|
|
691
|
+
{
|
|
692
|
+
type: 'compact_20260112' as const,
|
|
693
|
+
trigger: { type: 'input_tokens' as const, value: 50000 },
|
|
694
|
+
},
|
|
695
|
+
],
|
|
696
|
+
};
|
|
697
|
+
const mcpServers: AnthropicMCPServerURLDefinition[] = [
|
|
698
|
+
{
|
|
699
|
+
type: 'url',
|
|
700
|
+
url: 'https://example.com/mcp',
|
|
701
|
+
name: 'docs',
|
|
702
|
+
},
|
|
703
|
+
];
|
|
704
|
+
const model = new CustomAnthropic({
|
|
705
|
+
model: 'claude-opus-4-7-test',
|
|
706
|
+
apiKey: 'test-key',
|
|
707
|
+
maxTokens: 4096,
|
|
708
|
+
contextManagement,
|
|
709
|
+
betas: ['model-beta'],
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
const params = model.invocationParams({
|
|
713
|
+
outputConfig: {
|
|
714
|
+
effort: 'low',
|
|
715
|
+
task_budget: { type: 'token_budget', value: 1024 },
|
|
716
|
+
},
|
|
717
|
+
betas: ['request-beta', 'model-beta'],
|
|
718
|
+
container: 'container_123',
|
|
719
|
+
mcp_servers: mcpServers,
|
|
720
|
+
tools: [
|
|
721
|
+
{
|
|
722
|
+
type: 'tool_search_tool_bm25_20251119',
|
|
723
|
+
name: 'search',
|
|
724
|
+
} as ChatAnthropicToolType,
|
|
725
|
+
],
|
|
726
|
+
} as AnthropicCallOptions);
|
|
727
|
+
|
|
728
|
+
expect(params.betas).toEqual([
|
|
729
|
+
'model-beta',
|
|
730
|
+
'request-beta',
|
|
731
|
+
'advanced-tool-use-2025-11-20',
|
|
732
|
+
'compact-2026-01-12',
|
|
733
|
+
'task-budgets-2026-03-13',
|
|
734
|
+
]);
|
|
735
|
+
expect(params.container).toBe('container_123');
|
|
736
|
+
expect(params.mcp_servers).toBe(mcpServers);
|
|
737
|
+
expect(params.temperature).toBeUndefined();
|
|
738
|
+
expect(params.top_k).toBeUndefined();
|
|
739
|
+
expect(params.top_p).toBeUndefined();
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it('matches Anthropic Opus 4.7 sampling compatibility checks', () => {
|
|
743
|
+
const thinkingModel = new CustomAnthropic({
|
|
744
|
+
model: 'claude-opus-4-7-test',
|
|
745
|
+
apiKey: 'test-key',
|
|
746
|
+
maxTokens: 4096,
|
|
747
|
+
thinking: { type: 'enabled', budget_tokens: 1024 },
|
|
748
|
+
});
|
|
749
|
+
const topKModel = new CustomAnthropic({
|
|
750
|
+
model: 'claude-opus-4-7-test',
|
|
751
|
+
apiKey: 'test-key',
|
|
752
|
+
maxTokens: 4096,
|
|
753
|
+
topK: 5,
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
expect(() => thinkingModel.invocationParams()).toThrow(
|
|
757
|
+
'thinking.type="enabled" is not supported for claude-opus-4-7'
|
|
758
|
+
);
|
|
759
|
+
expect(() => topKModel.invocationParams()).toThrow(
|
|
760
|
+
'topK is not supported for claude-opus-4-7'
|
|
761
|
+
);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it('keeps Bedrock Converse application profiles and service tier passthroughs', () => {
|
|
765
|
+
const applicationInferenceProfile =
|
|
766
|
+
'arn:aws:bedrock:eu-west-1:123456789012:application-inference-profile/test-profile';
|
|
767
|
+
const model = new CustomChatBedrockConverse({
|
|
768
|
+
...baseBedrockFields,
|
|
769
|
+
model: 'anthropic.claude-3-haiku-20240307-v1:0',
|
|
770
|
+
applicationInferenceProfile,
|
|
771
|
+
serviceTier: 'priority',
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
expect(CustomChatBedrockConverse.lc_name()).toBe(
|
|
775
|
+
'LibreChatBedrockConverse'
|
|
776
|
+
);
|
|
777
|
+
expect(model.applicationInferenceProfile).toBe(applicationInferenceProfile);
|
|
778
|
+
expect(model.invocationParams({}).serviceTier).toEqual({
|
|
779
|
+
type: 'priority',
|
|
780
|
+
});
|
|
781
|
+
expect(model.invocationParams({ serviceTier: 'flex' }).serviceTier).toEqual(
|
|
782
|
+
{ type: 'flex' }
|
|
783
|
+
);
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
it('keeps Google and Vertex thinking configuration wiring offline', () => {
|
|
787
|
+
const thinkingConfig = {
|
|
788
|
+
thinkingLevel: 'HIGH' as const,
|
|
789
|
+
includeThoughts: true,
|
|
790
|
+
};
|
|
791
|
+
const google = new CustomChatGoogleGenerativeAI({
|
|
792
|
+
model: 'models/gemini-3-pro-preview',
|
|
793
|
+
apiKey: 'test-key',
|
|
794
|
+
thinkingConfig,
|
|
795
|
+
});
|
|
796
|
+
const vertex = new ChatVertexAI({
|
|
797
|
+
model: 'gemini-3-pro-preview',
|
|
798
|
+
location: 'global',
|
|
799
|
+
thinkingBudget: -1,
|
|
800
|
+
thinkingConfig,
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
expect(CustomChatGoogleGenerativeAI.lc_name()).toBe(
|
|
804
|
+
'LibreChatGoogleGenerativeAI'
|
|
805
|
+
);
|
|
806
|
+
expect(google.model).toBe('gemini-3-pro-preview');
|
|
807
|
+
expect(google._isMultimodalModel).toBe(true);
|
|
808
|
+
expect(google.thinkingConfig).toEqual(thinkingConfig);
|
|
809
|
+
expect(ChatVertexAI.lc_name()).toBe('LibreChatVertexAI');
|
|
810
|
+
expect(vertex.dynamicThinkingBudget).toBe(true);
|
|
811
|
+
expect(vertex.thinkingConfig).toEqual(thinkingConfig);
|
|
812
|
+
expect(vertex.invocationParams({}).maxReasoningTokens).toBe(-1);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('uppercases custom OpenAI fetch methods before dispatch', async () => {
|
|
816
|
+
let method: string | undefined;
|
|
817
|
+
const client = new CustomOpenAIClient({
|
|
818
|
+
apiKey: 'test-key',
|
|
819
|
+
fetch: async (_url, init): Promise<Response> => {
|
|
820
|
+
method = init?.method;
|
|
821
|
+
return new Response('{}', { status: 200 });
|
|
822
|
+
},
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
const response = await client.fetchWithTimeout(
|
|
826
|
+
'https://example.test/v1/chat/completions',
|
|
827
|
+
{ method: 'patch' },
|
|
828
|
+
1000,
|
|
829
|
+
new AbortController()
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
expect(response.status).toBe(200);
|
|
833
|
+
expect(method).toBe('PATCH');
|
|
834
|
+
expect(client.abortHandler).toBeDefined();
|
|
835
|
+
});
|
|
836
|
+
});
|