@librechat/agents 3.3.5 → 3.3.7

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 (127) hide show
  1. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
  2. package/dist/cjs/llm/openai/index.cjs +51 -6
  3. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  4. package/dist/cjs/main.cjs +16 -0
  5. package/dist/cjs/session/JsonlSessionStore.cjs +1 -1
  6. package/dist/cjs/stream.cjs +6 -1
  7. package/dist/cjs/stream.cjs.map +1 -1
  8. package/dist/cjs/tools/BashExecutor.cjs +3 -1
  9. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  10. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -0
  11. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  12. package/dist/cjs/tools/CodeExecutor.cjs +19 -16
  13. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  14. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +2 -0
  15. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  16. package/dist/cjs/tools/ReadFile.cjs +8 -4
  17. package/dist/cjs/tools/ReadFile.cjs.map +1 -1
  18. package/dist/cjs/tools/SkillTool.cjs +2 -0
  19. package/dist/cjs/tools/SkillTool.cjs.map +1 -1
  20. package/dist/cjs/tools/SubagentTool.cjs +3 -0
  21. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  22. package/dist/cjs/tools/ToolNode.cjs +88 -27
  23. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  24. package/dist/cjs/tools/ToolSearch.cjs +5 -1
  25. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  26. package/dist/cjs/tools/intentArg.cjs +256 -0
  27. package/dist/cjs/tools/intentArg.cjs.map +1 -0
  28. package/dist/cjs/tools/local/CompileCheckTool.cjs +3 -2
  29. package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
  30. package/dist/cjs/tools/local/LocalCodingTools.cjs +13 -12
  31. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
  32. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +2 -2
  33. package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
  34. package/dist/cjs/tools/search/schema.cjs +2 -0
  35. package/dist/cjs/tools/search/schema.cjs.map +1 -1
  36. package/dist/cjs/tools/search/tool.cjs +31 -1
  37. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  38. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -0
  39. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  40. package/dist/cjs/tools/toolOutputReferences.cjs +41 -7
  41. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  42. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
  43. package/dist/esm/llm/openai/index.mjs +51 -6
  44. package/dist/esm/llm/openai/index.mjs.map +1 -1
  45. package/dist/esm/main.mjs +4 -3
  46. package/dist/esm/session/JsonlSessionStore.mjs +1 -1
  47. package/dist/esm/stream.mjs +6 -1
  48. package/dist/esm/stream.mjs.map +1 -1
  49. package/dist/esm/tools/BashExecutor.mjs +3 -1
  50. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  51. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +2 -0
  52. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  53. package/dist/esm/tools/CodeExecutor.mjs +19 -16
  54. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  55. package/dist/esm/tools/ProgrammaticToolCalling.mjs +2 -0
  56. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  57. package/dist/esm/tools/ReadFile.mjs +8 -4
  58. package/dist/esm/tools/ReadFile.mjs.map +1 -1
  59. package/dist/esm/tools/SkillTool.mjs +2 -0
  60. package/dist/esm/tools/SkillTool.mjs.map +1 -1
  61. package/dist/esm/tools/SubagentTool.mjs +3 -0
  62. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  63. package/dist/esm/tools/ToolNode.mjs +88 -28
  64. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  65. package/dist/esm/tools/ToolSearch.mjs +5 -1
  66. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  67. package/dist/esm/tools/intentArg.mjs +244 -0
  68. package/dist/esm/tools/intentArg.mjs.map +1 -0
  69. package/dist/esm/tools/local/CompileCheckTool.mjs +3 -2
  70. package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
  71. package/dist/esm/tools/local/LocalCodingTools.mjs +13 -12
  72. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
  73. package/dist/esm/tools/local/LocalExecutionEngine.mjs +2 -2
  74. package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
  75. package/dist/esm/tools/search/schema.mjs +2 -0
  76. package/dist/esm/tools/search/schema.mjs.map +1 -1
  77. package/dist/esm/tools/search/tool.mjs +31 -2
  78. package/dist/esm/tools/search/tool.mjs.map +1 -1
  79. package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -0
  80. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  81. package/dist/esm/tools/toolOutputReferences.mjs +41 -7
  82. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  83. package/dist/types/index.d.ts +1 -0
  84. package/dist/types/llm/openai/index.d.ts +0 -11
  85. package/dist/types/tools/BashExecutor.d.ts +18 -0
  86. package/dist/types/tools/CodeExecutor.d.ts +18 -0
  87. package/dist/types/tools/ReadFile.d.ts +18 -0
  88. package/dist/types/tools/SkillTool.d.ts +18 -0
  89. package/dist/types/tools/SubagentTool.d.ts +9 -0
  90. package/dist/types/tools/ToolNode.d.ts +37 -5
  91. package/dist/types/tools/ToolSearch.d.ts +18 -0
  92. package/dist/types/tools/intentArg.d.ts +181 -0
  93. package/dist/types/tools/ptcTimeout.d.ts +2 -0
  94. package/dist/types/tools/search/schema.d.ts +18 -0
  95. package/dist/types/tools/search/tool.d.ts +18 -0
  96. package/dist/types/tools/toolOutputReferences.d.ts +12 -2
  97. package/dist/types/types/stream.d.ts +18 -0
  98. package/dist/types/types/tools.d.ts +27 -2
  99. package/package.json +1 -1
  100. package/src/index.ts +1 -0
  101. package/src/llm/openai/index.ts +74 -10
  102. package/src/llm/openai/llm.spec.ts +105 -0
  103. package/src/stream.ts +12 -1
  104. package/src/tools/BashExecutor.ts +6 -0
  105. package/src/tools/BashProgrammaticToolCalling.ts +2 -0
  106. package/src/tools/CodeExecutor.ts +6 -0
  107. package/src/tools/ProgrammaticToolCalling.ts +2 -0
  108. package/src/tools/ReadFile.ts +2 -0
  109. package/src/tools/SkillTool.ts +2 -0
  110. package/src/tools/SubagentTool.ts +3 -0
  111. package/src/tools/ToolNode.ts +190 -43
  112. package/src/tools/ToolSearch.ts +3 -0
  113. package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +174 -0
  114. package/src/tools/__tests__/intentArg.test.ts +357 -0
  115. package/src/tools/__tests__/intentCoverage.test.ts +139 -0
  116. package/src/tools/__tests__/toolOutputReferences.test.ts +83 -0
  117. package/src/tools/intentArg.ts +357 -0
  118. package/src/tools/local/CompileCheckTool.ts +4 -3
  119. package/src/tools/local/LocalCodingTools.ts +14 -13
  120. package/src/tools/ptcTimeout.ts +2 -0
  121. package/src/tools/search/outcome.test.ts +90 -0
  122. package/src/tools/search/schema.ts +3 -0
  123. package/src/tools/search/tool.ts +47 -1
  124. package/src/tools/subagent/SubagentExecutor.ts +2 -1
  125. package/src/tools/toolOutputReferences.ts +77 -9
  126. package/src/types/stream.ts +18 -0
  127. package/src/types/tools.ts +24 -2
@@ -17,6 +17,7 @@ import { createKeenableScraper } from './keenable-scraper';
17
17
  import { createSerperScraper } from './serper-scraper';
18
18
  import { createTavilyScraper } from './tavily-scraper';
19
19
  import { createFirecrawlScraper } from './firecrawl';
20
+ import { INTENT_PROPERTY } from '@/tools/intentArg';
20
21
  import { createCrwScraper } from './crw-scraper';
21
22
  import { expandHighlights } from './highlights';
22
23
  import { formatResultsForLLM } from './format';
@@ -24,6 +25,46 @@ import { createDefaultLogger } from './utils';
24
25
  import { createReranker } from './rerankers';
25
26
  import { Constants } from '@/common';
26
27
 
28
+ /**
29
+ * Settled label for a `web_search` call's intent (see `intentArg.ts`).
30
+ *
31
+ * Counts the result kinds `formatResultsForLLM` actually renders —
32
+ * `references` only tracks links embedded in extracted highlights, so it
33
+ * undercounts ordinary results and can overcount when one highlight embeds
34
+ * several links.
35
+ *
36
+ * A caught provider or processing failure is reported through `data.error`
37
+ * while the tool still returns NORMALLY, so that case must author its own
38
+ * label: the `ToolMessage` carries success status, so without an authored
39
+ * outcome the in-flight intent ("Searching…") would stand as the settled
40
+ * label and present a failed search as an ordinary one.
41
+ *
42
+ * Returns undefined for a genuine zero-result search, leaving the
43
+ * model-authored intent to stand unchanged as the label.
44
+ */
45
+ export function resolveSearchOutcome(
46
+ data: t.SearchResultData,
47
+ query: string
48
+ ): string | undefined {
49
+ if (data.error != null && data.error !== '') {
50
+ return `Search failed for "${query}"`;
51
+ }
52
+ const count =
53
+ (data.organic?.length ?? 0) +
54
+ (data.topStories?.length ?? 0) +
55
+ (data.news?.length ?? 0) +
56
+ (data.images?.length ?? 0) +
57
+ (data.videos?.length ?? 0) +
58
+ (data.places?.length ?? 0) +
59
+ (data.peopleAlsoAsk?.length ?? 0) +
60
+ (data.knowledgeGraph != null ? 1 : 0) +
61
+ (data.answerBox != null ? 1 : 0);
62
+ if (count === 0) {
63
+ return undefined;
64
+ }
65
+ return `Found ${count} result${count === 1 ? '' : 's'} for "${query}"`;
66
+ }
67
+
27
68
  /**
28
69
  * Executes parallel searches and merges the results,
29
70
  * deduplicating top stories by link
@@ -335,7 +376,11 @@ function createTool({
335
376
  maxOutputChars
336
377
  );
337
378
  const data: t.SearchResultData = { turn, ...searchResult, references };
338
- return [output, { [Constants.WEB_SEARCH]: data }];
379
+ const outcome = resolveSearchOutcome(data, query);
380
+ return [
381
+ output,
382
+ { [Constants.WEB_SEARCH]: data, ...(outcome != null && { outcome }) },
383
+ ];
339
384
  },
340
385
  {
341
386
  name: WebSearchToolName,
@@ -421,6 +466,7 @@ export const createSearchTool = (
421
466
  : tavilySearchOptions;
422
467
 
423
468
  const schemaProperties: Record<string, unknown> = {
469
+ intent: { ...INTENT_PROPERTY },
424
470
  query: querySchema,
425
471
  date: dateSchema,
426
472
  images: imagesSchema,
@@ -115,7 +115,7 @@ type SanitizedStepCompleted =
115
115
  };
116
116
 
117
117
  type SanitizedProcessedToolCall = Partial<
118
- Pick<ProcessedToolCall, 'args' | 'id' | 'name' | 'output' | 'progress'>
118
+ Pick<ProcessedToolCall, 'args' | 'id' | 'name' | 'output' | 'progress' | 'outcome'>
119
119
  >;
120
120
 
121
121
  type SanitizedRunStepCompleted = {
@@ -1195,6 +1195,7 @@ function sanitizeProcessedToolCall(
1195
1195
  sanitized.args = call.args;
1196
1196
  }
1197
1197
  assignString(sanitized, 'output', call.output);
1198
+ assignString(sanitized, 'outcome', call.outcome);
1198
1199
  assignNumber(sanitized, 'progress', call.progress);
1199
1200
  return sanitized;
1200
1201
  }
@@ -30,6 +30,25 @@ import {
30
30
  HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE,
31
31
  } from '@/utils/truncation';
32
32
  import { isComputerCallOutputMessage } from '@/utils/toolContent';
33
+ import { INTENT_ARG } from '@/tools/intentArg';
34
+
35
+ /** Parses a stringified JSON object arg; undefined for anything else. */
36
+ function parseStringifiedArgsObject(
37
+ value: string
38
+ ): Record<string, unknown> | undefined {
39
+ if (!value.trim().startsWith('{')) {
40
+ return undefined;
41
+ }
42
+ try {
43
+ const parsed = JSON.parse(value) as unknown;
44
+ if (parsed != null && typeof parsed === 'object' && !Array.isArray(parsed)) {
45
+ return parsed as Record<string, unknown>;
46
+ }
47
+ } catch {
48
+ return undefined;
49
+ }
50
+ return undefined;
51
+ }
33
52
 
34
53
  /**
35
54
  * Non-global matcher for a single `{{tool<i>turn<n>}}` placeholder.
@@ -89,9 +108,20 @@ export type ResolveResult<T> = {
89
108
  * point in time, ignoring any subsequent registrations.
90
109
  */
91
110
  export interface ToolOutputResolveView {
92
- resolve<T>(args: T): ResolveResult<T>;
111
+ resolve<T>(args: T, options?: ResolveOptions): ResolveResult<T>;
93
112
  }
94
113
 
114
+ /**
115
+ * Per-call resolution options. `substituteIntentKey` opts the top-level
116
+ * `intent` key back INTO placeholder substitution: the exemption protects
117
+ * the injected display label, but a tool whose own schema declares a
118
+ * business parameter named `intent` (the injectors skip such tools) still
119
+ * needs references piped into it like any other argument.
120
+ */
121
+ export type ResolveOptions = {
122
+ substituteIntentKey?: boolean;
123
+ };
124
+
95
125
  /**
96
126
  * Pre-resolved arg map keyed by `toolCallId`. Used by the mixed
97
127
  * direct+event dispatch path to feed event calls' resolved args
@@ -333,12 +363,16 @@ export class ToolOutputReferenceRegistry {
333
363
  * the serialized args, the original input is returned without
334
364
  * walking the tree.
335
365
  */
336
- resolve<T>(runId: string | undefined, args: T): ResolveResult<T> {
366
+ resolve<T>(
367
+ runId: string | undefined,
368
+ args: T,
369
+ options?: ResolveOptions
370
+ ): ResolveResult<T> {
337
371
  if (!hasAnyPlaceholder(args)) {
338
372
  return { resolved: args, unresolved: [] };
339
373
  }
340
374
  const bucket = this.runStates.get(this.keyFor(runId));
341
- return this.resolveAgainst(bucket?.entries ?? EMPTY_ENTRIES, args);
375
+ return this.resolveAgainst(bucket?.entries ?? EMPTY_ENTRIES, args, options);
342
376
  }
343
377
 
344
378
  /**
@@ -358,27 +392,52 @@ export class ToolOutputReferenceRegistry {
358
392
  ? new Map(bucket.entries)
359
393
  : EMPTY_ENTRIES;
360
394
  return {
361
- resolve: <T>(args: T): ResolveResult<T> =>
362
- this.resolveAgainst(entries, args),
395
+ resolve: <T>(args: T, options?: ResolveOptions): ResolveResult<T> =>
396
+ this.resolveAgainst(entries, args, options),
363
397
  };
364
398
  }
365
399
 
366
400
  private resolveAgainst<T>(
367
401
  entries: ReadonlyMap<string, string>,
368
- args: T
402
+ args: T,
403
+ options?: ResolveOptions
369
404
  ): ResolveResult<T> {
370
405
  if (!hasAnyPlaceholder(args)) {
371
406
  return { resolved: args, unresolved: [] };
372
407
  }
408
+ const exemptIntentKey = options?.substituteIntentKey !== true;
373
409
  const unresolved = new Set<string>();
374
- const resolved = this.transform(entries, args, unresolved) as T;
410
+ /**
411
+ * Providers may deliver the args OBJECT as a JSON string. A plain
412
+ * string-root transform would expand placeholders inside the `intent`
413
+ * label too, bypassing the top-level exclusion below — parse, transform
414
+ * key-aware, and re-serialize so the label stays verbatim while every
415
+ * other field still substitutes. Only taken when an `intent` key is
416
+ * actually present; other strings keep the fast raw-string path.
417
+ */
418
+ if (exemptIntentKey && typeof args === 'string') {
419
+ const parsedRoot = parseStringifiedArgsObject(args);
420
+ if (parsedRoot != null && INTENT_ARG in parsedRoot) {
421
+ const resolved = JSON.stringify(
422
+ this.transform(entries, parsedRoot, unresolved, true)
423
+ ) as T;
424
+ return { resolved, unresolved: Array.from(unresolved) };
425
+ }
426
+ }
427
+ const resolved = this.transform(
428
+ entries,
429
+ args,
430
+ unresolved,
431
+ exemptIntentKey
432
+ ) as T;
375
433
  return { resolved, unresolved: Array.from(unresolved) };
376
434
  }
377
435
 
378
436
  private transform(
379
437
  entries: ReadonlyMap<string, string>,
380
438
  value: unknown,
381
- unresolved: Set<string>
439
+ unresolved: Set<string>,
440
+ exemptRootIntent = false
382
441
  ): unknown {
383
442
  if (typeof value === 'string') {
384
443
  return this.replaceInString(entries, value, unresolved);
@@ -390,7 +449,16 @@ export class ToolOutputReferenceRegistry {
390
449
  const source = value as Record<string, unknown>;
391
450
  const next: Record<string, unknown> = {};
392
451
  for (const [key, item] of Object.entries(source)) {
393
- next[key] = this.transform(entries, item, unresolved);
452
+ /**
453
+ * The top-level `intent` arg is a display label, never a data
454
+ * channel: expanding a `{{tool<i>turn<n>}}` placeholder there would
455
+ * dump a stored output (up to the registry cap) into a single-line
456
+ * UI label and persist it with the message. Left verbatim instead.
457
+ */
458
+ next[key] =
459
+ exemptRootIntent && key === INTENT_ARG
460
+ ? item
461
+ : this.transform(entries, item, unresolved);
394
462
  }
395
463
  return next;
396
464
  }
@@ -142,6 +142,19 @@ export type ProcessedToolCall = {
142
142
  id: string;
143
143
  output: string;
144
144
  progress: number;
145
+ /**
146
+ * Settled label for the call, resolved from the tool-supplied
147
+ * `outcome`/`outcome_patch` result fields against the model-authored
148
+ * `intent` arg. Present ONLY when the tool authored one.
149
+ *
150
+ * When absent, display the `intent` arg unchanged — do NOT rewrite its
151
+ * tense. A gerund→past-tense rewrite can only be a closed list of English
152
+ * verbs, so it never fires for the non-English labels this feature expects
153
+ * and fires for some sibling calls but not others within one group.
154
+ * Completion belongs to UI state (the shimmer stopping, the icon settling),
155
+ * which is language-neutral and always consistent.
156
+ */
157
+ outcome?: string;
145
158
  };
146
159
 
147
160
  export type ProcessedContent = {
@@ -336,6 +349,11 @@ export type ToolCallPart = {
336
349
  id?: string;
337
350
  /** If provided, the output of the tool call */
338
351
  output?: ToolResultContent['content'];
352
+ /**
353
+ * Tool-authored settled label for the call (see `ProcessedToolCall.outcome`),
354
+ * preserved through aggregation so it survives persistence/reload.
355
+ */
356
+ outcome?: string;
339
357
  /** Auth URL */
340
358
  auth?: string;
341
359
  /** Expiration time */
@@ -236,8 +236,12 @@ export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
236
236
  export type ToolEndEvent = {
237
237
  /** The Step Id of the Tool Call */
238
238
  id: string;
239
- /** The Completed Tool Call */
240
- tool_call: ToolCall;
239
+ /**
240
+ * The Completed Tool Call. Carries the tool-authored `outcome` label when
241
+ * present (see `ProcessedToolCall.outcome`) so `ON_RUN_STEP_COMPLETED`
242
+ * consumers can read it without an unsafe cast.
243
+ */
244
+ tool_call: ToolCall & { output?: string; progress?: number; outcome?: string };
241
245
  /** The content index of the tool call */
242
246
  index: number;
243
247
  type?: 'tool_call';
@@ -521,6 +525,17 @@ export type InjectedMessage = {
521
525
  skillName?: string;
522
526
  };
523
527
 
528
+ /**
529
+ * In-place edit of a call's model-authored `intent` label: the first
530
+ * occurrence of `from` in the intent is replaced with `to` (case-sensitive).
531
+ * Lets a tool settle the label while preserving the model's own phrasing,
532
+ * e.g. `{ from: 'Searching', to: 'Searched' }`.
533
+ */
534
+ export type OutcomePatch = {
535
+ from: string;
536
+ to: string;
537
+ };
538
+
524
539
  /** Result for a single tool call in event-driven execution */
525
540
  export type ToolExecuteResult = {
526
541
  /** Matches ToolCallRequest.id */
@@ -533,6 +548,13 @@ export type ToolExecuteResult = {
533
548
  status: 'success' | 'error';
534
549
  /** Error message if status is 'error' */
535
550
  errorMessage?: string;
551
+ /**
552
+ * Settled human-readable label for this call, replacing the model-authored
553
+ * `intent` arg in the UI. Full replacement; wins over `outcome_patch`.
554
+ */
555
+ outcome?: string;
556
+ /** In-place edit of the model-authored `intent` label (see {@link OutcomePatch}). */
557
+ outcome_patch?: OutcomePatch;
536
558
  /**
537
559
  * Messages to inject into graph state after the ToolMessage for this call.
538
560
  * Placed after tool results to respect provider message ordering (tool_call -> tool_result adjacency).