@librechat/agents 3.7.3 → 3.7.4

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.
@@ -71,6 +71,8 @@ import {
71
71
  } from '@/tools/intentArg';
72
72
  import {
73
73
  buildToolExecutionRequestPlan,
74
+ coerceArgsForSchema,
75
+ coerceRecordArgs,
74
76
  resolveRuntimeSessionHint,
75
77
  recordArgsEqual,
76
78
  } from '@/tools/eagerEventExecution';
@@ -1129,6 +1131,26 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
1129
1131
  );
1130
1132
  }
1131
1133
 
1134
+ private getToolParameterSchema(
1135
+ toolName: string
1136
+ ): t.JsonSchemaType | undefined {
1137
+ return (
1138
+ this.toolDefinitions?.get(toolName)?.parameters ??
1139
+ this.toolRegistry?.get(toolName)?.parameters
1140
+ );
1141
+ }
1142
+
1143
+ private coerceEventToolArgs(
1144
+ toolName: string,
1145
+ args: unknown
1146
+ ): Record<string, unknown> {
1147
+ const recordArgs = coerceRecordArgs(args);
1148
+ return coerceArgsForSchema(
1149
+ recordArgs ?? (args as Record<string, unknown>),
1150
+ this.getToolParameterSchema(toolName)
1151
+ );
1152
+ }
1153
+
1132
1154
  /** Serializes the live caller projection for event-driven hosts. */
1133
1155
  private getCallerCapabilityProjectionSnapshot(): t.CallerCapabilityProjectionSnapshot {
1134
1156
  return createCallerCapabilityProjectionSnapshot(
@@ -2758,7 +2780,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
2758
2780
  return {
2759
2781
  call,
2760
2782
  stepId: this.toolCallStepIds?.get(call.id!) ?? '',
2761
- args: resolvedArgs,
2783
+ args: this.coerceEventToolArgs(call.name, resolvedArgs),
2762
2784
  batchIndex: batchIndices?.[i],
2763
2785
  };
2764
2786
  });
@@ -3002,7 +3024,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
3002
3024
  entry.call.name
3003
3025
  ),
3004
3026
  });
3005
- entry.args = resolved as Record<string, unknown>;
3027
+ entry.args = this.coerceEventToolArgs(
3028
+ entry.call.name,
3029
+ resolved as Record<string, unknown>
3030
+ );
3006
3031
  if (entry.call.id != null) {
3007
3032
  if (unresolved.length > 0) {
3008
3033
  unresolvedByCallId.set(entry.call.id, unresolved);
@@ -3012,7 +3037,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
3012
3037
  }
3013
3038
  return;
3014
3039
  }
3015
- entry.args = nextArgs;
3040
+ entry.args = this.coerceEventToolArgs(entry.call.name, nextArgs);
3016
3041
  };
3017
3042
 
3018
3043
  const askEntries: Array<{
@@ -3369,6 +3394,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
3369
3394
  }),
3370
3395
  usageCount: this.toolUsageCount,
3371
3396
  invalidArgsBehavior: 'error-result',
3397
+ getToolSchema: (toolName) => this.getToolParameterSchema(toolName),
3372
3398
  recordTurn: (toolName, reservedTurn, callId) => {
3373
3399
  this.recordEventToolPlanningTurn(
3374
3400
  toolName,
@@ -19,6 +19,89 @@ export function coerceRecordArgs(
19
19
  return args as Record<string, unknown>;
20
20
  }
21
21
 
22
+ const INTEGER_STRING = /^-?(?:0|[1-9]\d*)$/;
23
+ const NUMBER_STRING = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
24
+
25
+ function coerceValueForSchema(
26
+ value: unknown,
27
+ schema: t.JsonSchemaType | undefined
28
+ ): unknown {
29
+ if (schema == null) {
30
+ return value;
31
+ }
32
+
33
+ if (schema.type === 'integer' && typeof value === 'string') {
34
+ if (!INTEGER_STRING.test(value)) {
35
+ return value;
36
+ }
37
+ const parsed = Number(value);
38
+ return Number.isSafeInteger(parsed) ? parsed : value;
39
+ }
40
+
41
+ if (
42
+ (schema.type === 'number' || schema.type === 'float') &&
43
+ typeof value === 'string'
44
+ ) {
45
+ if (!NUMBER_STRING.test(value)) {
46
+ return value;
47
+ }
48
+ const parsed = Number(value);
49
+ return Number.isFinite(parsed) && String(parsed) === value
50
+ ? parsed
51
+ : value;
52
+ }
53
+
54
+ if (schema.type === 'boolean' && typeof value === 'string') {
55
+ if (value === 'true') {
56
+ return true;
57
+ }
58
+ if (value === 'false') {
59
+ return false;
60
+ }
61
+ return value;
62
+ }
63
+
64
+ if (schema.type === 'array' && Array.isArray(value)) {
65
+ return value.map((item) => coerceValueForSchema(item, schema.items));
66
+ }
67
+
68
+ if (
69
+ schema.type !== 'object' ||
70
+ value == null ||
71
+ typeof value !== 'object' ||
72
+ Array.isArray(value)
73
+ ) {
74
+ return value;
75
+ }
76
+
77
+ const record = value as Record<string, unknown>;
78
+ const additionalProperties =
79
+ typeof schema.additionalProperties === 'object'
80
+ ? schema.additionalProperties
81
+ : undefined;
82
+
83
+ return Object.fromEntries(
84
+ Object.entries(record).map(([key, entry]) => [
85
+ key,
86
+ coerceValueForSchema(
87
+ entry,
88
+ schema.properties?.[key] ?? additionalProperties
89
+ ),
90
+ ])
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Applies only lossless, schema-directed repairs to model-generated arguments.
96
+ * The host remains responsible for full schema validation and all business rules.
97
+ */
98
+ export function coerceArgsForSchema(
99
+ args: Record<string, unknown>,
100
+ schema: t.JsonSchemaType | undefined
101
+ ): Record<string, unknown> {
102
+ return coerceValueForSchema(args, schema) as Record<string, unknown>;
103
+ }
104
+
22
105
  export function stableStringify(value: unknown): string {
23
106
  if (Array.isArray(value)) {
24
107
  return `[${value.map((item) => stableStringify(item)).join(',')}]`;
@@ -87,6 +170,7 @@ export function buildToolExecutionRequestPlan(args: {
87
170
  usageCount: Map<string, number>;
88
171
  invalidArgsBehavior?: 'abort' | 'error-result';
89
172
  recordTurn?: (toolName: string, turn: number, callId: string) => void;
173
+ getToolSchema?: (toolName: string) => t.JsonSchemaType | undefined;
90
174
  }): ToolExecutionRequestPlan | undefined {
91
175
  const invalidArgsBehavior = args.invalidArgsBehavior ?? 'abort';
92
176
  const prepared: Array<{
@@ -103,8 +187,8 @@ export function buildToolExecutionRequestPlan(args: {
103
187
  if (toolCall.id == null || toolCall.id === '' || toolCall.name === '') {
104
188
  return undefined;
105
189
  }
106
- const coercedArgs = coerceRecordArgs(toolCall.args);
107
- if (coercedArgs == null) {
190
+ const recordArgs = coerceRecordArgs(toolCall.args);
191
+ if (recordArgs == null) {
108
192
  if (invalidArgsBehavior === 'abort') {
109
193
  return undefined;
110
194
  }
@@ -120,6 +204,10 @@ export function buildToolExecutionRequestPlan(args: {
120
204
  });
121
205
  continue;
122
206
  }
207
+ const coercedArgs = coerceArgsForSchema(
208
+ recordArgs,
209
+ args.getToolSchema?.(toolCall.name)
210
+ );
123
211
  prepared.push({
124
212
  id: toolCall.id,
125
213
  name: toolCall.name,