@librechat/agents 4.0.1 → 4.0.3

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 (245) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +3 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/decisions/deadline.cjs +52 -0
  4. package/dist/cjs/decisions/deadline.cjs.map +1 -0
  5. package/dist/cjs/decisions/dialect.cjs +88 -0
  6. package/dist/cjs/decisions/dialect.cjs.map +1 -0
  7. package/dist/cjs/decisions/http.cjs +77 -0
  8. package/dist/cjs/decisions/http.cjs.map +1 -0
  9. package/dist/cjs/decisions/index.cjs +7 -0
  10. package/dist/cjs/decisions/presets.cjs +82 -0
  11. package/dist/cjs/decisions/presets.cjs.map +1 -0
  12. package/dist/cjs/decisions/questions.cjs +70 -0
  13. package/dist/cjs/decisions/questions.cjs.map +1 -0
  14. package/dist/cjs/decisions/structuredChat.cjs +172 -0
  15. package/dist/cjs/decisions/structuredChat.cjs.map +1 -0
  16. package/dist/cjs/decisions/structuredOutput.cjs +18 -0
  17. package/dist/cjs/decisions/structuredOutput.cjs.map +1 -0
  18. package/dist/cjs/decisions/traceMarker.cjs +6 -0
  19. package/dist/cjs/decisions/traceMarker.cjs.map +1 -0
  20. package/dist/cjs/decisions/transport.cjs +166 -0
  21. package/dist/cjs/decisions/transport.cjs.map +1 -0
  22. package/dist/cjs/decisions/types.cjs +44 -0
  23. package/dist/cjs/decisions/types.cjs.map +1 -0
  24. package/dist/cjs/graphs/Graph.cjs +28 -9
  25. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  26. package/dist/cjs/langfuseToolOutputTracing.cjs +6 -1
  27. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  28. package/dist/cjs/llm/google/index.cjs +1 -1
  29. package/dist/cjs/llm/invoke.cjs +34 -12
  30. package/dist/cjs/llm/invoke.cjs.map +1 -1
  31. package/dist/cjs/llm/openai/index.cjs +7 -0
  32. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  33. package/dist/cjs/llm/providerTextBoundary.cjs +355 -0
  34. package/dist/cjs/llm/providerTextBoundary.cjs.map +1 -0
  35. package/dist/cjs/llm/providerTextChunk.cjs +14 -0
  36. package/dist/cjs/llm/providerTextChunk.cjs.map +1 -0
  37. package/dist/cjs/llm/providerTextControls.cjs +236 -0
  38. package/dist/cjs/llm/providerTextControls.cjs.map +1 -0
  39. package/dist/cjs/llm/stream/smoother.cjs +2 -1
  40. package/dist/cjs/llm/stream/smoother.cjs.map +1 -1
  41. package/dist/cjs/main.cjs +49 -1
  42. package/dist/cjs/protection/providerText.cjs +224 -0
  43. package/dist/cjs/protection/providerText.cjs.map +1 -0
  44. package/dist/cjs/protection/providerTextInput.cjs +25 -0
  45. package/dist/cjs/protection/providerTextInput.cjs.map +1 -0
  46. package/dist/cjs/rerank/index.cjs +2 -0
  47. package/dist/cjs/rerank/search.cjs +45 -0
  48. package/dist/cjs/rerank/search.cjs.map +1 -0
  49. package/dist/cjs/rerank/systemone.cjs +70 -0
  50. package/dist/cjs/rerank/systemone.cjs.map +1 -0
  51. package/dist/cjs/run.cjs +11 -1
  52. package/dist/cjs/run.cjs.map +1 -1
  53. package/dist/cjs/stream.cjs +42 -14
  54. package/dist/cjs/stream.cjs.map +1 -1
  55. package/dist/cjs/summarization/node.cjs +26 -8
  56. package/dist/cjs/summarization/node.cjs.map +1 -1
  57. package/dist/cjs/tools/SubagentTool.cjs +11 -3
  58. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  59. package/dist/cjs/tools/ToolNode.cjs +6 -5
  60. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  61. package/dist/cjs/tools/search/rerankers.cjs +1 -0
  62. package/dist/cjs/tools/search/search.cjs +3 -3
  63. package/dist/cjs/tools/search/search.cjs.map +1 -1
  64. package/dist/cjs/tools/search/tool.cjs +4 -3
  65. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  66. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +13 -2
  67. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
  68. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +149 -26
  69. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  70. package/dist/cjs/tools/subagent/SubagentReplay.cjs +4 -2
  71. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  72. package/dist/cjs/tools/subagent/childGraphConfig.cjs +3 -1
  73. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
  74. package/dist/cjs/tools/subagent/diagnostics.cjs +88 -2
  75. package/dist/cjs/tools/subagent/diagnostics.cjs.map +1 -1
  76. package/dist/cjs/tools/subagent/hostArgs.cjs +214 -0
  77. package/dist/cjs/tools/subagent/hostArgs.cjs.map +1 -0
  78. package/dist/cjs/tools/subagent/index.cjs +2 -0
  79. package/dist/cjs/tools/toolErrorContent.cjs +1 -1
  80. package/dist/esm/agents/AgentContext.mjs +3 -2
  81. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  82. package/dist/esm/decisions/deadline.mjs +52 -0
  83. package/dist/esm/decisions/deadline.mjs.map +1 -0
  84. package/dist/esm/decisions/dialect.mjs +87 -0
  85. package/dist/esm/decisions/dialect.mjs.map +1 -0
  86. package/dist/esm/decisions/http.mjs +75 -0
  87. package/dist/esm/decisions/http.mjs.map +1 -0
  88. package/dist/esm/decisions/index.mjs +8 -0
  89. package/dist/esm/decisions/presets.mjs +77 -0
  90. package/dist/esm/decisions/presets.mjs.map +1 -0
  91. package/dist/esm/decisions/questions.mjs +67 -0
  92. package/dist/esm/decisions/questions.mjs.map +1 -0
  93. package/dist/esm/decisions/structuredChat.mjs +172 -0
  94. package/dist/esm/decisions/structuredChat.mjs.map +1 -0
  95. package/dist/esm/decisions/structuredOutput.mjs +18 -0
  96. package/dist/esm/decisions/structuredOutput.mjs.map +1 -0
  97. package/dist/esm/decisions/traceMarker.mjs +6 -0
  98. package/dist/esm/decisions/traceMarker.mjs.map +1 -0
  99. package/dist/esm/decisions/transport.mjs +164 -0
  100. package/dist/esm/decisions/transport.mjs.map +1 -0
  101. package/dist/esm/decisions/types.mjs +39 -0
  102. package/dist/esm/decisions/types.mjs.map +1 -0
  103. package/dist/esm/graphs/Graph.mjs +28 -9
  104. package/dist/esm/graphs/Graph.mjs.map +1 -1
  105. package/dist/esm/langfuseToolOutputTracing.mjs +6 -1
  106. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  107. package/dist/esm/llm/google/index.mjs +1 -1
  108. package/dist/esm/llm/invoke.mjs +34 -12
  109. package/dist/esm/llm/invoke.mjs.map +1 -1
  110. package/dist/esm/llm/openai/index.mjs +7 -0
  111. package/dist/esm/llm/openai/index.mjs.map +1 -1
  112. package/dist/esm/llm/providerTextBoundary.mjs +355 -0
  113. package/dist/esm/llm/providerTextBoundary.mjs.map +1 -0
  114. package/dist/esm/llm/providerTextChunk.mjs +13 -0
  115. package/dist/esm/llm/providerTextChunk.mjs.map +1 -0
  116. package/dist/esm/llm/providerTextControls.mjs +230 -0
  117. package/dist/esm/llm/providerTextControls.mjs.map +1 -0
  118. package/dist/esm/llm/stream/smoother.mjs +2 -1
  119. package/dist/esm/llm/stream/smoother.mjs.map +1 -1
  120. package/dist/esm/main.mjs +16 -2
  121. package/dist/esm/protection/providerText.mjs +218 -0
  122. package/dist/esm/protection/providerText.mjs.map +1 -0
  123. package/dist/esm/protection/providerTextInput.mjs +24 -0
  124. package/dist/esm/protection/providerTextInput.mjs.map +1 -0
  125. package/dist/esm/rerank/index.mjs +3 -0
  126. package/dist/esm/rerank/search.mjs +45 -0
  127. package/dist/esm/rerank/search.mjs.map +1 -0
  128. package/dist/esm/rerank/systemone.mjs +70 -0
  129. package/dist/esm/rerank/systemone.mjs.map +1 -0
  130. package/dist/esm/run.mjs +11 -1
  131. package/dist/esm/run.mjs.map +1 -1
  132. package/dist/esm/stream.mjs +42 -14
  133. package/dist/esm/stream.mjs.map +1 -1
  134. package/dist/esm/summarization/node.mjs +26 -8
  135. package/dist/esm/summarization/node.mjs.map +1 -1
  136. package/dist/esm/tools/SubagentTool.mjs +11 -3
  137. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  138. package/dist/esm/tools/ToolNode.mjs +6 -5
  139. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  140. package/dist/esm/tools/search/rerankers.mjs +1 -1
  141. package/dist/esm/tools/search/search.mjs +3 -3
  142. package/dist/esm/tools/search/search.mjs.map +1 -1
  143. package/dist/esm/tools/search/tool.mjs +4 -3
  144. package/dist/esm/tools/search/tool.mjs.map +1 -1
  145. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +13 -2
  146. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
  147. package/dist/esm/tools/subagent/SubagentExecutor.mjs +149 -26
  148. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  149. package/dist/esm/tools/subagent/SubagentReplay.mjs +4 -2
  150. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  151. package/dist/esm/tools/subagent/childGraphConfig.mjs +3 -1
  152. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
  153. package/dist/esm/tools/subagent/diagnostics.mjs +84 -3
  154. package/dist/esm/tools/subagent/diagnostics.mjs.map +1 -1
  155. package/dist/esm/tools/subagent/hostArgs.mjs +207 -0
  156. package/dist/esm/tools/subagent/hostArgs.mjs.map +1 -0
  157. package/dist/esm/tools/subagent/index.mjs +2 -0
  158. package/dist/esm/tools/toolErrorContent.mjs +1 -1
  159. package/dist/types/decisions/deadline.d.ts +3 -0
  160. package/dist/types/decisions/dialect.d.ts +11 -0
  161. package/dist/types/decisions/http.d.ts +22 -0
  162. package/dist/types/decisions/index.d.ts +10 -0
  163. package/dist/types/decisions/presets.d.ts +17 -0
  164. package/dist/types/decisions/questions.d.ts +8 -0
  165. package/dist/types/decisions/structuredChat.d.ts +17 -0
  166. package/dist/types/decisions/structuredOutput.d.ts +4 -0
  167. package/dist/types/decisions/traceMarker.d.ts +2 -0
  168. package/dist/types/decisions/transport.d.ts +29 -0
  169. package/dist/types/decisions/types.d.ts +119 -0
  170. package/dist/types/graphs/Graph.d.ts +7 -3
  171. package/dist/types/index.d.ts +4 -0
  172. package/dist/types/llm/invoke.d.ts +4 -1
  173. package/dist/types/llm/providerTextBoundary.d.ts +4 -0
  174. package/dist/types/llm/providerTextChunk.d.ts +7 -0
  175. package/dist/types/llm/providerTextControls.d.ts +18 -0
  176. package/dist/types/protection/providerText.d.ts +77 -0
  177. package/dist/types/protection/providerTextInput.d.ts +3 -0
  178. package/dist/types/rerank/index.d.ts +4 -0
  179. package/dist/types/rerank/search.d.ts +7 -0
  180. package/dist/types/rerank/systemone.d.ts +13 -0
  181. package/dist/types/rerank/types.d.ts +24 -0
  182. package/dist/types/run.d.ts +2 -0
  183. package/dist/types/tools/BashExecutor.d.ts +4 -0
  184. package/dist/types/tools/CodeExecutor.d.ts +4 -0
  185. package/dist/types/tools/ReadFile.d.ts +4 -0
  186. package/dist/types/tools/SkillTool.d.ts +4 -0
  187. package/dist/types/tools/SubagentTool.d.ts +2 -0
  188. package/dist/types/tools/ToolSearch.d.ts +4 -0
  189. package/dist/types/tools/search/schema.d.ts +4 -0
  190. package/dist/types/tools/search/types.d.ts +3 -1
  191. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +6 -0
  192. package/dist/types/tools/subagent/SubagentExecutor.d.ts +22 -1
  193. package/dist/types/tools/subagent/SubagentReplay.d.ts +3 -1
  194. package/dist/types/tools/subagent/diagnostics.d.ts +56 -8
  195. package/dist/types/tools/subagent/hostArgs.d.ts +57 -0
  196. package/dist/types/tools/subagent/index.d.ts +3 -0
  197. package/dist/types/types/graph.d.ts +39 -0
  198. package/dist/types/types/run.d.ts +6 -2
  199. package/dist/types/types/tools.d.ts +2 -0
  200. package/package.json +4 -4
  201. package/src/agents/AgentContext.ts +3 -2
  202. package/src/decisions/deadline.ts +92 -0
  203. package/src/decisions/dialect.ts +200 -0
  204. package/src/decisions/http.ts +175 -0
  205. package/src/decisions/index.ts +14 -0
  206. package/src/decisions/presets.ts +111 -0
  207. package/src/decisions/questions.ts +128 -0
  208. package/src/decisions/structuredChat.ts +358 -0
  209. package/src/decisions/structuredOutput.ts +25 -0
  210. package/src/decisions/traceMarker.ts +2 -0
  211. package/src/decisions/transport.ts +335 -0
  212. package/src/decisions/types.ts +193 -0
  213. package/src/graphs/Graph.ts +57 -9
  214. package/src/index.ts +9 -0
  215. package/src/langfuseToolOutputTracing.ts +8 -1
  216. package/src/llm/invoke.ts +48 -7
  217. package/src/llm/openai/index.ts +8 -0
  218. package/src/llm/providerTextBoundary.ts +446 -0
  219. package/src/llm/providerTextChunk.ts +15 -0
  220. package/src/llm/providerTextControls.ts +173 -0
  221. package/src/llm/stream/smoother.ts +4 -2
  222. package/src/protection/providerText.ts +325 -0
  223. package/src/protection/providerTextInput.ts +21 -0
  224. package/src/rerank/index.ts +4 -0
  225. package/src/rerank/search.ts +85 -0
  226. package/src/rerank/systemone.ts +140 -0
  227. package/src/rerank/types.ts +28 -0
  228. package/src/run.ts +13 -0
  229. package/src/stream.ts +33 -7
  230. package/src/summarization/node.ts +32 -14
  231. package/src/tools/SubagentTool.ts +14 -3
  232. package/src/tools/ToolNode.ts +7 -3
  233. package/src/tools/search/search.ts +4 -4
  234. package/src/tools/search/tool.ts +27 -25
  235. package/src/tools/search/types.ts +4 -1
  236. package/src/tools/subagent/SubagentExecutionRegistry.ts +27 -2
  237. package/src/tools/subagent/SubagentExecutor.ts +323 -28
  238. package/src/tools/subagent/SubagentReplay.ts +10 -3
  239. package/src/tools/subagent/childGraphConfig.ts +12 -1
  240. package/src/tools/subagent/diagnostics.ts +180 -9
  241. package/src/tools/subagent/hostArgs.ts +413 -0
  242. package/src/tools/subagent/index.ts +20 -0
  243. package/src/types/graph.ts +44 -0
  244. package/src/types/run.ts +6 -2
  245. package/src/types/tools.ts +6 -1
@@ -0,0 +1,128 @@
1
+ import type {
2
+ BooleanCriteria,
3
+ BooleanQuestion,
4
+ ChoiceQuestion,
5
+ ScoreQuestion,
6
+ DecisionText,
7
+ DecisionQuestion,
8
+ } from './types';
9
+ import { DecisionError, isDecisionObject } from './types';
10
+
11
+ function validQuestionId(id: string): boolean {
12
+ if (id.length === 0 || id.length > 128) {
13
+ return false;
14
+ }
15
+ for (let index = 0; index < id.length; index++) {
16
+ if (id.charCodeAt(index) < 32) {
17
+ return false;
18
+ }
19
+ }
20
+ return true;
21
+ }
22
+
23
+ function validBooleanCriteria(criteria: BooleanQuestion['criteria']): boolean {
24
+ if (criteria === undefined || typeof criteria === 'string') {
25
+ return true;
26
+ }
27
+ if (!isDecisionObject(criteria)) {
28
+ return false;
29
+ }
30
+ for (const [side, description] of Object.entries(criteria)) {
31
+ if (
32
+ (side !== 'true' && side !== 'false') ||
33
+ (description !== undefined &&
34
+ typeof description !== 'string' &&
35
+ !Array.isArray(description) &&
36
+ !isDecisionObject(description))
37
+ ) {
38
+ return false;
39
+ }
40
+ }
41
+ return true;
42
+ }
43
+
44
+ export function validateDecisionQuestions(
45
+ questions: Record<string, DecisionQuestion>,
46
+ provider: string
47
+ ): Array<[string, DecisionQuestion]> {
48
+ if (!isDecisionObject(questions)) {
49
+ throw new DecisionError('bad_request', 'invalid decision model questions', {
50
+ provider,
51
+ });
52
+ }
53
+ const supplied = Object.entries(questions);
54
+ if (supplied.length === 0) {
55
+ throw new DecisionError(
56
+ 'bad_request',
57
+ 'decision model requires questions',
58
+ {
59
+ provider,
60
+ }
61
+ );
62
+ }
63
+ const entries: Array<[string, DecisionQuestion]> = [];
64
+ for (const [id, question] of supplied) {
65
+ if (
66
+ !validQuestionId(id) ||
67
+ !isDecisionObject(question) ||
68
+ !['boolean', 'choice', 'score'].includes(question.type) ||
69
+ (question.type === 'boolean' &&
70
+ !validBooleanCriteria(question.criteria)) ||
71
+ (question.type === 'choice' &&
72
+ (!isDecisionObject(question.criteria) ||
73
+ Object.keys(question.criteria).length === 0)) ||
74
+ (question.type === 'score' &&
75
+ (!Array.isArray(question.criteria) || question.criteria.length < 2))
76
+ ) {
77
+ throw new DecisionError(
78
+ 'bad_request',
79
+ 'invalid decision model question',
80
+ {
81
+ provider,
82
+ }
83
+ );
84
+ }
85
+ if (question.type === 'choice') {
86
+ entries.push([id, { ...question, criteria: { ...question.criteria } }]);
87
+ } else if (question.type === 'score') {
88
+ entries.push([id, { ...question, criteria: [...question.criteria] }]);
89
+ } else {
90
+ entries.push([
91
+ id,
92
+ {
93
+ ...question,
94
+ ...(isDecisionObject(question.criteria)
95
+ ? { criteria: { ...question.criteria } }
96
+ : {}),
97
+ },
98
+ ]);
99
+ }
100
+ }
101
+ return entries;
102
+ }
103
+
104
+ /** A yes/no question; `criteria` describes what counts as yes (a string) or both sides. */
105
+ export function booleanQuestion(
106
+ instructions: DecisionText,
107
+ criteria?: BooleanCriteria | string
108
+ ): BooleanQuestion {
109
+ return criteria == null
110
+ ? { type: 'boolean', instructions }
111
+ : { type: 'boolean', instructions, criteria };
112
+ }
113
+
114
+ /** Pick one of the named options; a `null` description means the name speaks for itself. */
115
+ export function choiceQuestion(
116
+ instructions: DecisionText,
117
+ criteria: Record<string, DecisionText | null>
118
+ ): ChoiceQuestion {
119
+ return { type: 'choice', instructions, criteria };
120
+ }
121
+
122
+ /** Rate against an ordered rubric, level 0 first. */
123
+ export function scoreQuestion(
124
+ instructions: DecisionText,
125
+ levels: DecisionText[]
126
+ ): ScoreQuestion {
127
+ return { type: 'score', instructions, criteria: levels };
128
+ }
@@ -0,0 +1,358 @@
1
+ import { HumanMessage, SystemMessage } from '@langchain/core/messages';
2
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
3
+ import type {
4
+ DecisionModel,
5
+ DecisionQuestion,
6
+ DecisionResult,
7
+ DecisionRequest,
8
+ } from './types';
9
+ import type { DecisionOutputMethod } from './structuredOutput';
10
+ import { DecisionError, isDecisionObject, readDecisionUsage } from './types';
11
+ import { withDecisionStructuredOutput } from './structuredOutput';
12
+ import { failureForStatus, retryAfterMs } from './transport';
13
+ import { validateDecisionQuestions } from './questions';
14
+ import { DECISION_PROMPT_PREFIX } from './traceMarker';
15
+ import { withDecisionDeadline } from './deadline';
16
+
17
+ const MAX_QUESTIONS = 32;
18
+ const MAX_CHOICE_OPTIONS = 128;
19
+ const DEFAULT_TIMEOUT_MS = 20_000;
20
+ const PROVIDER_ID = 'structured-chat';
21
+
22
+ export function readChatUsage(raw: unknown): DecisionResult['usage'] {
23
+ if (!isDecisionObject(raw)) {
24
+ return null;
25
+ }
26
+ const usage = readDecisionUsage(raw.usage_metadata);
27
+ const response = isDecisionObject(raw.response_metadata)
28
+ ? raw.response_metadata
29
+ : null;
30
+ const nested = isDecisionObject(response?.metadata)
31
+ ? response.metadata
32
+ : null;
33
+ const bedrock = isDecisionObject(nested?.usage)
34
+ ? nested.usage
35
+ : response?.usage;
36
+ if (
37
+ usage?.inputTokens === undefined ||
38
+ !isDecisionObject(bedrock) ||
39
+ usage.inputTokens !== bedrock.inputTokens
40
+ ) {
41
+ return usage;
42
+ }
43
+ const read = bedrock.cacheReadInputTokens;
44
+ const write = bedrock.cacheWriteInputTokens;
45
+ if (
46
+ (read != null &&
47
+ (typeof read !== 'number' || !Number.isSafeInteger(read) || read < 0)) ||
48
+ (write != null &&
49
+ (typeof write !== 'number' || !Number.isSafeInteger(write) || write < 0))
50
+ ) {
51
+ return usage;
52
+ }
53
+ const inputTokens =
54
+ usage.inputTokens +
55
+ (typeof read === 'number' ? read : 0) +
56
+ (typeof write === 'number' ? write : 0);
57
+ return Number.isSafeInteger(inputTokens) ? { ...usage, inputTokens } : usage;
58
+ }
59
+
60
+ function providerFailure(error: unknown, provider: string): DecisionError {
61
+ const details = isDecisionObject(error) ? error : null;
62
+ const metadata = isDecisionObject(details?.$metadata)
63
+ ? details.$metadata
64
+ : null;
65
+ const rawStatus =
66
+ details?.status ?? details?.statusCode ?? metadata?.httpStatusCode;
67
+ const status =
68
+ typeof rawStatus === 'number' &&
69
+ Number.isInteger(rawStatus) &&
70
+ rawStatus >= 100 &&
71
+ rawStatus <= 599
72
+ ? rawStatus
73
+ : undefined;
74
+ const failure = new DecisionError(
75
+ status == null ? 'network' : failureForStatus(status),
76
+ 'structured decision model request failed',
77
+ { provider, status }
78
+ );
79
+ const headers = details?.headers;
80
+ if (headers instanceof Headers) {
81
+ failure.retryAfterMs = retryAfterMs(headers.get('retry-after'));
82
+ }
83
+ return failure;
84
+ }
85
+
86
+ export interface StructuredChatDecisionModelOptions {
87
+ /** An already configured chat model whose chosen method enforces strict schemas. */
88
+ model: BaseChatModel;
89
+ modelId: string;
90
+ providerId?: string;
91
+ /** Verified OpenAI/Azure strict modes or Anthropic strict tools; other paths fail closed. */
92
+ method: DecisionOutputMethod;
93
+ timeoutMs?: number;
94
+ maxQuestions?: number;
95
+ onAnswered?: (label: string, ms: number) => void;
96
+ }
97
+
98
+ function decisionSchema(
99
+ entries: Array<[string, DecisionQuestion]>
100
+ ): Record<string, unknown> {
101
+ const properties: Record<string, object> = Object.create(null);
102
+ for (const [id, question] of entries) {
103
+ if (question.type === 'boolean') {
104
+ properties[id] = {
105
+ type: 'object',
106
+ properties: { decision: { type: 'boolean' } },
107
+ required: ['decision'],
108
+ additionalProperties: false,
109
+ };
110
+ continue;
111
+ }
112
+ properties[id] = {
113
+ type: 'object',
114
+ properties: {
115
+ choice: { type: 'string', enum: Object.keys(question.criteria) },
116
+ },
117
+ required: ['choice'],
118
+ additionalProperties: false,
119
+ };
120
+ }
121
+ return {
122
+ type: 'object',
123
+ properties: {
124
+ answers: {
125
+ type: 'object',
126
+ properties,
127
+ required: entries.map(([id]) => id),
128
+ additionalProperties: false,
129
+ },
130
+ },
131
+ required: ['answers'],
132
+ additionalProperties: false,
133
+ };
134
+ }
135
+
136
+ /** Do not trust even a provider-parsed response: check every required id and choice locally. */
137
+ function readDecisions(
138
+ parsed: unknown,
139
+ entries: Array<[string, DecisionQuestion]>,
140
+ provider: string
141
+ ): DecisionResult['answers'] {
142
+ const invalid = (): DecisionError =>
143
+ new DecisionError(
144
+ 'malformed_response',
145
+ 'invalid structured decision model answer',
146
+ {
147
+ provider,
148
+ }
149
+ );
150
+ if (
151
+ !isDecisionObject(parsed) ||
152
+ Object.keys(parsed).length !== 1 ||
153
+ !isDecisionObject(parsed.answers) ||
154
+ Object.keys(parsed.answers).length !== entries.length
155
+ ) {
156
+ throw invalid();
157
+ }
158
+ const answers: DecisionResult['answers'] = Object.create(null);
159
+ for (const [id, question] of entries) {
160
+ if (!Object.hasOwn(parsed.answers, id)) {
161
+ throw invalid();
162
+ }
163
+ const raw = parsed.answers[id];
164
+ if (!isDecisionObject(raw) || Object.keys(raw).length !== 1) {
165
+ throw invalid();
166
+ }
167
+ if (question.type === 'boolean') {
168
+ if (typeof raw.decision !== 'boolean') {
169
+ throw invalid();
170
+ }
171
+ answers[id] = {
172
+ type: 'boolean',
173
+ decision: raw.decision,
174
+ probability: null,
175
+ };
176
+ continue;
177
+ }
178
+ if (
179
+ question.type !== 'choice' ||
180
+ typeof raw.choice !== 'string' ||
181
+ !Object.hasOwn(question.criteria, raw.choice)
182
+ ) {
183
+ throw invalid();
184
+ }
185
+ answers[id] = {
186
+ type: 'choice',
187
+ choice: raw.choice,
188
+ confidence: null,
189
+ probabilities: null,
190
+ };
191
+ }
192
+ return answers;
193
+ }
194
+
195
+ function questionsForChat(
196
+ questions: DecisionRequest['questions'],
197
+ provider: string,
198
+ maxQuestions: number
199
+ ): Array<[string, DecisionQuestion]> {
200
+ const entries = validateDecisionQuestions(questions, provider);
201
+ let choices = 0;
202
+ for (const [, question] of entries) {
203
+ if (question.type === 'score') {
204
+ throw new DecisionError(
205
+ 'unsupported_question',
206
+ 'chat decision cannot score expected values',
207
+ { provider }
208
+ );
209
+ }
210
+ if (question.type === 'choice') {
211
+ choices += Object.keys(question.criteria).length;
212
+ }
213
+ }
214
+ if (entries.length > maxQuestions || choices > MAX_CHOICE_OPTIONS) {
215
+ throw new DecisionError(
216
+ 'unsupported_question',
217
+ 'decision model question batch too large',
218
+ { provider }
219
+ );
220
+ }
221
+ return entries;
222
+ }
223
+
224
+ /** Strict provider schema or strict tool calling, without an agent loop or fabricated probabilities. */
225
+ export function createStructuredChatDecisionModel(
226
+ options: StructuredChatDecisionModelOptions
227
+ ): DecisionModel {
228
+ const provider = options.providerId ?? PROVIDER_ID;
229
+ if (!['jsonSchema', 'functionCalling'].includes(options.method)) {
230
+ throw new DecisionError(
231
+ 'unsupported_mode',
232
+ 'unsupported structured decision model mode',
233
+ {
234
+ provider,
235
+ }
236
+ );
237
+ }
238
+ if (!options.modelId.trim()) {
239
+ throw new DecisionError(
240
+ 'bad_request',
241
+ 'decision model requires a model id',
242
+ {
243
+ provider,
244
+ }
245
+ );
246
+ }
247
+ const maxQuestions = options.maxQuestions ?? MAX_QUESTIONS;
248
+ if (
249
+ !Number.isSafeInteger(maxQuestions) ||
250
+ maxQuestions < 1 ||
251
+ maxQuestions > MAX_QUESTIONS
252
+ ) {
253
+ throw new DecisionError(
254
+ 'bad_request',
255
+ 'invalid decision model question limit',
256
+ {
257
+ provider,
258
+ }
259
+ );
260
+ }
261
+ const { model, method } = options;
262
+ const modelId = options.modelId.trim();
263
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
264
+ const onAnswered = options.onAnswered;
265
+
266
+ return {
267
+ id: provider,
268
+ model: modelId,
269
+ async decide(request: DecisionRequest): Promise<DecisionResult> {
270
+ const started = performance.now();
271
+ return withDecisionDeadline(
272
+ provider,
273
+ request.timeoutMs ?? timeoutMs,
274
+ request.signal,
275
+ async (signal, waitFor) => {
276
+ let entries: Array<[string, DecisionQuestion]>;
277
+ let input: string;
278
+ try {
279
+ entries = questionsForChat(
280
+ request.questions,
281
+ provider,
282
+ maxQuestions
283
+ );
284
+ input =
285
+ DECISION_PROMPT_PREFIX +
286
+ JSON.stringify({
287
+ state: request.state,
288
+ questions: Object.fromEntries(entries),
289
+ });
290
+ } catch (error) {
291
+ if (error instanceof DecisionError) {
292
+ throw error;
293
+ }
294
+ throw new DecisionError(
295
+ 'bad_request',
296
+ 'invalid decision model request',
297
+ {
298
+ provider,
299
+ }
300
+ );
301
+ }
302
+ let structured: ReturnType<typeof model.withStructuredOutput>;
303
+ try {
304
+ structured = withDecisionStructuredOutput(
305
+ model,
306
+ decisionSchema(entries),
307
+ method
308
+ );
309
+ } catch {
310
+ throw new DecisionError(
311
+ 'unsupported_mode',
312
+ 'model cannot enforce the requested strict mode',
313
+ {
314
+ provider,
315
+ }
316
+ );
317
+ }
318
+ await waitFor(Promise.resolve());
319
+ let output: Awaited<ReturnType<typeof structured.invoke>>;
320
+ try {
321
+ output = await waitFor(
322
+ structured.invoke(
323
+ [
324
+ new SystemMessage(
325
+ 'Classify the state using the questions. Return only the required decisions. Do not estimate probabilities, confidence, or token usage.'
326
+ ),
327
+ new HumanMessage(input),
328
+ ],
329
+ { signal }
330
+ )
331
+ );
332
+ } catch (error) {
333
+ if (error instanceof DecisionError) {
334
+ throw error;
335
+ }
336
+ throw providerFailure(error, provider);
337
+ }
338
+ const answers = readDecisions(output.parsed, entries, provider);
339
+ const result: DecisionResult = {
340
+ model: modelId,
341
+ answers,
342
+ usage: readChatUsage(output.raw),
343
+ };
344
+ await waitFor(Promise.resolve());
345
+ try {
346
+ onAnswered?.(
347
+ request.label ?? 'decide',
348
+ performance.now() - started
349
+ );
350
+ } catch {
351
+ // A callback error must not make a valid decision fail.
352
+ }
353
+ return result;
354
+ }
355
+ );
356
+ },
357
+ };
358
+ }
@@ -0,0 +1,25 @@
1
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
2
+
3
+ export type DecisionOutputMethod = 'jsonSchema' | 'functionCalling';
4
+
5
+ /** Binds only verified enforcement paths; a model profile alone is not a strict-output guarantee. */
6
+ export function withDecisionStructuredOutput(
7
+ model: BaseChatModel,
8
+ schema: Record<string, unknown>,
9
+ method: DecisionOutputMethod
10
+ ): ReturnType<BaseChatModel['withStructuredOutput']> {
11
+ const options = {
12
+ name: 'DecideQuestions',
13
+ method,
14
+ includeRaw: true as const,
15
+ };
16
+ const adapter = model._llmType();
17
+ if (
18
+ adapter === 'openai' ||
19
+ adapter === 'azure_openai' ||
20
+ (adapter === 'anthropic' && method === 'functionCalling')
21
+ ) {
22
+ return model.withStructuredOutput(schema, { ...options, strict: true });
23
+ }
24
+ throw new Error('unverified strict decision model adapter');
25
+ }
@@ -0,0 +1,2 @@
1
+ /** Marks a JSON decision prompt so trace redaction can safely inspect its nested state. */
2
+ export const DECISION_PROMPT_PREFIX = 'librechat-decision-state:';