@librechat/agents 2.4.38 → 2.4.41

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 (30) hide show
  1. package/dist/cjs/graphs/Graph.cjs +2 -1
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  4. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +13 -3
  5. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  6. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +22 -2
  7. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  8. package/dist/cjs/llm/openai/index.cjs +116 -2
  9. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  10. package/dist/esm/graphs/Graph.mjs +2 -1
  11. package/dist/esm/graphs/Graph.mjs.map +1 -1
  12. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  13. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +13 -3
  14. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  15. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +22 -2
  16. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  17. package/dist/esm/llm/openai/index.mjs +115 -4
  18. package/dist/esm/llm/openai/index.mjs.map +1 -1
  19. package/dist/types/llm/anthropic/types.d.ts +7 -3
  20. package/dist/types/llm/anthropic/utils/message_inputs.d.ts +1 -1
  21. package/dist/types/llm/anthropic/utils/output_parsers.d.ts +2 -2
  22. package/dist/types/llm/openai/index.d.ts +38 -3
  23. package/package.json +13 -13
  24. package/src/graphs/Graph.ts +3 -4
  25. package/src/llm/anthropic/types.ts +23 -3
  26. package/src/llm/anthropic/utils/message_inputs.ts +21 -5
  27. package/src/llm/anthropic/utils/message_outputs.ts +21 -2
  28. package/src/llm/anthropic/utils/output_parsers.ts +23 -4
  29. package/src/llm/openai/index.ts +189 -14
  30. package/src/scripts/simple.ts +1 -1
@@ -2,8 +2,40 @@ import { AzureOpenAI as AzureOpenAIClient } from 'openai';
2
2
  import { ChatXAI as OriginalChatXAI } from '@langchain/xai';
3
3
  import { ChatDeepSeek as OriginalChatDeepSeek } from '@langchain/deepseek';
4
4
  import { OpenAIClient, ChatOpenAI as OriginalChatOpenAI, AzureChatOpenAI as OriginalAzureChatOpenAI } from '@langchain/openai';
5
- import type { OpenAICoreRequestOptions } from 'node_modules/@langchain/deepseek/node_modules/@langchain/openai';
5
+ import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
6
+ import type { AIMessageChunk } from '@langchain/core/messages';
7
+ import type { Runnable } from '@langchain/core/runnables';
6
8
  import type * as t from '@langchain/openai';
9
+ import { BaseLanguageModelInput } from '@langchain/core/language_models/base';
10
+ type ResponsesCreateParams = Parameters<OpenAIClient.Responses['create']>[0];
11
+ type ResponsesTool = Exclude<ResponsesCreateParams['tools'], undefined>[number];
12
+ type ChatOpenAIToolType = BindToolsInput | OpenAIClient.ChatCompletionTool | ResponsesTool;
13
+ type HeaderValue = string | undefined | null;
14
+ export type HeadersLike = Headers | readonly HeaderValue[][] | Record<string, HeaderValue | readonly HeaderValue[]> | undefined | null | {
15
+ values: Headers;
16
+ [key: string]: unknown;
17
+ };
18
+ export declare function isHeaders(headers: unknown): headers is Headers;
19
+ export declare function normalizeHeaders(headers: HeadersLike): Record<string, HeaderValue | readonly HeaderValue[]>;
20
+ type OpenAICoreRequestOptions = OpenAIClient.RequestOptions;
21
+ /**
22
+ * Formats a tool in either OpenAI format, or LangChain structured tool format
23
+ * into an OpenAI tool format. If the tool is already in OpenAI format, return without
24
+ * any changes. If it is in LangChain structured tool format, convert it to OpenAI tool format
25
+ * using OpenAI's `zodFunction` util, falling back to `convertToOpenAIFunction` if the parameters
26
+ * returned from the `zodFunction` util are not defined.
27
+ *
28
+ * @param {BindToolsInput} tool The tool to convert to an OpenAI tool.
29
+ * @param {Object} [fields] Additional fields to add to the OpenAI tool.
30
+ * @returns {ToolDefinition} The inputted tool in OpenAI tool format.
31
+ */
32
+ export declare function _convertToOpenAITool(tool: BindToolsInput, fields?: {
33
+ /**
34
+ * If `true`, model output is guaranteed to exactly match the JSON Schema
35
+ * provided in the function definition.
36
+ */
37
+ strict?: boolean;
38
+ }): OpenAIClient.ChatCompletionTool;
7
39
  export declare class CustomOpenAIClient extends OpenAIClient {
8
40
  abortHandler?: () => void;
9
41
  fetchWithTimeout(url: RequestInfo, init: RequestInit | undefined, ms: number, controller: AbortController): Promise<Response>;
@@ -14,11 +46,13 @@ export declare class CustomAzureOpenAIClient extends AzureOpenAIClient {
14
46
  }
15
47
  export declare class ChatOpenAI extends OriginalChatOpenAI<t.ChatOpenAICallOptions> {
16
48
  get exposedClient(): CustomOpenAIClient;
17
- protected _getClientOptions(options?: t.OpenAICoreRequestOptions): t.OpenAICoreRequestOptions;
49
+ bindTools(tools: ChatOpenAIToolType[], kwargs?: Partial<t.ChatOpenAICallOptions>): Runnable<BaseLanguageModelInput, AIMessageChunk, t.ChatOpenAICallOptions>;
50
+ protected _getClientOptions(options?: OpenAICoreRequestOptions): OpenAICoreRequestOptions;
18
51
  }
19
52
  export declare class AzureChatOpenAI extends OriginalAzureChatOpenAI {
53
+ bindTools(tools: ChatOpenAIToolType[], kwargs?: Partial<t.ChatOpenAICallOptions>): Runnable<BaseLanguageModelInput, AIMessageChunk, t.ChatOpenAICallOptions>;
20
54
  get exposedClient(): CustomOpenAIClient;
21
- protected _getClientOptions(options: t.OpenAICoreRequestOptions | undefined): t.OpenAICoreRequestOptions;
55
+ protected _getClientOptions(options: OpenAICoreRequestOptions | undefined): OpenAICoreRequestOptions;
22
56
  }
23
57
  export declare class ChatDeepSeek extends OriginalChatDeepSeek {
24
58
  get exposedClient(): CustomOpenAIClient;
@@ -28,3 +62,4 @@ export declare class ChatXAI extends OriginalChatXAI {
28
62
  get exposedClient(): CustomOpenAIClient;
29
63
  protected _getClientOptions(options?: OpenAICoreRequestOptions): OpenAICoreRequestOptions;
30
64
  }
65
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "2.4.38",
3
+ "version": "2.4.41",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -72,18 +72,18 @@
72
72
  "format": "prettier --write ."
73
73
  },
74
74
  "dependencies": {
75
- "@langchain/anthropic": "^0.3.21",
76
- "@langchain/aws": "^0.1.10",
77
- "@langchain/community": "^0.3.44",
78
- "@langchain/core": "^0.3.57",
79
- "@langchain/deepseek": "^0.0.1",
80
- "@langchain/google-genai": "^0.2.9",
81
- "@langchain/google-vertexai": "^0.2.9",
82
- "@langchain/langgraph": "^0.2.73",
83
- "@langchain/mistralai": "^0.2.0",
84
- "@langchain/ollama": "^0.2.0",
85
- "@langchain/openai": "^0.5.11",
86
- "@langchain/xai": "^0.0.2",
75
+ "@langchain/anthropic": "^0.3.23",
76
+ "@langchain/aws": "^0.1.11",
77
+ "@langchain/community": "^0.3.47",
78
+ "@langchain/core": "^0.3.60",
79
+ "@langchain/deepseek": "^0.0.2",
80
+ "@langchain/google-genai": "^0.2.13",
81
+ "@langchain/google-vertexai": "^0.2.13",
82
+ "@langchain/langgraph": "^0.3.4",
83
+ "@langchain/mistralai": "^0.2.1",
84
+ "@langchain/ollama": "^0.2.3",
85
+ "@langchain/openai": "^0.5.14",
86
+ "@langchain/xai": "^0.0.3",
87
87
  "cheerio": "^1.0.0",
88
88
  "dotenv": "^16.4.7",
89
89
  "https-proxy-agent": "^7.0.6",
@@ -184,10 +184,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, GraphNode> {
184
184
  finalInstructions &&
185
185
  provider === Providers.ANTHROPIC &&
186
186
  ((
187
- clientOptions as t.AnthropicClientOptions
188
- ).clientOptions?.defaultHeaders?.['anthropic-beta']?.includes(
189
- 'prompt-caching'
190
- ) ??
187
+ (clientOptions as t.AnthropicClientOptions).clientOptions
188
+ ?.defaultHeaders as Record<string, string> | undefined
189
+ )?.['anthropic-beta']?.includes('prompt-caching') ??
191
190
  false)
192
191
  ) {
193
192
  finalInstructions = {
@@ -1,6 +1,10 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
2
  import { BindToolsInput } from '@langchain/core/language_models/chat_models';
3
3
 
4
+ export type AnthropicStreamUsage = Anthropic.Usage;
5
+ export type AnthropicMessageDeltaEvent = Anthropic.MessageDeltaEvent;
6
+ export type AnthropicMessageStartEvent = Anthropic.MessageStartEvent;
7
+
4
8
  export type AnthropicToolResponse = {
5
9
  type: 'tool_use';
6
10
  id: string;
@@ -9,10 +13,7 @@ export type AnthropicToolResponse = {
9
13
  input: Record<string, any>;
10
14
  };
11
15
 
12
- export type AnthropicStreamUsage = Anthropic.Usage;
13
16
  export type AnthropicMessageParam = Anthropic.MessageParam;
14
- export type AnthropicMessageDeltaEvent = Anthropic.MessageDeltaEvent;
15
- export type AnthropicMessageStartEvent = Anthropic.MessageStartEvent;
16
17
  export type AnthropicMessageResponse =
17
18
  | Anthropic.ContentBlock
18
19
  | AnthropicToolResponse;
@@ -42,6 +43,25 @@ export type AnthropicDocumentBlockParam = Anthropic.Messages.DocumentBlockParam;
42
43
  export type AnthropicThinkingBlockParam = Anthropic.Messages.ThinkingBlockParam;
43
44
  export type AnthropicRedactedThinkingBlockParam =
44
45
  Anthropic.Messages.RedactedThinkingBlockParam;
46
+ export type AnthropicServerToolUseBlockParam =
47
+ Anthropic.Messages.ServerToolUseBlockParam;
48
+ export type AnthropicWebSearchToolResultBlockParam =
49
+ Anthropic.Messages.WebSearchToolResultBlockParam;
50
+ export type AnthropicWebSearchResultBlockParam =
51
+ Anthropic.Messages.WebSearchResultBlockParam;
52
+
53
+ // Union of all possible content block types including server tool use
54
+ export type AnthropicContentBlock =
55
+ | AnthropicTextBlockParam
56
+ | AnthropicImageBlockParam
57
+ | AnthropicToolUseBlockParam
58
+ | AnthropicToolResultBlockParam
59
+ | AnthropicDocumentBlockParam
60
+ | AnthropicThinkingBlockParam
61
+ | AnthropicRedactedThinkingBlockParam
62
+ | AnthropicServerToolUseBlockParam
63
+ | AnthropicWebSearchToolResultBlockParam
64
+ | AnthropicWebSearchResultBlockParam;
45
65
 
46
66
  export function isAnthropicImageBlockParam(
47
67
  block: unknown
@@ -21,7 +21,7 @@ import {
21
21
  parseBase64DataUrl,
22
22
  } from '@langchain/core/messages';
23
23
  import { ToolCall } from '@langchain/core/messages/tool';
24
- import type {
24
+ import {
25
25
  AnthropicImageBlockParam,
26
26
  AnthropicMessageCreateParams,
27
27
  AnthropicTextBlockParam,
@@ -31,8 +31,10 @@ import type {
31
31
  AnthropicDocumentBlockParam,
32
32
  AnthropicThinkingBlockParam,
33
33
  AnthropicRedactedThinkingBlockParam,
34
+ AnthropicServerToolUseBlockParam,
35
+ AnthropicWebSearchToolResultBlockParam,
36
+ isAnthropicImageBlockParam,
34
37
  } from '@/llm/anthropic/types';
35
- import { isAnthropicImageBlockParam } from '@/llm/anthropic/types';
36
38
 
37
39
  function _formatImage(imageUrl: string) {
38
40
  const parsed = parseBase64DataUrl({ dataUrl: imageUrl });
@@ -119,7 +121,6 @@ function _ensureMessageContents(
119
121
  {
120
122
  type: 'tool_result',
121
123
  // rare case: message.content could be undefined
122
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
123
124
  ...(message.content != null
124
125
  ? { content: _formatContent(message.content) }
125
126
  : {}),
@@ -347,7 +348,14 @@ const standardContentBlockConverter: StandardContentBlockConverter<{
347
348
  };
348
349
 
349
350
  function _formatContent(content: MessageContent) {
350
- const toolTypes = ['tool_use', 'tool_result', 'input_json_delta'];
351
+ const toolTypes = [
352
+ 'tool_use',
353
+ 'tool_result',
354
+ 'input_json_delta',
355
+ 'server_tool_use',
356
+ 'web_search_tool_result',
357
+ 'web_search_result',
358
+ ];
351
359
  const textTypes = ['text', 'text_delta'];
352
360
 
353
361
  if (typeof content === 'string') {
@@ -408,6 +416,9 @@ function _formatContent(content: MessageContent) {
408
416
  type: 'text' as const, // Explicitly setting the type as "text"
409
417
  text: contentPart.text,
410
418
  ...(cacheControl ? { cache_control: cacheControl } : {}),
419
+ ...('citations' in contentPart && contentPart.citations
420
+ ? { citations: contentPart.citations }
421
+ : {}),
411
422
  };
412
423
  } else if (toolTypes.find((t) => t === contentPart.type)) {
413
424
  const contentPartCopy = { ...contentPart };
@@ -502,7 +513,8 @@ export function _convertMessagesToAnthropicPayload(
502
513
  content.find(
503
514
  (contentPart) =>
504
515
  (contentPart.type === 'tool_use' ||
505
- contentPart.type === 'input_json_delta') &&
516
+ contentPart.type === 'input_json_delta' ||
517
+ contentPart.type === 'server_tool_use') &&
506
518
  contentPart.id === toolCall.id
507
519
  )
508
520
  );
@@ -548,6 +560,8 @@ function mergeMessages(messages: AnthropicMessageCreateParams['messages']) {
548
560
  | AnthropicDocumentBlockParam
549
561
  | AnthropicThinkingBlockParam
550
562
  | AnthropicRedactedThinkingBlockParam
563
+ | AnthropicServerToolUseBlockParam
564
+ | AnthropicWebSearchToolResultBlockParam
551
565
  >
552
566
  ): Array<
553
567
  | AnthropicTextBlockParam
@@ -557,6 +571,8 @@ function mergeMessages(messages: AnthropicMessageCreateParams['messages']) {
557
571
  | AnthropicDocumentBlockParam
558
572
  | AnthropicThinkingBlockParam
559
573
  | AnthropicRedactedThinkingBlockParam
574
+ | AnthropicServerToolUseBlockParam
575
+ | AnthropicWebSearchToolResultBlockParam
560
576
  > => {
561
577
  if (typeof content === 'string') {
562
578
  return [
@@ -77,7 +77,12 @@ export function _makeMessageChunkFromAnthropicEvent(
77
77
  };
78
78
  } else if (
79
79
  data.type === 'content_block_start' &&
80
- ['tool_use', 'document'].includes(data.content_block.type)
80
+ [
81
+ 'tool_use',
82
+ 'document',
83
+ 'server_tool_use',
84
+ 'web_search_tool_result',
85
+ ].includes(data.content_block.type)
81
86
  ) {
82
87
  const contentBlock = data.content_block;
83
88
  let toolCallChunks: ToolCallChunk[];
@@ -90,6 +95,16 @@ export function _makeMessageChunkFromAnthropicEvent(
90
95
  args: '',
91
96
  },
92
97
  ];
98
+ } else if (contentBlock.type === 'server_tool_use') {
99
+ // Handle anthropic built-in server tool use (like web search)
100
+ toolCallChunks = [
101
+ {
102
+ id: contentBlock.id,
103
+ index: data.index,
104
+ name: contentBlock.name,
105
+ args: '',
106
+ },
107
+ ];
93
108
  } else {
94
109
  toolCallChunks = [];
95
110
  }
@@ -101,7 +116,11 @@ export function _makeMessageChunkFromAnthropicEvent(
101
116
  {
102
117
  index: data.index,
103
118
  ...data.content_block,
104
- input: '',
119
+ input:
120
+ contentBlock.type === 'server_tool_use' ||
121
+ contentBlock.type === 'tool_use'
122
+ ? ''
123
+ : undefined,
105
124
  },
106
125
  ],
107
126
  additional_kwargs: {},
@@ -1,11 +1,14 @@
1
1
  /* eslint-disable @typescript-eslint/explicit-function-return-type */
2
2
  /* eslint-disable @typescript-eslint/no-empty-object-type */
3
- import { z } from 'zod';
4
3
  import {
5
4
  BaseLLMOutputParser,
6
5
  OutputParserException,
7
6
  } from '@langchain/core/output_parsers';
8
7
  import { JsonOutputKeyToolsParserParams } from '@langchain/core/output_parsers/openai_tools';
8
+ import {
9
+ interopSafeParseAsync,
10
+ InteropZodType,
11
+ } from '@langchain/core/utils/types';
9
12
  import { ChatGeneration } from '@langchain/core/outputs';
10
13
  import { ToolCall } from '@langchain/core/messages/tool';
11
14
 
@@ -31,7 +34,7 @@ export class AnthropicToolsOutputParser<
31
34
  /** Whether to return only the first tool call. */
32
35
  returnSingle = false;
33
36
 
34
- zodSchema?: z.ZodType<T>;
37
+ zodSchema?: InteropZodType<T>;
35
38
 
36
39
  constructor(params: AnthropicToolsOutputParserParams<T>) {
37
40
  super(params);
@@ -62,7 +65,10 @@ export class AnthropicToolsOutputParser<
62
65
  if (this.zodSchema === undefined) {
63
66
  return parsedResult as T;
64
67
  }
65
- const zodParsedResult = await this.zodSchema.safeParseAsync(parsedResult);
68
+ const zodParsedResult = await interopSafeParseAsync(
69
+ this.zodSchema,
70
+ parsedResult
71
+ );
66
72
  if (zodParsedResult.success) {
67
73
  return zodParsedResult.data;
68
74
  } else {
@@ -71,7 +77,7 @@ export class AnthropicToolsOutputParser<
71
77
  result,
72
78
  null,
73
79
  2
74
- )}". Error: ${JSON.stringify(zodParsedResult.error.errors)}`,
80
+ )}". Error: ${JSON.stringify(zodParsedResult.error.issues)}`,
75
81
  JSON.stringify(parsedResult, null, 2)
76
82
  );
77
83
  }
@@ -100,6 +106,7 @@ export class AnthropicToolsOutputParser<
100
106
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
107
  export function extractToolCalls(content: Record<string, any>[]) {
102
108
  const toolCalls: ToolCall[] = [];
109
+
103
110
  for (const block of content) {
104
111
  if (block.type === 'tool_use') {
105
112
  toolCalls.push({
@@ -108,7 +115,19 @@ export function extractToolCalls(content: Record<string, any>[]) {
108
115
  id: block.id,
109
116
  type: 'tool_call',
110
117
  });
118
+ } else if (
119
+ block.type === 'server_tool_use' &&
120
+ block.name === 'web_search'
121
+ ) {
122
+ // Handle Anthropic built-in web search tool
123
+ toolCalls.push({
124
+ name: block.name,
125
+ args: block.input,
126
+ id: block.id,
127
+ type: 'tool_call',
128
+ });
111
129
  }
112
130
  }
131
+
113
132
  return toolCalls;
114
133
  }
@@ -4,17 +4,150 @@ import { ChatDeepSeek as OriginalChatDeepSeek } from '@langchain/deepseek';
4
4
  import {
5
5
  getEndpoint,
6
6
  OpenAIClient,
7
+ formatToOpenAITool,
7
8
  ChatOpenAI as OriginalChatOpenAI,
8
9
  AzureChatOpenAI as OriginalAzureChatOpenAI,
9
10
  } from '@langchain/openai';
10
- import type { OpenAICoreRequestOptions } from 'node_modules/@langchain/deepseek/node_modules/@langchain/openai';
11
+ import { isLangChainTool } from '@langchain/core/utils/function_calling';
12
+ import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
13
+ import type { OpenAIEndpointConfig } from '@langchain/openai/dist/utils/azure';
14
+ import type { AIMessageChunk } from '@langchain/core/messages';
15
+ import type { Runnable } from '@langchain/core/runnables';
11
16
  import type * as t from '@langchain/openai';
17
+ import {
18
+ isOpenAITool,
19
+ ToolDefinition,
20
+ BaseLanguageModelInput,
21
+ } from '@langchain/core/language_models/base';
22
+
23
+ type ResponsesCreateParams = Parameters<OpenAIClient.Responses['create']>[0];
24
+ type ResponsesTool = Exclude<ResponsesCreateParams['tools'], undefined>[number];
25
+
26
+ type ChatOpenAIToolType =
27
+ | BindToolsInput
28
+ | OpenAIClient.ChatCompletionTool
29
+ | ResponsesTool;
30
+
31
+ type HeaderValue = string | undefined | null;
32
+ export type HeadersLike =
33
+ | Headers
34
+ | readonly HeaderValue[][]
35
+ | Record<string, HeaderValue | readonly HeaderValue[]>
36
+ | undefined
37
+ | null
38
+ // NullableHeaders
39
+ | { values: Headers; [key: string]: unknown };
40
+
41
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
42
+ const iife = <T>(fn: () => T) => fn();
43
+
44
+ export function isHeaders(headers: unknown): headers is Headers {
45
+ return (
46
+ typeof Headers !== 'undefined' &&
47
+ headers !== null &&
48
+ typeof headers === 'object' &&
49
+ Object.prototype.toString.call(headers) === '[object Headers]'
50
+ );
51
+ }
52
+
53
+ export function normalizeHeaders(
54
+ headers: HeadersLike
55
+ ): Record<string, HeaderValue | readonly HeaderValue[]> {
56
+ const output = iife(() => {
57
+ // If headers is a Headers instance
58
+ if (isHeaders(headers)) {
59
+ return headers;
60
+ }
61
+ // If headers is an array of [key, value] pairs
62
+ else if (Array.isArray(headers)) {
63
+ return new Headers(headers);
64
+ }
65
+ // If headers is a NullableHeaders-like object (has 'values' property that is a Headers)
66
+ else if (
67
+ typeof headers === 'object' &&
68
+ headers !== null &&
69
+ 'values' in headers &&
70
+ isHeaders(headers.values)
71
+ ) {
72
+ return headers.values;
73
+ }
74
+ // If headers is a plain object
75
+ else if (typeof headers === 'object' && headers !== null) {
76
+ const entries: [string, string][] = Object.entries(headers)
77
+ .filter(([, v]) => typeof v === 'string')
78
+ .map(([k, v]) => [k, v as string]);
79
+ return new Headers(entries);
80
+ }
81
+ return new Headers();
82
+ });
83
+
84
+ return Object.fromEntries(output.entries());
85
+ }
86
+
87
+ type OpenAICoreRequestOptions = OpenAIClient.RequestOptions;
12
88
 
13
89
  function createAbortHandler(controller: AbortController): () => void {
14
90
  return function (): void {
15
91
  controller.abort();
16
92
  };
17
93
  }
94
+ /**
95
+ * Formats a tool in either OpenAI format, or LangChain structured tool format
96
+ * into an OpenAI tool format. If the tool is already in OpenAI format, return without
97
+ * any changes. If it is in LangChain structured tool format, convert it to OpenAI tool format
98
+ * using OpenAI's `zodFunction` util, falling back to `convertToOpenAIFunction` if the parameters
99
+ * returned from the `zodFunction` util are not defined.
100
+ *
101
+ * @param {BindToolsInput} tool The tool to convert to an OpenAI tool.
102
+ * @param {Object} [fields] Additional fields to add to the OpenAI tool.
103
+ * @returns {ToolDefinition} The inputted tool in OpenAI tool format.
104
+ */
105
+ export function _convertToOpenAITool(
106
+ tool: BindToolsInput,
107
+ fields?: {
108
+ /**
109
+ * If `true`, model output is guaranteed to exactly match the JSON Schema
110
+ * provided in the function definition.
111
+ */
112
+ strict?: boolean;
113
+ }
114
+ ): OpenAIClient.ChatCompletionTool {
115
+ let toolDef: OpenAIClient.ChatCompletionTool | undefined;
116
+
117
+ if (isLangChainTool(tool)) {
118
+ toolDef = formatToOpenAITool(tool);
119
+ } else {
120
+ toolDef = tool as ToolDefinition;
121
+ }
122
+
123
+ if (fields?.strict !== undefined) {
124
+ toolDef.function.strict = fields.strict;
125
+ }
126
+
127
+ return toolDef;
128
+ }
129
+
130
+ function _convertChatOpenAIToolTypeToOpenAITool(
131
+ tool: ChatOpenAIToolType,
132
+ fields?: {
133
+ strict?: boolean;
134
+ }
135
+ ): OpenAIClient.ChatCompletionTool {
136
+ if (isOpenAITool(tool)) {
137
+ if (fields?.strict !== undefined) {
138
+ return {
139
+ ...tool,
140
+ function: {
141
+ ...tool.function,
142
+ strict: fields.strict,
143
+ },
144
+ };
145
+ }
146
+
147
+ return tool;
148
+ }
149
+ return _convertToOpenAITool(tool, fields);
150
+ }
18
151
 
19
152
  export class CustomOpenAIClient extends OpenAIClient {
20
153
  abortHandler?: () => void;
@@ -87,13 +220,36 @@ export class CustomAzureOpenAIClient extends AzureOpenAIClient {
87
220
  }
88
221
  }
89
222
 
223
+ function isBuiltInTool(tool: ChatOpenAIToolType): tool is ResponsesTool {
224
+ return 'type' in tool && tool.type !== 'function';
225
+ }
226
+
90
227
  export class ChatOpenAI extends OriginalChatOpenAI<t.ChatOpenAICallOptions> {
91
228
  public get exposedClient(): CustomOpenAIClient {
92
229
  return this.client;
93
230
  }
231
+ override bindTools(
232
+ tools: ChatOpenAIToolType[],
233
+ kwargs?: Partial<t.ChatOpenAICallOptions>
234
+ ): Runnable<BaseLanguageModelInput, AIMessageChunk, t.ChatOpenAICallOptions> {
235
+ let strict: boolean | undefined;
236
+ if (kwargs?.strict !== undefined) {
237
+ strict = kwargs.strict;
238
+ } else if (this.supportsStrictToolCalling !== undefined) {
239
+ strict = this.supportsStrictToolCalling;
240
+ }
241
+ return this.withConfig({
242
+ tools: tools.map((tool) =>
243
+ isBuiltInTool(tool)
244
+ ? tool
245
+ : _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })
246
+ ),
247
+ ...kwargs,
248
+ } as Partial<t.ChatOpenAICallOptions>);
249
+ }
94
250
  protected _getClientOptions(
95
- options?: t.OpenAICoreRequestOptions
96
- ): t.OpenAICoreRequestOptions {
251
+ options?: OpenAICoreRequestOptions
252
+ ): OpenAICoreRequestOptions {
97
253
  if (!(this.client as OpenAIClient | undefined)) {
98
254
  const openAIEndpointConfig: t.OpenAIEndpointConfig = {
99
255
  baseURL: this.clientConfig.baseURL,
@@ -115,20 +271,39 @@ export class ChatOpenAI extends OriginalChatOpenAI<t.ChatOpenAICallOptions> {
115
271
  const requestOptions = {
116
272
  ...this.clientConfig,
117
273
  ...options,
118
- } as t.OpenAICoreRequestOptions;
274
+ } as OpenAICoreRequestOptions;
119
275
  return requestOptions;
120
276
  }
121
277
  }
122
278
 
123
279
  export class AzureChatOpenAI extends OriginalAzureChatOpenAI {
280
+ override bindTools(
281
+ tools: ChatOpenAIToolType[],
282
+ kwargs?: Partial<t.ChatOpenAICallOptions>
283
+ ): Runnable<BaseLanguageModelInput, AIMessageChunk, t.ChatOpenAICallOptions> {
284
+ let strict: boolean | undefined;
285
+ if (kwargs?.strict !== undefined) {
286
+ strict = kwargs.strict;
287
+ } else if (this.supportsStrictToolCalling !== undefined) {
288
+ strict = this.supportsStrictToolCalling;
289
+ }
290
+ return this.withConfig({
291
+ tools: tools.map((tool) =>
292
+ isBuiltInTool(tool)
293
+ ? tool
294
+ : _convertChatOpenAIToolTypeToOpenAITool(tool, { strict })
295
+ ),
296
+ ...kwargs,
297
+ } as Partial<t.ChatOpenAICallOptions>);
298
+ }
124
299
  public get exposedClient(): CustomOpenAIClient {
125
300
  return this.client;
126
301
  }
127
302
  protected _getClientOptions(
128
- options: t.OpenAICoreRequestOptions | undefined
129
- ): t.OpenAICoreRequestOptions {
130
- if (!(this.client as AzureOpenAIClient | undefined)) {
131
- const openAIEndpointConfig: t.OpenAIEndpointConfig = {
303
+ options: OpenAICoreRequestOptions | undefined
304
+ ): OpenAICoreRequestOptions {
305
+ if (!(this.client as unknown as AzureOpenAIClient | undefined)) {
306
+ const openAIEndpointConfig: OpenAIEndpointConfig = {
132
307
  azureOpenAIApiDeploymentName: this.azureOpenAIApiDeploymentName,
133
308
  azureOpenAIApiInstanceName: this.azureOpenAIApiInstanceName,
134
309
  azureOpenAIApiKey: this.azureOpenAIApiKey,
@@ -154,25 +329,26 @@ export class AzureChatOpenAI extends OriginalAzureChatOpenAI {
154
329
  delete params.baseURL;
155
330
  }
156
331
 
332
+ const defaultHeaders = normalizeHeaders(params.defaultHeaders);
157
333
  params.defaultHeaders = {
158
334
  ...params.defaultHeaders,
159
335
  'User-Agent':
160
- params.defaultHeaders?.['User-Agent'] != null
161
- ? `${params.defaultHeaders['User-Agent']}: langchainjs-azure-openai-v2`
336
+ defaultHeaders['User-Agent'] != null
337
+ ? `${defaultHeaders['User-Agent']}: langchainjs-azure-openai-v2`
162
338
  : 'langchainjs-azure-openai-v2',
163
339
  };
164
340
 
165
341
  this.client = new CustomAzureOpenAIClient({
166
342
  apiVersion: this.azureOpenAIApiVersion,
167
343
  azureADTokenProvider: this.azureADTokenProvider,
168
- ...params,
169
- });
344
+ ...(params as t.AzureOpenAIInput),
345
+ }) as unknown as CustomOpenAIClient;
170
346
  }
171
347
 
172
348
  const requestOptions = {
173
349
  ...this.clientConfig,
174
350
  ...options,
175
- } as t.OpenAICoreRequestOptions;
351
+ } as OpenAICoreRequestOptions;
176
352
  if (this.azureOpenAIApiKey != null) {
177
353
  requestOptions.headers = {
178
354
  'api-key': this.azureOpenAIApiKey,
@@ -186,7 +362,6 @@ export class AzureChatOpenAI extends OriginalAzureChatOpenAI {
186
362
  return requestOptions;
187
363
  }
188
364
  }
189
-
190
365
  export class ChatDeepSeek extends OriginalChatDeepSeek {
191
366
  public get exposedClient(): CustomOpenAIClient {
192
367
  return this.client;
@@ -99,7 +99,7 @@ async function testStandardStreaming(): Promise<void> {
99
99
  const openAIConfig = llmConfig as t.OpenAIClientOptions;
100
100
  if (openAIConfig.configuration) {
101
101
  openAIConfig.configuration.fetch = (
102
- url: RequestInfo,
102
+ url: string | URL | Request,
103
103
  init?: RequestInit
104
104
  ) => {
105
105
  console.log('Fetching:', url);