@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,335 @@
1
+ import type { AwaitWithinDeadline } from './deadline';
2
+ import type { DecisionCredential } from './types';
3
+ import { withDecisionDeadline } from './deadline';
4
+ import { DecisionError } from './types';
5
+
6
+ const DEFAULT_TIMEOUT_MS = 4_000;
7
+ const DEFAULT_MAX_RETRIES = 2;
8
+ const MAX_RESPONSE_BYTES = 256 * 1024;
9
+ const BACKOFF_MS = [250, 750, 1_500, 3_000, 6_000];
10
+ const MAX_RETRY_AFTER_MS = 10_000;
11
+
12
+ export type DecisionFetch = (
13
+ input: string,
14
+ init: {
15
+ method: string;
16
+ headers: Record<string, string>;
17
+ body: string;
18
+ signal: AbortSignal;
19
+ redirect: 'error';
20
+ }
21
+ ) => Promise<Pick<Response, 'ok' | 'status' | 'headers' | 'body'>>;
22
+
23
+ export interface TransportOptions {
24
+ providerId: string;
25
+ apiKey?: DecisionCredential;
26
+ requiresAuth?: boolean;
27
+ /** Full URL, not a base path. */
28
+ endpoint: string;
29
+ timeoutMs?: number;
30
+ maxRetries?: number;
31
+ fetch?: DecisionFetch;
32
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
33
+ /** Called only after the response parses, with no state, answers, or credentials. */
34
+ onAnswered?: (label: string, ms: number) => void;
35
+ }
36
+
37
+ export type Transport = <T>(
38
+ prepare: () => { payload: string; parse: (body: string) => T },
39
+ signal: AbortSignal | undefined,
40
+ label: string,
41
+ timeoutOverrideMs?: number
42
+ ) => Promise<T>;
43
+
44
+ export function failureForStatus(
45
+ status: number
46
+ ): 'unauthorized' | 'rate_limited' | 'server_error' | 'bad_request' {
47
+ if (status === 401 || status === 403) {
48
+ return 'unauthorized';
49
+ }
50
+ if (status === 429) {
51
+ return 'rate_limited';
52
+ }
53
+ if (status >= 500) {
54
+ return 'server_error';
55
+ }
56
+ return 'bad_request';
57
+ }
58
+
59
+ export function retryAfterMs(header: string | null): number | undefined {
60
+ if (header == null || header === '') {
61
+ return undefined;
62
+ }
63
+ const seconds = Number(header);
64
+ if (Number.isFinite(seconds) && seconds >= 0) {
65
+ return Math.min(seconds * 1_000, MAX_RETRY_AFTER_MS);
66
+ }
67
+ const at = Date.parse(header);
68
+ return Number.isFinite(at)
69
+ ? Math.min(Math.max(at - Date.now(), 0), MAX_RETRY_AFTER_MS)
70
+ : undefined;
71
+ }
72
+
73
+ function defaultSleep(ms: number, signal?: AbortSignal): Promise<void> {
74
+ if (signal?.aborted === true) {
75
+ return Promise.resolve();
76
+ }
77
+ return new Promise((resolve) => {
78
+ const done = (): void => {
79
+ clearTimeout(timer);
80
+ signal?.removeEventListener('abort', done);
81
+ resolve();
82
+ };
83
+ const timer = setTimeout(done, ms);
84
+ signal?.addEventListener('abort', done, { once: true });
85
+ });
86
+ }
87
+
88
+ /** Stream successful bodies only, so neither errors nor oversized replies are ever buffered. */
89
+ async function readResponse(
90
+ body: ReadableStream<Uint8Array> | null,
91
+ waitFor: AwaitWithinDeadline,
92
+ provider: string
93
+ ): Promise<string> {
94
+ if (!body) {
95
+ return '';
96
+ }
97
+ const reader = body.getReader();
98
+ const decoder = new TextDecoder();
99
+ let bytes = 0;
100
+ let text = '';
101
+ try {
102
+ for (;;) {
103
+ const chunk = await waitFor(reader.read());
104
+ if (chunk.done) {
105
+ return text + decoder.decode();
106
+ }
107
+ bytes += chunk.value.byteLength;
108
+ if (bytes > MAX_RESPONSE_BYTES) {
109
+ throw new DecisionError(
110
+ 'malformed_response',
111
+ 'decision model response too large',
112
+ {
113
+ provider,
114
+ }
115
+ );
116
+ }
117
+ text += decoder.decode(chunk.value, { stream: true });
118
+ }
119
+ } catch (error) {
120
+ void reader.cancel().catch(() => {});
121
+ throw error;
122
+ } finally {
123
+ try {
124
+ reader.releaseLock();
125
+ } catch {
126
+ // A timed-out read still holds the lock until cancellation settles.
127
+ }
128
+ }
129
+ }
130
+
131
+ /** One immutable transport; every call owns its signal, deadline, and credentials. */
132
+ export function createTransport(options: TransportOptions): Transport {
133
+ const { providerId } = options;
134
+ const credential = options.apiKey;
135
+ if (
136
+ options.requiresAuth !== false &&
137
+ (credential == null ||
138
+ (typeof credential === 'string' && credential.trim() === ''))
139
+ ) {
140
+ throw new DecisionError(
141
+ 'unauthorized',
142
+ 'decision model requires an API key',
143
+ {
144
+ provider: providerId,
145
+ }
146
+ );
147
+ }
148
+ const endpoint = options.endpoint.trim();
149
+ if (!endpoint) {
150
+ throw new DecisionError(
151
+ 'bad_request',
152
+ 'decision model requires an endpoint',
153
+ {
154
+ provider: providerId,
155
+ }
156
+ );
157
+ }
158
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
159
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
160
+ const sleep = options.sleep ?? defaultSleep;
161
+ const onAnswered = options.onAnswered;
162
+ if (!Number.isSafeInteger(maxRetries) || maxRetries < 0 || maxRetries > 5) {
163
+ throw new DecisionError(
164
+ 'bad_request',
165
+ 'invalid decision model retry limit',
166
+ {
167
+ provider: providerId,
168
+ }
169
+ );
170
+ }
171
+ const fetchImpl: DecisionFetch = options.fetch ?? globalThis.fetch;
172
+ if (typeof fetchImpl !== 'function') {
173
+ throw new DecisionError('network', 'no fetch implementation available', {
174
+ provider: providerId,
175
+ });
176
+ }
177
+
178
+ async function resolveKey(
179
+ refresh: boolean,
180
+ signal: AbortSignal
181
+ ): Promise<string | null> {
182
+ if (typeof credential === 'string') {
183
+ return credential.trim() || null;
184
+ }
185
+ if (!credential) {
186
+ return null;
187
+ }
188
+ const minted: string = await credential({ refresh, signal });
189
+ const token = typeof minted === 'string' ? minted.trim() : '';
190
+ if (!token) {
191
+ throw new DecisionError(
192
+ 'unauthorized',
193
+ 'credential function returned no token',
194
+ {
195
+ provider: providerId,
196
+ }
197
+ );
198
+ }
199
+ return token;
200
+ }
201
+
202
+ return async function send(prepare, callerSignal, label, timeoutOverrideMs) {
203
+ const started = performance.now();
204
+ return withDecisionDeadline(
205
+ providerId,
206
+ timeoutOverrideMs ?? timeoutMs,
207
+ callerSignal,
208
+ async (signal, waitFor) => {
209
+ let prepared: ReturnType<typeof prepare>;
210
+ try {
211
+ prepared = prepare();
212
+ } catch (error) {
213
+ if (error instanceof DecisionError) {
214
+ throw error;
215
+ }
216
+ throw new DecisionError(
217
+ 'bad_request',
218
+ 'invalid decision model request',
219
+ {
220
+ provider: providerId,
221
+ }
222
+ );
223
+ }
224
+ const { payload, parse } = prepared;
225
+ await waitFor(Promise.resolve());
226
+ const wasAborted = (): boolean => signal.aborted;
227
+ const interrupted = (): DecisionError =>
228
+ new DecisionError(
229
+ callerSignal?.aborted === true ? 'aborted' : 'timeout',
230
+ 'decision model request interrupted',
231
+ { provider: providerId }
232
+ );
233
+ let refreshed = false;
234
+ let key: string | null | undefined;
235
+ let lastError: DecisionError | undefined;
236
+ for (let attemptNo = 0; attemptNo <= maxRetries; attemptNo++) {
237
+ if (wasAborted()) {
238
+ throw interrupted();
239
+ }
240
+ try {
241
+ if (key === undefined) {
242
+ key = await waitFor(resolveKey(refreshed, signal));
243
+ }
244
+ const response = await waitFor(
245
+ fetchImpl(endpoint, {
246
+ method: 'POST',
247
+ headers: {
248
+ ...(key !== null ? { Authorization: `Bearer ${key}` } : {}),
249
+ 'Content-Type': 'application/json',
250
+ },
251
+ body: payload,
252
+ signal,
253
+ redirect: 'error',
254
+ })
255
+ );
256
+ if (!response.ok) {
257
+ void response.body?.cancel().catch(() => {});
258
+ const error = new DecisionError(
259
+ failureForStatus(response.status),
260
+ `decision model returned HTTP ${response.status}`,
261
+ { provider: providerId, status: response.status }
262
+ );
263
+ error.retryAfterMs = retryAfterMs(
264
+ response.headers.get('retry-after')
265
+ );
266
+ throw error;
267
+ }
268
+ const body = await readResponse(response.body, waitFor, providerId);
269
+ const result = await waitFor(Promise.resolve(parse(body)));
270
+ try {
271
+ onAnswered?.(label, performance.now() - started);
272
+ } catch {
273
+ // Observability must not turn a valid answer into a retried request.
274
+ }
275
+ return result;
276
+ } catch (error) {
277
+ if (wasAborted()) {
278
+ throw interrupted();
279
+ }
280
+ if (error instanceof DecisionError) {
281
+ lastError = error;
282
+ } else {
283
+ lastError = new DecisionError(
284
+ 'network',
285
+ 'decision model request failed',
286
+ { provider: providerId }
287
+ );
288
+ }
289
+ if (
290
+ lastError.status === 401 &&
291
+ typeof credential === 'function' &&
292
+ !refreshed
293
+ ) {
294
+ refreshed = true;
295
+ key = undefined;
296
+ attemptNo -= 1;
297
+ continue;
298
+ }
299
+ if (
300
+ attemptNo === maxRetries ||
301
+ !['rate_limited', 'server_error', 'network'].includes(
302
+ lastError.failure
303
+ )
304
+ ) {
305
+ break;
306
+ }
307
+ const wait =
308
+ lastError.retryAfterMs ??
309
+ BACKOFF_MS[Math.min(attemptNo, BACKOFF_MS.length - 1)];
310
+ try {
311
+ await waitFor(sleep(wait, signal));
312
+ } catch (error) {
313
+ if (error instanceof DecisionError) {
314
+ throw error;
315
+ }
316
+ throw new DecisionError(
317
+ 'network',
318
+ 'decision model backoff failed',
319
+ {
320
+ provider: providerId,
321
+ }
322
+ );
323
+ }
324
+ }
325
+ }
326
+ throw (
327
+ lastError ??
328
+ new DecisionError('network', 'decision model request failed', {
329
+ provider: providerId,
330
+ })
331
+ );
332
+ }
333
+ );
334
+ };
335
+ }
@@ -0,0 +1,193 @@
1
+ /** Typed decisions over content, independent of the model that makes them. */
2
+
3
+ export type DecisionJson =
4
+ | string
5
+ | number
6
+ | boolean
7
+ | null
8
+ | DecisionJson[]
9
+ | { [key: string]: DecisionJson };
10
+
11
+ export type DecisionText =
12
+ | string
13
+ | DecisionJson[]
14
+ | { [key: string]: DecisionJson };
15
+
16
+ export type DecisionState = DecisionText;
17
+
18
+ export interface BooleanCriteria {
19
+ true?: DecisionText;
20
+ false?: DecisionText;
21
+ }
22
+
23
+ /** A string criterion describes the yes side of a yes/no question. */
24
+ export interface BooleanQuestion {
25
+ type: 'boolean';
26
+ instructions: DecisionText;
27
+ criteria?: BooleanCriteria | string;
28
+ }
29
+
30
+ export interface ChoiceQuestion {
31
+ type: 'choice';
32
+ instructions: DecisionText;
33
+ criteria: Record<string, DecisionText | null>;
34
+ }
35
+
36
+ /** The measured answer is an expected level, which may fall between two rubric levels. */
37
+ export interface ScoreQuestion {
38
+ type: 'score';
39
+ instructions: DecisionText;
40
+ criteria: DecisionText[];
41
+ }
42
+
43
+ export type DecisionQuestion = BooleanQuestion | ChoiceQuestion | ScoreQuestion;
44
+
45
+ /** `null` is an unmeasured decision, not a probability of zero. */
46
+ export type BooleanAnswer =
47
+ | { type: 'boolean'; probability: number; decision?: never }
48
+ | { type: 'boolean'; probability: null; decision: boolean };
49
+
50
+ export interface ChoiceAnswer {
51
+ type: 'choice';
52
+ choice: string;
53
+ /** Backend-specific concentration measure; Jev and Laya use different definitions. */
54
+ confidence: number | null;
55
+ /** `null` when no distribution was measured; never substitute an empty distribution. */
56
+ probabilities: Record<string, number> | null;
57
+ }
58
+
59
+ export interface ScoreAnswer {
60
+ type: 'score';
61
+ /** Expected value over rubric levels, not an LLM's selection of one level. */
62
+ score: number;
63
+ /** Backend-specific concentration measure; do not transfer thresholds between checkpoints. */
64
+ confidence: number | null;
65
+ probabilities: Record<string, number> | null;
66
+ }
67
+
68
+ export type DecisionAnswer = BooleanAnswer | ChoiceAnswer | ScoreAnswer;
69
+
70
+ /** Unknown counts are omitted, never inferred from an answer or replaced by zero. */
71
+ export interface DecisionUsage {
72
+ inputTokens?: number;
73
+ outputTokens?: number;
74
+ }
75
+
76
+ export interface DecisionRequest {
77
+ state: DecisionState;
78
+ questions: Record<string, DecisionQuestion>;
79
+ signal?: AbortSignal;
80
+ label?: string;
81
+ /** Overrides the provider's timeout for this request alone. */
82
+ timeoutMs?: number;
83
+ }
84
+
85
+ export interface DecisionResult {
86
+ model: string;
87
+ /** A requested answer can be omitted; callers must handle `undefined` explicitly. */
88
+ answers: Record<string, DecisionAnswer | undefined>;
89
+ usage: DecisionUsage | null;
90
+ }
91
+
92
+ export interface DecisionModel {
93
+ readonly id: string;
94
+ readonly model: string;
95
+ decide(request: DecisionRequest): Promise<DecisionResult>;
96
+ }
97
+
98
+ export type DecisionDialect = 'port' | 'systemone';
99
+
100
+ /** A bearer key, or a per-request token minter; a 401 requests one fresh token for that call. */
101
+ export type DecisionCredential =
102
+ | string
103
+ | ((options: { refresh: boolean; signal?: AbortSignal }) => Promise<string>);
104
+
105
+ export interface DecisionProviderSettings {
106
+ /** Full URL of the decision endpoint, not a base path. */
107
+ baseURL?: string;
108
+ model?: string;
109
+ dialect?: DecisionDialect;
110
+ requestKey?: string;
111
+ responseKey?: string;
112
+ timeoutMs?: number;
113
+ maxRetries?: number;
114
+ /** Auth is required by default; self-hosted Laya can be unauthenticated. */
115
+ requiresAuth?: boolean;
116
+ apiKeyEnv?: string;
117
+ }
118
+
119
+ export type DecisionFailure =
120
+ | 'timeout'
121
+ | 'aborted'
122
+ | 'rate_limited'
123
+ | 'unauthorized'
124
+ | 'bad_request'
125
+ | 'server_error'
126
+ | 'network'
127
+ | 'unsupported_question'
128
+ | 'unsupported_mode'
129
+ | 'malformed_response';
130
+
131
+ export class DecisionError extends Error {
132
+ readonly failure: DecisionFailure;
133
+ readonly provider: string;
134
+ readonly status?: number;
135
+ retryAfterMs?: number;
136
+
137
+ constructor(
138
+ failure: DecisionFailure,
139
+ message: string,
140
+ options?: { provider?: string; status?: number }
141
+ ) {
142
+ super(message);
143
+ this.name = 'DecisionError';
144
+ this.failure = failure;
145
+ this.provider = options?.provider ?? 'unknown';
146
+ this.status = options?.status;
147
+ }
148
+ }
149
+
150
+ /** A JSON boundary guard; never trust a parsed response's declared TypeScript type. */
151
+ export function isDecisionObject(
152
+ value: unknown
153
+ ): value is { [key: string]: unknown } {
154
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
155
+ }
156
+
157
+ export function readDecisionUsage(raw: unknown): DecisionUsage | null {
158
+ if (!isDecisionObject(raw)) {
159
+ return null;
160
+ }
161
+ const input = raw.input_tokens;
162
+ const output = raw.output_tokens;
163
+ const usage: DecisionUsage = {};
164
+ if (typeof input === 'number' && Number.isSafeInteger(input) && input >= 0) {
165
+ usage.inputTokens = input;
166
+ }
167
+ if (
168
+ typeof output === 'number' &&
169
+ Number.isSafeInteger(output) &&
170
+ output >= 0
171
+ ) {
172
+ usage.outputTokens = output;
173
+ }
174
+ return Object.keys(usage).length > 0 ? usage : null;
175
+ }
176
+
177
+ export function isBooleanAnswer(
178
+ answer: DecisionAnswer | undefined
179
+ ): answer is BooleanAnswer {
180
+ return answer?.type === 'boolean';
181
+ }
182
+
183
+ export function isChoiceAnswer(
184
+ answer: DecisionAnswer | undefined
185
+ ): answer is ChoiceAnswer {
186
+ return answer?.type === 'choice';
187
+ }
188
+
189
+ export function isScoreAnswer(
190
+ answer: DecisionAnswer | undefined
191
+ ): answer is ScoreAnswer {
192
+ return answer?.type === 'score';
193
+ }
@@ -131,6 +131,10 @@ import {
131
131
  resolveLangfuseConfig,
132
132
  resolveToolOutputTracingConfig,
133
133
  } from '@/langfuseConfig';
134
+ import {
135
+ ProviderTextProtectionError,
136
+ validateProviderTextProtection,
137
+ } from '@/protection/providerText';
134
138
  import {
135
139
  annotateMessagesForLLM,
136
140
  ToolOutputReferenceRegistry,
@@ -139,6 +143,10 @@ import {
139
143
  InvalidModelToolCallError,
140
144
  snapshotAcceptedModelResponse,
141
145
  } from './acceptedModelResponse';
146
+ import {
147
+ collectSubagentHostArgNames,
148
+ pickSubagentHostArgInput,
149
+ } from '@/tools/subagent/hostArgs';
142
150
  import {
143
151
  prepareProviderRequest,
144
152
  usesNativeOpenAIResponses,
@@ -1369,6 +1377,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1369
1377
  /** See {@link t.StandardGraphInput.subagentExecutionContext}. */
1370
1378
  readonly subagentExecutionContext?: t.SubagentExecutionContext;
1371
1379
  private readonly subagentContext?: t.SubagentContextAdapter;
1380
+ private readonly onSubagentResolutionFailure?: t.StandardGraphInput['onSubagentResolutionFailure'];
1372
1381
  /** See {@link t.StandardGraphInput.preemption}. */
1373
1382
  preemption?: t.StreamPreemption;
1374
1383
  /**
@@ -1377,6 +1386,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1377
1386
  * these on every streamed chunk event.
1378
1387
  */
1379
1388
  streamLimits: ResolvedStreamLimits;
1389
+ /** Default-off mandatory provider prose release. */
1390
+ providerTextProtection?: t.StandardGraphInput['providerTextProtection'];
1380
1391
  /**
1381
1392
  * Cumulative streamed argument bytes per in-flight tool call, keyed by
1382
1393
  * generation key + chunk index (see `resolveGenerationKey`). Per-run
@@ -1422,25 +1433,31 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1422
1433
  * attempt lease; spans resets on purpose. */
1423
1434
  activeStreamLimitGenerations?: Set<string>;
1424
1435
 
1425
- /** The stream-limit error behind an already-fired breaker, whether this
1436
+ /** The safety error behind an already-fired breaker, whether this
1426
1437
  * graph's own controller tripped or a parent run's breaker arrived through
1427
1438
  * the composed constructor signal (child graphs own separate controllers).
1428
1439
  * Providers can translate either abort into a generic error, and recovery
1429
1440
  * paths must not run in that state. */
1430
1441
  protected resolveTrippedBreakerReason(
1431
1442
  breakerSignal: AbortSignal = this.breakerAbort.signal
1432
- ): StreamLimitExceededError | PreparedSubagentError | undefined {
1443
+ ):
1444
+ | StreamLimitExceededError
1445
+ | PreparedSubagentError
1446
+ | ProviderTextProtectionError
1447
+ | undefined {
1433
1448
  if (
1434
1449
  breakerSignal.aborted &&
1435
1450
  (breakerSignal.reason instanceof StreamLimitExceededError ||
1436
- breakerSignal.reason instanceof PreparedSubagentError)
1451
+ breakerSignal.reason instanceof PreparedSubagentError ||
1452
+ breakerSignal.reason instanceof ProviderTextProtectionError)
1437
1453
  ) {
1438
1454
  return breakerSignal.reason;
1439
1455
  }
1440
1456
  if (
1441
1457
  this.signal?.aborted === true &&
1442
1458
  (this.signal.reason instanceof StreamLimitExceededError ||
1443
- this.signal.reason instanceof PreparedSubagentError)
1459
+ this.signal.reason instanceof PreparedSubagentError ||
1460
+ this.signal.reason instanceof ProviderTextProtectionError)
1444
1461
  ) {
1445
1462
  return this.signal.reason;
1446
1463
  }
@@ -1547,8 +1564,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1547
1564
  subagentScope,
1548
1565
  subagentExecutionContext,
1549
1566
  subagentContext,
1567
+ onSubagentResolutionFailure,
1550
1568
  preemption,
1551
1569
  streamLimits,
1570
+ providerTextProtection,
1552
1571
  toolExecution,
1553
1572
  clientDelegatedToolNames,
1554
1573
  }: t.StandardGraphInput,
@@ -1575,10 +1594,17 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1575
1594
  this.subagentScope = subagentScope === true;
1576
1595
  this.subagentExecutionContext = subagentExecutionContext;
1577
1596
  this.subagentContext = subagentContext;
1597
+ this.onSubagentResolutionFailure = onSubagentResolutionFailure;
1578
1598
  this.preemption = preemption;
1579
1599
  this.streamLimits = resolveStreamLimits(streamLimits);
1600
+ if (providerTextProtection != null)
1601
+ validateProviderTextProtection(providerTextProtection);
1602
+ this.providerTextProtection = providerTextProtection;
1580
1603
  this.toolExecution = toolExecution;
1581
- if (clientDelegatedToolNames != null && clientDelegatedToolNames.length > 0) {
1604
+ if (
1605
+ clientDelegatedToolNames != null &&
1606
+ clientDelegatedToolNames.length > 0
1607
+ ) {
1582
1608
  if (agents.length !== 1) {
1583
1609
  throw new Error('Client tool delegation requires a single-agent graph');
1584
1610
  }
@@ -4401,7 +4427,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4401
4427
  * succeeding fallback would resolve a run the public contract says
4402
4428
  * must reject. Rethrow before any recovery path.
4403
4429
  */
4404
- if (primaryError instanceof InvalidModelToolCallError) {
4430
+ if (
4431
+ primaryError instanceof InvalidModelToolCallError ||
4432
+ primaryError instanceof ProviderTextProtectionError
4433
+ ) {
4434
+ if (primaryError instanceof ProviderTextProtectionError)
4435
+ attemptBreaker.abort(primaryError);
4405
4436
  throw primaryError;
4406
4437
  }
4407
4438
  if (
@@ -4744,7 +4775,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4744
4775
  })
4745
4776
  );
4746
4777
  } catch (fallbackError) {
4747
- if (fallbackError instanceof InvalidModelToolCallError) {
4778
+ if (
4779
+ fallbackError instanceof InvalidModelToolCallError ||
4780
+ fallbackError instanceof ProviderTextProtectionError
4781
+ ) {
4782
+ if (fallbackError instanceof ProviderTextProtectionError)
4783
+ attemptBreaker.abort(fallbackError);
4748
4784
  throw fallbackError;
4749
4785
  }
4750
4786
  if (
@@ -5342,7 +5378,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
5342
5378
  usageSink: this.subagentUsageSink,
5343
5379
  taskConfig: this.subagentTasks,
5344
5380
  subagentContext: this.subagentContext,
5381
+ onResolutionFailure: this.onSubagentResolutionFailure,
5345
5382
  streamLimits: this.streamLimits,
5383
+ providerTextProtection: this.providerTextProtection,
5346
5384
  humanInTheLoop: this.humanInTheLoop,
5347
5385
  checkpointer: this.compileOptions?.checkpointer,
5348
5386
  maxDepth: effectiveSubagentDepth,
@@ -5357,6 +5395,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
5357
5395
  ): GraphFactory => snapshotChildGraphFactory(parentHandlerRegistry),
5358
5396
  });
5359
5397
  this.registerSubagentExecutor(executor);
5398
+ const hostArgNames = collectSubagentHostArgNames(executableConfigs);
5360
5399
 
5361
5400
  const subagentTool = tool(
5362
5401
  async (rawInput, config) => {
@@ -5366,6 +5405,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
5366
5405
  subagent_thread_id?: string;
5367
5406
  run_in_background?: boolean;
5368
5407
  };
5408
+ const hostArgInput = pickSubagentHostArgInput(input, hostArgNames);
5409
+ if (!hostArgInput.ok) {
5410
+ return hostArgInput.message;
5411
+ }
5369
5412
  const description =
5370
5413
  typeof input.description === 'string' &&
5371
5414
  input.description.trim().length > 0
@@ -5422,6 +5465,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
5422
5465
  parentConfigurable: config.configurable as
5423
5466
  | Record<string, unknown>
5424
5467
  | undefined,
5468
+ ...(hostArgInput.hostArgs == null
5469
+ ? {}
5470
+ : { hostArgs: hostArgInput.hostArgs }),
5425
5471
  };
5426
5472
  if (input.run_in_background === true) {
5427
5473
  return executor.executeInBackground({
@@ -5552,8 +5598,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
5552
5598
  const delegatedNames = this.clientDelegatedToolNames;
5553
5599
  if (delegatedNames != null && delegatedNames.size > 0) {
5554
5600
  const { messages } = state as t.BaseGraphState;
5555
- const last = messages[messages.length - 1] as AIMessageChunk | undefined;
5556
- const calls = last?.getType() === 'ai' ? last.tool_calls ?? [] : [];
5601
+ const last = messages[messages.length - 1] as
5602
+ | AIMessageChunk
5603
+ | undefined;
5604
+ const calls = last?.getType() === 'ai' ? (last.tool_calls ?? []) : [];
5557
5605
  if (calls.some((call) => delegatedNames.has(call.name))) {
5558
5606
  if (
5559
5607
  calls.some(