@librechat/agents 3.6.14 → 3.6.16
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/README.md +41 -0
- package/dist/cjs/agents/AgentContext.cjs +1 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +9 -7
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/contextPressureMeter.cjs +8 -6
- package/dist/cjs/llm/contextPressureMeter.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +1 -0
- package/dist/cjs/llm/init.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +9 -2
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +1 -1
- package/dist/cjs/llm/prepareProviderRequest.cjs +6 -3
- package/dist/cjs/llm/prepareProviderRequest.cjs.map +1 -1
- package/dist/cjs/llm/providerRegistry.cjs +69 -0
- package/dist/cjs/llm/providerRegistry.cjs.map +1 -0
- package/dist/cjs/llm/providers.cjs +38 -25
- package/dist/cjs/llm/providers.cjs.map +1 -1
- package/dist/cjs/llm/request.cjs +6 -4
- package/dist/cjs/llm/request.cjs.map +1 -1
- package/dist/cjs/llm/truncation.cjs +2 -1
- package/dist/cjs/llm/truncation.cjs.map +1 -1
- package/dist/cjs/main.cjs +18 -15
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/prune.cjs +8 -4
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/provider-registration.cjs +4 -0
- package/dist/cjs/run.cjs +2 -2
- package/dist/cjs/stream.cjs +1 -1
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +2 -2
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/utils/llm.cjs +6 -4
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/toolContent.cjs +2 -2
- package/dist/cjs/utils/toolContent.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +1 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +10 -8
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/contextPressureMeter.mjs +8 -6
- package/dist/esm/llm/contextPressureMeter.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +1 -0
- package/dist/esm/llm/init.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +9 -2
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +1 -1
- package/dist/esm/llm/prepareProviderRequest.mjs +7 -4
- package/dist/esm/llm/prepareProviderRequest.mjs.map +1 -1
- package/dist/esm/llm/providerRegistry.mjs +64 -0
- package/dist/esm/llm/providerRegistry.mjs.map +1 -0
- package/dist/esm/llm/providers.mjs +36 -22
- package/dist/esm/llm/providers.mjs.map +1 -1
- package/dist/esm/llm/request.mjs +6 -4
- package/dist/esm/llm/request.mjs.map +1 -1
- package/dist/esm/llm/truncation.mjs +2 -1
- package/dist/esm/llm/truncation.mjs.map +1 -1
- package/dist/esm/main.mjs +12 -10
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/prune.mjs +8 -4
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/provider-registration.mjs +3 -0
- package/dist/esm/run.mjs +2 -2
- package/dist/esm/stream.mjs +1 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +2 -2
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/utils/llm.mjs +6 -4
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/toolContent.mjs +3 -3
- package/dist/esm/utils/toolContent.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +5 -5
- package/dist/types/index.d.ts +2 -0
- package/dist/types/llm/contextOverflowRecovery.d.ts +2 -2
- package/dist/types/llm/init.d.ts +21 -14
- package/dist/types/llm/invoke.d.ts +5 -6
- package/dist/types/llm/prepareProviderRequest.d.ts +5 -6
- package/dist/types/llm/providerRegistry.d.ts +11 -0
- package/dist/types/llm/providers.d.ts +4 -5
- package/dist/types/llm/request.d.ts +2 -3
- package/dist/types/llm/truncation.d.ts +2 -2
- package/dist/types/messages/core.d.ts +1 -2
- package/dist/types/messages/format.d.ts +3 -3
- package/dist/types/messages/prune.d.ts +3 -2
- package/dist/types/provider-registration.d.ts +15 -0
- package/dist/types/stream.d.ts +1 -2
- package/dist/types/summarization/node.d.ts +1 -2
- package/dist/types/types/activityLabel.d.ts +3 -4
- package/dist/types/types/graph.d.ts +4 -5
- package/dist/types/types/llm.d.ts +54 -13
- package/dist/types/types/reasoningLabel.d.ts +2 -3
- package/dist/types/types/run.d.ts +3 -3
- package/dist/types/types/summarize.d.ts +2 -2
- package/dist/types/utils/errors.d.ts +3 -3
- package/dist/types/utils/llm.d.ts +4 -4
- package/dist/types/utils/llmConfig.d.ts +2 -2
- package/package.json +9 -1
- package/src/agents/AgentContext.ts +27 -26
- package/src/graphs/Graph.ts +14 -14
- package/src/index.ts +5 -0
- package/src/llm/contextOverflowRecovery.ts +2 -2
- package/src/llm/contextPressureMeter.ts +23 -13
- package/src/llm/init.ts +40 -17
- package/src/llm/invoke.ts +36 -21
- package/src/llm/prepareProviderRequest.ts +19 -18
- package/src/llm/providerRegistry.ts +134 -0
- package/src/llm/providers.ts +73 -30
- package/src/llm/request.ts +10 -6
- package/src/llm/truncation.ts +9 -3
- package/src/messages/core.ts +2 -2
- package/src/messages/format.ts +14 -16
- package/src/messages/prune.ts +22 -17
- package/src/provider-registration.ts +29 -0
- package/src/run.ts +1 -1
- package/src/session/AgentSession.ts +1 -1
- package/src/stream.ts +7 -8
- package/src/summarization/node.ts +7 -7
- package/src/types/activityLabel.ts +3 -4
- package/src/types/graph.ts +5 -5
- package/src/types/llm.ts +95 -13
- package/src/types/reasoningLabel.ts +2 -3
- package/src/types/run.ts +3 -3
- package/src/types/summarize.ts +2 -2
- package/src/utils/errors.ts +3 -3
- package/src/utils/llm.ts +22 -15
- package/src/utils/llmConfig.ts +4 -4
- package/src/utils/toolContent.ts +9 -3
package/src/llm/invoke.ts
CHANGED
|
@@ -13,14 +13,9 @@ import type { ToolCall } from '@langchain/core/messages/tool';
|
|
|
13
13
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
14
14
|
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
15
15
|
import type { PreparedProviderRequest } from '@/llm/prepareProviderRequest';
|
|
16
|
-
import type { StreamLimitState } from '@/llm/streamLimits';
|
|
17
16
|
import type { ContextOverflowContext } from '@/utils/errors';
|
|
17
|
+
import type { StreamLimitState } from '@/llm/streamLimits';
|
|
18
18
|
import type * as t from '@/types';
|
|
19
|
-
import {
|
|
20
|
-
modifyDeltaProperties,
|
|
21
|
-
} from '@/messages';
|
|
22
|
-
import { ChatModelStreamHandler, dispatchesChatModelStream } from '@/stream';
|
|
23
|
-
import { Constants, ContentTypes, GraphEvents, Providers } from '@/common';
|
|
24
19
|
import {
|
|
25
20
|
enforceStreamLimitsForWireChunk,
|
|
26
21
|
registerActiveStreamLimitGeneration,
|
|
@@ -35,11 +30,17 @@ import {
|
|
|
35
30
|
assertPreparedProviderRequestFor,
|
|
36
31
|
prepareProviderRequest,
|
|
37
32
|
} from '@/llm/prepareProviderRequest';
|
|
33
|
+
import {
|
|
34
|
+
getProviderFamily,
|
|
35
|
+
providerUsesManualToolStream,
|
|
36
|
+
} from '@/llm/providers';
|
|
37
|
+
import { ChatModelStreamHandler, dispatchesChatModelStream } from '@/stream';
|
|
38
|
+
import { Constants, ContentTypes, GraphEvents, Providers } from '@/common';
|
|
38
39
|
import { assertNotTruncatedToolCall } from '@/llm/truncation';
|
|
39
|
-
import { manualToolStreamProviders } from '@/llm/providers';
|
|
40
40
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
41
41
|
import { getContextOverflowInfo } from '@/utils/errors';
|
|
42
42
|
import { appendCallbacks } from '@/utils/callbacks';
|
|
43
|
+
import { modifyDeltaProperties } from '@/messages';
|
|
43
44
|
import { canSealPreempt } from '@/llm/preempt';
|
|
44
45
|
import { initializeModel } from '@/llm/init';
|
|
45
46
|
|
|
@@ -105,6 +106,19 @@ export type OnChunk = (
|
|
|
105
106
|
/** Unique per-model-attempt sequence; see the stamp in `attemptInvoke`. */
|
|
106
107
|
let streamLimitAttemptSeq = 0;
|
|
107
108
|
|
|
109
|
+
function getManualToolStreamNormalizationProvider(
|
|
110
|
+
provider: t.ProviderName
|
|
111
|
+
): t.ProviderName {
|
|
112
|
+
const family = getProviderFamily(provider);
|
|
113
|
+
if (family === 'anthropic') {
|
|
114
|
+
return Providers.ANTHROPIC;
|
|
115
|
+
}
|
|
116
|
+
if (family === 'bedrock') {
|
|
117
|
+
return Providers.BEDROCK;
|
|
118
|
+
}
|
|
119
|
+
return provider;
|
|
120
|
+
}
|
|
121
|
+
|
|
108
122
|
/**
|
|
109
123
|
* The registered handler that owns content-part dispatch, if any.
|
|
110
124
|
*
|
|
@@ -136,7 +150,7 @@ function removeOpenRouterFinalReasoningReplayContent({
|
|
|
136
150
|
}: {
|
|
137
151
|
current?: AIMessageChunk;
|
|
138
152
|
next: AIMessageChunk;
|
|
139
|
-
provider:
|
|
153
|
+
provider: t.ProviderName;
|
|
140
154
|
}): AIMessageChunk {
|
|
141
155
|
const content = getOpenRouterFinalReasoningContent({
|
|
142
156
|
current,
|
|
@@ -161,7 +175,7 @@ function getOpenRouterFinalReasoningContent({
|
|
|
161
175
|
}: {
|
|
162
176
|
current?: AIMessageChunk;
|
|
163
177
|
next: AIMessageChunk;
|
|
164
|
-
provider:
|
|
178
|
+
provider: t.ProviderName;
|
|
165
179
|
}): string | undefined {
|
|
166
180
|
if (
|
|
167
181
|
provider !== Providers.OPENROUTER ||
|
|
@@ -197,7 +211,7 @@ function getStreamHandlingChunk({
|
|
|
197
211
|
}: {
|
|
198
212
|
current?: AIMessageChunk;
|
|
199
213
|
next: AIMessageChunk;
|
|
200
|
-
provider:
|
|
214
|
+
provider: t.ProviderName;
|
|
201
215
|
}): AIMessageChunk | undefined {
|
|
202
216
|
const content = getOpenRouterFinalReasoningContent({
|
|
203
217
|
current,
|
|
@@ -473,7 +487,7 @@ function appendStreamChunk({
|
|
|
473
487
|
}: {
|
|
474
488
|
current?: AIMessageChunk;
|
|
475
489
|
next: AIMessageChunk;
|
|
476
|
-
provider:
|
|
490
|
+
provider: t.ProviderName;
|
|
477
491
|
}): AIMessageChunk {
|
|
478
492
|
if (current == null) {
|
|
479
493
|
return next;
|
|
@@ -514,11 +528,11 @@ type AttemptInvokeParams = AttemptInvokeCommonParams &
|
|
|
514
528
|
request?: never;
|
|
515
529
|
model: t.ChatModel;
|
|
516
530
|
messages: BaseMessage[];
|
|
517
|
-
provider:
|
|
531
|
+
provider: t.ProviderName;
|
|
518
532
|
}
|
|
519
533
|
);
|
|
520
534
|
|
|
521
|
-
function resolveAttemptProvider(params: AttemptInvokeParams):
|
|
535
|
+
function resolveAttemptProvider(params: AttemptInvokeParams): t.ProviderName {
|
|
522
536
|
if (params.request != null) {
|
|
523
537
|
return params.request.provider;
|
|
524
538
|
}
|
|
@@ -586,9 +600,7 @@ export async function attemptInvoke(
|
|
|
586
600
|
: undefined;
|
|
587
601
|
const generationKey =
|
|
588
602
|
leaseTarget != null
|
|
589
|
-
? resolveGenerationKey(
|
|
590
|
-
stampedConfig.metadata as Record<string, unknown>
|
|
591
|
-
)
|
|
603
|
+
? resolveGenerationKey(stampedConfig.metadata as Record<string, unknown>)
|
|
592
604
|
: undefined;
|
|
593
605
|
if (leaseTarget != null && generationKey != null) {
|
|
594
606
|
registerActiveStreamLimitGeneration(leaseTarget, generationKey);
|
|
@@ -809,8 +821,11 @@ async function attemptInvokeBody(
|
|
|
809
821
|
}
|
|
810
822
|
}
|
|
811
823
|
|
|
812
|
-
if (
|
|
813
|
-
finalChunk = modifyDeltaProperties(
|
|
824
|
+
if (providerUsesManualToolStream(provider)) {
|
|
825
|
+
finalChunk = modifyDeltaProperties(
|
|
826
|
+
getManualToolStreamNormalizationProvider(provider),
|
|
827
|
+
finalChunk
|
|
828
|
+
);
|
|
814
829
|
}
|
|
815
830
|
|
|
816
831
|
if (preempted && finalChunk != null) {
|
|
@@ -857,7 +872,7 @@ async function attemptInvokeBody(
|
|
|
857
872
|
* differ, which is the whole reason a fallback exists.
|
|
858
873
|
*/
|
|
859
874
|
export interface FallbackErrorContext {
|
|
860
|
-
provider:
|
|
875
|
+
provider: t.ProviderName;
|
|
861
876
|
clientOptions?: t.ClientOptions;
|
|
862
877
|
maxContextTokens?: number;
|
|
863
878
|
}
|
|
@@ -968,7 +983,7 @@ export async function tryFallbackProviders({
|
|
|
968
983
|
prepareProviderRequest?: (input: {
|
|
969
984
|
model: t.ChatModel;
|
|
970
985
|
messages: BaseMessage[];
|
|
971
|
-
provider:
|
|
986
|
+
provider: t.ProviderName;
|
|
972
987
|
clientOptions?: t.ClientOptions;
|
|
973
988
|
maxContextTokens?: number;
|
|
974
989
|
config?: RunnableConfig;
|
|
@@ -977,7 +992,7 @@ export async function tryFallbackProviders({
|
|
|
977
992
|
prepareProviderMessages?: (input: {
|
|
978
993
|
model: t.ChatModel;
|
|
979
994
|
messages: BaseMessage[];
|
|
980
|
-
provider:
|
|
995
|
+
provider: t.ProviderName;
|
|
981
996
|
clientOptions?: t.ClientOptions;
|
|
982
997
|
maxContextTokens?: number;
|
|
983
998
|
config?: RunnableConfig;
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from '@/messages/core';
|
|
15
15
|
import {
|
|
16
16
|
coalesceAdjacentUserTurns,
|
|
17
|
-
strictAlternationProviders,
|
|
18
17
|
appendPredecessorHandoffCue,
|
|
19
18
|
removePredecessorHandoffCue,
|
|
20
19
|
} from '@/messages';
|
|
@@ -23,8 +22,10 @@ import {
|
|
|
23
22
|
stripBedrockCacheControl,
|
|
24
23
|
} from '@/messages/cache';
|
|
25
24
|
import { annotateMessagesForLLM } from '@/tools/toolOutputReferences';
|
|
26
|
-
import {
|
|
25
|
+
import { providerRequiresStrictAlternation } from '@/llm/providers';
|
|
27
26
|
import { isAnthropicLike, isOpenAILike } from '@/utils/llm';
|
|
27
|
+
import { getProviderFamily } from '@/llm/providerRegistry';
|
|
28
|
+
import { Providers } from '@/common';
|
|
28
29
|
|
|
29
30
|
const preparedProviderRequestBrand = Symbol('PreparedProviderRequest');
|
|
30
31
|
|
|
@@ -43,7 +44,7 @@ export interface ProviderPayloadMeasurement {
|
|
|
43
44
|
export interface PreparedProviderRequest {
|
|
44
45
|
readonly model: t.ChatModel;
|
|
45
46
|
readonly modelId?: string;
|
|
46
|
-
readonly provider:
|
|
47
|
+
readonly provider: t.ProviderName;
|
|
47
48
|
readonly projectionMode: ProviderMessageProjectionMode;
|
|
48
49
|
readonly messages: BaseMessage[];
|
|
49
50
|
readonly measurement?: ProviderPayloadMeasurement;
|
|
@@ -63,7 +64,7 @@ export interface ProviderRequestContext {
|
|
|
63
64
|
export interface PrepareProviderRequestParams {
|
|
64
65
|
model: t.ChatModel;
|
|
65
66
|
messages: BaseMessage[];
|
|
66
|
-
provider:
|
|
67
|
+
provider: t.ProviderName;
|
|
67
68
|
context?: ProviderRequestContext;
|
|
68
69
|
config?: RunnableConfig;
|
|
69
70
|
maxToolResultChars?: number;
|
|
@@ -72,7 +73,7 @@ export interface PrepareProviderRequestParams {
|
|
|
72
73
|
|
|
73
74
|
export function usesNativeOpenAIResponses(
|
|
74
75
|
model: t.ChatModel,
|
|
75
|
-
provider:
|
|
76
|
+
provider: t.ProviderName,
|
|
76
77
|
callOptions?: unknown
|
|
77
78
|
): boolean {
|
|
78
79
|
if (!isOpenAILike(provider)) {
|
|
@@ -142,7 +143,7 @@ export function usesNativeOpenAIResponses(
|
|
|
142
143
|
|
|
143
144
|
function resolveProviderMessageProjectionMode(
|
|
144
145
|
model: t.ChatModel,
|
|
145
|
-
provider:
|
|
146
|
+
provider: t.ProviderName,
|
|
146
147
|
callOptions?: unknown
|
|
147
148
|
): ProviderMessageProjectionMode {
|
|
148
149
|
return usesNativeOpenAIResponses(model, provider, callOptions)
|
|
@@ -153,18 +154,16 @@ function resolveProviderMessageProjectionMode(
|
|
|
153
154
|
interface ProjectMessagesForProviderParams {
|
|
154
155
|
model: t.ChatModel;
|
|
155
156
|
messages: BaseMessage[];
|
|
156
|
-
provider:
|
|
157
|
+
provider: t.ProviderName;
|
|
157
158
|
maxToolResultChars?: number;
|
|
158
159
|
callOptions?: unknown;
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
function projectMessagesForProviderMode(
|
|
162
|
-
messages,
|
|
163
|
-
|
|
164
|
-
maxToolResultChars,
|
|
165
|
-
}: ProjectMessagesForProviderParams,
|
|
166
|
-
projectionMode: ProviderMessageProjectionMode
|
|
162
|
+
function projectMessagesForProviderMode(
|
|
163
|
+
{ messages, provider, maxToolResultChars }: ProjectMessagesForProviderParams,
|
|
164
|
+
projectionMode: ProviderMessageProjectionMode
|
|
167
165
|
): BaseMessage[] {
|
|
166
|
+
const providerFamily = getProviderFamily(provider);
|
|
168
167
|
const nativeOpenAIResponses = projectionMode === 'openai-responses';
|
|
169
168
|
const providerInputMessages = projectToolStreamContentForProvider(
|
|
170
169
|
messages,
|
|
@@ -197,7 +196,7 @@ projectionMode: ProviderMessageProjectionMode
|
|
|
197
196
|
)
|
|
198
197
|
);
|
|
199
198
|
}
|
|
200
|
-
if (provider === Providers.ANTHROPIC) {
|
|
199
|
+
if (provider === Providers.ANTHROPIC || providerFamily === 'anthropic') {
|
|
201
200
|
return projectComputerCallOutputsToText(
|
|
202
201
|
projectSingleTextToolOutputsToText(
|
|
203
202
|
stripBedrockCacheControl(providerInputMessages),
|
|
@@ -205,7 +204,7 @@ projectionMode: ProviderMessageProjectionMode
|
|
|
205
204
|
)
|
|
206
205
|
);
|
|
207
206
|
}
|
|
208
|
-
if (provider === Providers.BEDROCK) {
|
|
207
|
+
if (provider === Providers.BEDROCK || providerFamily === 'bedrock') {
|
|
209
208
|
return stripAnthropicCacheControl(
|
|
210
209
|
projectComputerCallOutputsToText(
|
|
211
210
|
projectCacheControlledToolOutputsToText(
|
|
@@ -310,7 +309,7 @@ export function prepareProviderRequest({
|
|
|
310
309
|
: (message): boolean => isRunProduced.call(context, message)
|
|
311
310
|
)
|
|
312
311
|
: removePredecessorHandoffCue(annotated);
|
|
313
|
-
const preparedMessages =
|
|
312
|
+
const preparedMessages = providerRequiresStrictAlternation(provider)
|
|
314
313
|
? coalesceAdjacentUserTurns(cued)
|
|
315
314
|
: cued;
|
|
316
315
|
|
|
@@ -332,7 +331,7 @@ export function prepareProviderRequest({
|
|
|
332
331
|
export function assertPreparedProviderRequestFor(
|
|
333
332
|
request: PreparedProviderRequest,
|
|
334
333
|
model: t.ChatModel,
|
|
335
|
-
provider:
|
|
334
|
+
provider: t.ProviderName,
|
|
336
335
|
config?: RunnableConfig
|
|
337
336
|
): void {
|
|
338
337
|
if (
|
|
@@ -344,7 +343,9 @@ export function assertPreparedProviderRequestFor(
|
|
|
344
343
|
throw new Error('Prepared provider request does not match serving model');
|
|
345
344
|
}
|
|
346
345
|
if (request.provider !== provider) {
|
|
347
|
-
throw new Error(
|
|
346
|
+
throw new Error(
|
|
347
|
+
'Prepared provider request does not match serving provider'
|
|
348
|
+
);
|
|
348
349
|
}
|
|
349
350
|
if (
|
|
350
351
|
request.projectionMode !==
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
2
|
+
import type {
|
|
3
|
+
ProviderFamily,
|
|
4
|
+
ProviderRegistrationOptions,
|
|
5
|
+
} from '../provider-registration';
|
|
6
|
+
import type { ProviderModelConstructor, ProviderName } from '@/types';
|
|
7
|
+
|
|
8
|
+
interface StoredProviderRegistration {
|
|
9
|
+
model: new (config: never) => BaseChatModel;
|
|
10
|
+
family: ProviderFamily;
|
|
11
|
+
manualToolStream: boolean;
|
|
12
|
+
strictAlternation: boolean;
|
|
13
|
+
owner: symbol;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ProviderRegistryGlobal {
|
|
17
|
+
[key: symbol]: Map<ProviderName, StoredProviderRegistration> | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PROVIDER_REGISTRY_KEY = Symbol.for(
|
|
21
|
+
'@librechat/agents:providerRegistry:v1'
|
|
22
|
+
);
|
|
23
|
+
const providerRegistryGlobal = globalThis as ProviderRegistryGlobal;
|
|
24
|
+
const registeredProviders =
|
|
25
|
+
providerRegistryGlobal[PROVIDER_REGISTRY_KEY] ??
|
|
26
|
+
new Map<ProviderName, StoredProviderRegistration>();
|
|
27
|
+
providerRegistryGlobal[PROVIDER_REGISTRY_KEY] = registeredProviders;
|
|
28
|
+
const builtInProviders = new Map<ProviderName, StoredProviderRegistration>();
|
|
29
|
+
|
|
30
|
+
function normalizeProvider(provider: ProviderName): ProviderName {
|
|
31
|
+
if (typeof provider !== 'string' || provider.trim() === '') {
|
|
32
|
+
throw new TypeError('LLM provider name must be a non-empty string');
|
|
33
|
+
}
|
|
34
|
+
if (provider !== provider.trim()) {
|
|
35
|
+
throw new TypeError(
|
|
36
|
+
'LLM provider name must not have surrounding whitespace'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return provider;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isConstructible<T extends abstract new (...args: never[]) => object>(
|
|
43
|
+
value: T
|
|
44
|
+
): boolean {
|
|
45
|
+
try {
|
|
46
|
+
Reflect.construct(String, [], value);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createRegistration<
|
|
54
|
+
TOptions extends object,
|
|
55
|
+
TModel extends BaseChatModel,
|
|
56
|
+
>(
|
|
57
|
+
provider: ProviderName,
|
|
58
|
+
options: ProviderRegistrationOptions<TOptions, TModel>
|
|
59
|
+
): StoredProviderRegistration {
|
|
60
|
+
if (typeof options.model !== 'function' || !isConstructible(options.model)) {
|
|
61
|
+
throw new TypeError(`LLM provider constructor is invalid: ${provider}`);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
model: options.model,
|
|
65
|
+
family: options.family ?? 'generic',
|
|
66
|
+
manualToolStream: options.manualToolStream ?? false,
|
|
67
|
+
strictAlternation: options.strictAlternation ?? false,
|
|
68
|
+
owner: Symbol(provider),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getRegistration(
|
|
73
|
+
provider: ProviderName
|
|
74
|
+
): StoredProviderRegistration | undefined {
|
|
75
|
+
return builtInProviders.get(provider) ?? registeredProviders.get(provider);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Registers one host provider until the returned disposer is called. */
|
|
79
|
+
export function registerProvider<
|
|
80
|
+
TOptions extends object,
|
|
81
|
+
TModel extends BaseChatModel,
|
|
82
|
+
>(options: ProviderRegistrationOptions<TOptions, TModel>): () => void {
|
|
83
|
+
const provider = normalizeProvider(options.provider);
|
|
84
|
+
if (getRegistration(provider) != null) {
|
|
85
|
+
throw new Error(`LLM provider already registered: ${provider}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const registration = createRegistration(provider, options);
|
|
89
|
+
registeredProviders.set(provider, registration);
|
|
90
|
+
|
|
91
|
+
return (): void => {
|
|
92
|
+
if (registeredProviders.get(provider)?.owner === registration.owner) {
|
|
93
|
+
registeredProviders.delete(provider);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Initializes one built-in for the current package module graph. */
|
|
99
|
+
export function registerBuiltInProvider<
|
|
100
|
+
TOptions extends object,
|
|
101
|
+
TModel extends BaseChatModel,
|
|
102
|
+
>(options: ProviderRegistrationOptions<TOptions, TModel>): void {
|
|
103
|
+
const provider = normalizeProvider(options.provider);
|
|
104
|
+
if (builtInProviders.has(provider)) {
|
|
105
|
+
throw new Error(`LLM provider already registered: ${provider}`);
|
|
106
|
+
}
|
|
107
|
+
builtInProviders.set(provider, createRegistration(provider, options));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getRegisteredChatModelClass<P extends ProviderName>(
|
|
111
|
+
provider: P
|
|
112
|
+
): ProviderModelConstructor<P> {
|
|
113
|
+
const registration = getRegistration(provider);
|
|
114
|
+
if (!registration) {
|
|
115
|
+
throw new Error(`Unsupported LLM provider: ${provider}`);
|
|
116
|
+
}
|
|
117
|
+
return registration.model as ProviderModelConstructor<P>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getProviderFamily(
|
|
121
|
+
provider: ProviderName
|
|
122
|
+
): ProviderFamily | undefined {
|
|
123
|
+
return getRegistration(provider)?.family;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function providerUsesManualToolStream(provider: ProviderName): boolean {
|
|
127
|
+
return getRegistration(provider)?.manualToolStream ?? false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function providerRequiresStrictAlternation(
|
|
131
|
+
provider: ProviderName
|
|
132
|
+
): boolean {
|
|
133
|
+
return getRegistration(provider)?.strictAlternation ?? false;
|
|
134
|
+
}
|
package/src/llm/providers.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
// src/llm/providers.ts
|
|
2
|
-
import { CustomChatMistralAI } from '@/llm/mistral';
|
|
3
1
|
import type {
|
|
4
2
|
ChatModelConstructorMap,
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
ProviderModelConstructor,
|
|
4
|
+
ProviderName,
|
|
7
5
|
} from '@/types';
|
|
6
|
+
import type { ProviderFamily } from '../provider-registration';
|
|
8
7
|
import {
|
|
9
8
|
AzureChatOpenAI,
|
|
10
9
|
ChatDeepSeek,
|
|
@@ -12,41 +11,85 @@ import {
|
|
|
12
11
|
ChatOpenAI,
|
|
13
12
|
ChatXAI,
|
|
14
13
|
} from '@/llm/openai';
|
|
14
|
+
import {
|
|
15
|
+
getRegisteredChatModelClass,
|
|
16
|
+
registerBuiltInProvider,
|
|
17
|
+
} from '@/llm/providerRegistry';
|
|
15
18
|
import { CustomChatGoogleGenerativeAI } from '@/llm/google';
|
|
16
19
|
import { CustomChatBedrockConverse } from '@/llm/bedrock';
|
|
20
|
+
import { CustomChatMistralAI } from '@/llm/mistral';
|
|
17
21
|
import { CustomAnthropic } from '@/llm/anthropic';
|
|
18
22
|
import { ChatOpenRouter } from '@/llm/openrouter';
|
|
19
23
|
import { ChatVertexAI } from '@/llm/vertexai';
|
|
20
24
|
import { Providers } from '@/common';
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
[Providers.VERTEXAI]: ChatVertexAI,
|
|
27
|
-
[Providers.DEEPSEEK]: ChatDeepSeek,
|
|
28
|
-
[Providers.MISTRALAI]: CustomChatMistralAI,
|
|
29
|
-
[Providers.MISTRAL]: CustomChatMistralAI,
|
|
30
|
-
[Providers.ANTHROPIC]: CustomAnthropic,
|
|
31
|
-
[Providers.OPENROUTER]: ChatOpenRouter,
|
|
32
|
-
[Providers.BEDROCK]: CustomChatBedrockConverse,
|
|
33
|
-
// [Providers.ANTHROPIC]: ChatAnthropic,
|
|
34
|
-
[Providers.GOOGLE]: CustomChatGoogleGenerativeAI,
|
|
35
|
-
[Providers.MOONSHOT]: ChatMoonshot,
|
|
26
|
+
type BuiltInProviderTraits = {
|
|
27
|
+
family: ProviderFamily;
|
|
28
|
+
manualToolStream?: boolean;
|
|
29
|
+
strictAlternation?: boolean;
|
|
36
30
|
};
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
function initializeBuiltInProvider<P extends Providers>(
|
|
33
|
+
provider: P,
|
|
34
|
+
model: ChatModelConstructorMap[P],
|
|
35
|
+
traits: BuiltInProviderTraits
|
|
36
|
+
): void {
|
|
37
|
+
registerBuiltInProvider({
|
|
38
|
+
provider,
|
|
39
|
+
model,
|
|
40
|
+
...traits,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
initializeBuiltInProvider(Providers.XAI, ChatXAI, { family: 'openai' });
|
|
45
|
+
initializeBuiltInProvider(Providers.OPENAI, ChatOpenAI, { family: 'openai' });
|
|
46
|
+
initializeBuiltInProvider(Providers.AZURE, AzureChatOpenAI, {
|
|
47
|
+
family: 'openai',
|
|
48
|
+
});
|
|
49
|
+
initializeBuiltInProvider(Providers.VERTEXAI, ChatVertexAI, {
|
|
50
|
+
family: 'google',
|
|
51
|
+
});
|
|
52
|
+
initializeBuiltInProvider(Providers.DEEPSEEK, ChatDeepSeek, {
|
|
53
|
+
family: 'openai',
|
|
54
|
+
});
|
|
55
|
+
initializeBuiltInProvider(Providers.MISTRALAI, CustomChatMistralAI, {
|
|
56
|
+
family: 'mistral',
|
|
57
|
+
strictAlternation: true,
|
|
58
|
+
});
|
|
59
|
+
initializeBuiltInProvider(Providers.MISTRAL, CustomChatMistralAI, {
|
|
60
|
+
family: 'mistral',
|
|
61
|
+
strictAlternation: true,
|
|
62
|
+
});
|
|
63
|
+
initializeBuiltInProvider(Providers.ANTHROPIC, CustomAnthropic, {
|
|
64
|
+
family: 'anthropic',
|
|
65
|
+
manualToolStream: true,
|
|
66
|
+
});
|
|
67
|
+
initializeBuiltInProvider(Providers.OPENROUTER, ChatOpenRouter, {
|
|
68
|
+
family: 'openai',
|
|
69
|
+
});
|
|
70
|
+
initializeBuiltInProvider(Providers.BEDROCK, CustomChatBedrockConverse, {
|
|
71
|
+
family: 'bedrock',
|
|
72
|
+
manualToolStream: true,
|
|
73
|
+
strictAlternation: true,
|
|
74
|
+
});
|
|
75
|
+
initializeBuiltInProvider(Providers.GOOGLE, CustomChatGoogleGenerativeAI, {
|
|
76
|
+
family: 'google',
|
|
77
|
+
});
|
|
78
|
+
initializeBuiltInProvider(Providers.MOONSHOT, ChatMoonshot, {
|
|
79
|
+
family: 'generic',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const getChatModelClass = <P extends ProviderName>(
|
|
44
83
|
provider: P
|
|
45
|
-
):
|
|
46
|
-
const ChatModelClass = llmProviders[provider];
|
|
47
|
-
if (!ChatModelClass) {
|
|
48
|
-
throw new Error(`Unsupported LLM provider: ${provider}`);
|
|
49
|
-
}
|
|
84
|
+
): ProviderModelConstructor<P> => getRegisteredChatModelClass(provider);
|
|
50
85
|
|
|
51
|
-
|
|
52
|
-
|
|
86
|
+
export {
|
|
87
|
+
getProviderFamily,
|
|
88
|
+
providerRequiresStrictAlternation,
|
|
89
|
+
providerUsesManualToolStream,
|
|
90
|
+
registerProvider,
|
|
91
|
+
} from '@/llm/providerRegistry';
|
|
92
|
+
export type {
|
|
93
|
+
ProviderFamily,
|
|
94
|
+
ProviderRegistrationOptions,
|
|
95
|
+
} from '../provider-registration';
|
package/src/llm/request.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as t from '@/types';
|
|
2
|
+
import { getProviderFamily } from '@/llm/providerRegistry';
|
|
2
3
|
import { Providers } from '@/common';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -7,20 +8,21 @@ import { Providers } from '@/common';
|
|
|
7
8
|
* and OpenAI-compat (modelKwargs.thinking).
|
|
8
9
|
*/
|
|
9
10
|
export function isThinkingEnabled(
|
|
10
|
-
provider:
|
|
11
|
+
provider: t.ProviderName,
|
|
11
12
|
clientOptions?: t.ClientOptions
|
|
12
13
|
): boolean {
|
|
13
14
|
if (!clientOptions) return false;
|
|
15
|
+
const family = getProviderFamily(provider);
|
|
14
16
|
|
|
15
17
|
if (
|
|
16
|
-
provider === Providers.ANTHROPIC &&
|
|
18
|
+
(provider === Providers.ANTHROPIC || family === 'anthropic') &&
|
|
17
19
|
(clientOptions as t.AnthropicClientOptions).thinking != null
|
|
18
20
|
) {
|
|
19
21
|
return true;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
if (
|
|
23
|
-
provider === Providers.BEDROCK &&
|
|
25
|
+
(provider === Providers.BEDROCK || family === 'bedrock') &&
|
|
24
26
|
(clientOptions as t.BedrockAnthropicInput).additionalModelRequestFields?.[
|
|
25
27
|
'thinking'
|
|
26
28
|
] != null
|
|
@@ -29,7 +31,7 @@ export function isThinkingEnabled(
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
if (
|
|
32
|
-
provider === Providers.OPENAI &&
|
|
34
|
+
(provider === Providers.OPENAI || family === 'openai') &&
|
|
33
35
|
(
|
|
34
36
|
(clientOptions as t.OpenAIClientOptions).modelKwargs
|
|
35
37
|
?.thinking as t.AnthropicClientOptions['thinking']
|
|
@@ -47,9 +49,11 @@ export function isThinkingEnabled(
|
|
|
47
49
|
* use `maxTokens`.
|
|
48
50
|
*/
|
|
49
51
|
export function getMaxOutputTokensKey(
|
|
50
|
-
provider:
|
|
52
|
+
provider: t.ProviderName
|
|
51
53
|
): 'maxOutputTokens' | 'maxTokens' {
|
|
52
|
-
return provider === Providers.GOOGLE ||
|
|
54
|
+
return provider === Providers.GOOGLE ||
|
|
55
|
+
provider === Providers.VERTEXAI ||
|
|
56
|
+
getProviderFamily(provider) === 'google'
|
|
53
57
|
? 'maxOutputTokens'
|
|
54
58
|
: 'maxTokens';
|
|
55
59
|
}
|
package/src/llm/truncation.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { AIMessageChunk, BaseMessage } from '@langchain/core/messages';
|
|
2
|
+
import type { ProviderName } from '@/types';
|
|
3
|
+
import { getProviderFamily } from '@/llm/providerRegistry';
|
|
2
4
|
import { Providers } from '@/common';
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -7,7 +9,7 @@ import { Providers } from '@/common';
|
|
|
7
9
|
* `functionCall` whole and seal it on arrival, so a `MAX_TOKENS` finish does
|
|
8
10
|
* NOT imply the arguments were cut off — the truncation guard must skip them.
|
|
9
11
|
*/
|
|
10
|
-
const ATOMIC_TOOL_CALL_ARG_PROVIDERS = new Set<
|
|
12
|
+
const ATOMIC_TOOL_CALL_ARG_PROVIDERS = new Set<ProviderName>([
|
|
11
13
|
Providers.GOOGLE,
|
|
12
14
|
Providers.VERTEXAI,
|
|
13
15
|
]);
|
|
@@ -162,12 +164,16 @@ function collectToolCallNames(message: AIMessageChunk | BaseMessage): string[] {
|
|
|
162
164
|
*/
|
|
163
165
|
export function assertNotTruncatedToolCall(
|
|
164
166
|
message: AIMessageChunk | BaseMessage | undefined | null,
|
|
165
|
-
provider?:
|
|
167
|
+
provider?: ProviderName
|
|
166
168
|
): void {
|
|
167
169
|
if (message == null) {
|
|
168
170
|
return;
|
|
169
171
|
}
|
|
170
|
-
if (
|
|
172
|
+
if (
|
|
173
|
+
provider != null &&
|
|
174
|
+
(ATOMIC_TOOL_CALL_ARG_PROVIDERS.has(provider) ||
|
|
175
|
+
getProviderFamily(provider) === 'google')
|
|
176
|
+
) {
|
|
171
177
|
return;
|
|
172
178
|
}
|
|
173
179
|
const stopReason = getTruncationStopReason(message);
|
package/src/messages/core.ts
CHANGED
|
@@ -90,7 +90,7 @@ const modifyContent = ({
|
|
|
90
90
|
messageType,
|
|
91
91
|
content,
|
|
92
92
|
}: {
|
|
93
|
-
provider:
|
|
93
|
+
provider: t.ProviderName;
|
|
94
94
|
messageType: string;
|
|
95
95
|
content: t.ExtendedMessageContent[];
|
|
96
96
|
}): (t.ExtendedMessageContent | null)[] => {
|
|
@@ -243,7 +243,7 @@ function hasReasoningContent(content: BaseMessage['content']): boolean {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
export function modifyDeltaProperties(
|
|
246
|
-
provider:
|
|
246
|
+
provider: t.ProviderName,
|
|
247
247
|
obj?: AIMessageChunk
|
|
248
248
|
): AIMessageChunk | undefined {
|
|
249
249
|
if (!obj || typeof obj !== 'object') return obj;
|