@juspay/neurolink 13.8.17 → 13.8.18

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 (192) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +1 -1
  3. package/dist/action/actionExecutor.d.ts +33 -3
  4. package/dist/action/actionExecutor.js +71 -15
  5. package/dist/action/actionInputs.js +4 -1
  6. package/dist/action/cliVersion.d.ts +25 -0
  7. package/dist/action/cliVersion.js +26 -0
  8. package/dist/action/index.d.ts +2 -1
  9. package/dist/action/index.js +2 -1
  10. package/dist/adapters/video/directorPipeline.js +46 -36
  11. package/dist/adapters/video/ffmpegAdapter.d.ts +17 -0
  12. package/dist/adapters/video/ffmpegAdapter.js +32 -5
  13. package/dist/adapters/video/frameExtractor.js +1 -1
  14. package/dist/adapters/video/videoMerger.js +5 -2
  15. package/dist/agent/arithmeticEvaluator.d.ts +32 -0
  16. package/dist/agent/arithmeticEvaluator.js +349 -0
  17. package/dist/agent/directTools.d.ts +6 -18
  18. package/dist/agent/directTools.js +17 -56
  19. package/dist/artifacts/artifactStore.d.ts +22 -1
  20. package/dist/artifacts/artifactStore.js +66 -6
  21. package/dist/auth/anthropicOAuth.d.ts +26 -1
  22. package/dist/auth/anthropicOAuth.js +81 -5
  23. package/dist/auth/secureFiles.d.ts +37 -0
  24. package/dist/auth/secureFiles.js +95 -0
  25. package/dist/auth/tokenStore.d.ts +43 -6
  26. package/dist/auth/tokenStore.js +260 -79
  27. package/dist/avatar/providers/DIDAvatar.js +49 -45
  28. package/dist/avatar/providers/ReplicateAvatar.js +33 -40
  29. package/dist/browser/neurolink.min.js +485 -558
  30. package/dist/cli/commands/auth.js +112 -70
  31. package/dist/cli/commands/proxy.d.ts +4 -0
  32. package/dist/cli/commands/proxy.js +128 -13
  33. package/dist/cli/commands/serve.d.ts +10 -1
  34. package/dist/cli/commands/serve.js +98 -17
  35. package/dist/cli/commands/server.js +17 -5
  36. package/dist/cli/index.js +146 -5
  37. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  38. package/dist/cli/utils/envManager.js +17 -3
  39. package/dist/cli/utils/serverUtils.d.ts +39 -3
  40. package/dist/cli/utils/serverUtils.js +63 -6
  41. package/dist/core/baseProvider.js +7 -1
  42. package/dist/core/modules/MessageBuilder.js +5 -0
  43. package/dist/core/modules/ToolsManager.d.ts +46 -1
  44. package/dist/core/modules/ToolsManager.js +190 -43
  45. package/dist/files/fileReferenceRegistry.d.ts +2 -23
  46. package/dist/files/fileReferenceRegistry.js +55 -5
  47. package/dist/hitl/hitlManager.js +1 -0
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.js +6 -1
  50. package/dist/mcp/auth/tokenStorage.d.ts +7 -0
  51. package/dist/mcp/auth/tokenStorage.js +23 -4
  52. package/dist/mcp/externalServerManager.d.ts +13 -3
  53. package/dist/mcp/externalServerManager.js +154 -136
  54. package/dist/mcp/index.d.ts +2 -0
  55. package/dist/mcp/index.js +3 -0
  56. package/dist/mcp/mcpClientFactory.d.ts +14 -0
  57. package/dist/mcp/mcpClientFactory.js +70 -7
  58. package/dist/mcp/projectConfigTrust.d.ts +72 -0
  59. package/dist/mcp/projectConfigTrust.js +237 -0
  60. package/dist/mcp/transportUrlPolicy.d.ts +39 -0
  61. package/dist/mcp/transportUrlPolicy.js +172 -0
  62. package/dist/music/providers/BeatovenMusic.js +35 -46
  63. package/dist/music/providers/ReplicateMusic.js +33 -40
  64. package/dist/neurolink.d.ts +16 -1
  65. package/dist/neurolink.js +64 -11
  66. package/dist/processors/archive/ArchiveProcessor.js +15 -1
  67. package/dist/processors/archive/zipEntryIndex.d.ts +22 -0
  68. package/dist/processors/archive/zipEntryIndex.js +33 -0
  69. package/dist/processors/base/BaseFileProcessor.d.ts +9 -21
  70. package/dist/processors/base/BaseFileProcessor.js +40 -111
  71. package/dist/processors/data/YamlProcessor.d.ts +13 -1
  72. package/dist/processors/data/YamlProcessor.js +93 -2
  73. package/dist/processors/document/OpenDocumentProcessor.js +7 -2
  74. package/dist/processors/document/PptxProcessor.js +12 -7
  75. package/dist/processors/media/VideoProcessor.js +93 -15
  76. package/dist/proxy/inboundAuth.d.ts +89 -0
  77. package/dist/proxy/inboundAuth.js +219 -0
  78. package/dist/proxy/tokenRefresh.d.ts +6 -0
  79. package/dist/proxy/tokenRefresh.js +13 -1
  80. package/dist/proxy/updateState.js +4 -4
  81. package/dist/rag/document/loaders.d.ts +0 -6
  82. package/dist/rag/document/loaders.js +16 -10
  83. package/dist/rag/ragIntegration.d.ts +2 -2
  84. package/dist/rag/ragIntegration.js +35 -10
  85. package/dist/rag/retrieval/vectorQueryTool.d.ts +7 -0
  86. package/dist/rag/retrieval/vectorQueryTool.js +54 -17
  87. package/dist/server/abstract/baseServerAdapter.d.ts +43 -0
  88. package/dist/server/abstract/baseServerAdapter.js +119 -6
  89. package/dist/server/adapters/expressAdapter.d.ts +3 -0
  90. package/dist/server/adapters/expressAdapter.js +78 -6
  91. package/dist/server/adapters/fastifyAdapter.d.ts +3 -0
  92. package/dist/server/adapters/fastifyAdapter.js +165 -70
  93. package/dist/server/adapters/honoAdapter.d.ts +9 -1
  94. package/dist/server/adapters/honoAdapter.js +99 -21
  95. package/dist/server/adapters/koaAdapter.d.ts +1 -0
  96. package/dist/server/adapters/koaAdapter.js +153 -74
  97. package/dist/server/index.d.ts +2 -0
  98. package/dist/server/index.js +4 -0
  99. package/dist/server/middleware/auth.js +6 -2
  100. package/dist/server/middleware/cache.js +6 -1
  101. package/dist/server/middleware/rateLimit.js +29 -4
  102. package/dist/server/routes/agentRoutes.js +24 -11
  103. package/dist/server/routes/claudeProxyRoutes.d.ts +1 -0
  104. package/dist/server/routes/claudeProxyRoutes.js +273 -84
  105. package/dist/server/routes/codexProxyRoutes.d.ts +2 -2
  106. package/dist/server/routes/codexProxyRoutes.js +125 -40
  107. package/dist/server/routes/index.d.ts +4 -0
  108. package/dist/server/routes/index.js +35 -4
  109. package/dist/server/routes/mcpRoutes.d.ts +6 -2
  110. package/dist/server/routes/mcpRoutes.js +21 -7
  111. package/dist/server/routes/memoryRoutes.js +7 -6
  112. package/dist/server/routes/toolRoutes.d.ts +12 -2
  113. package/dist/server/routes/toolRoutes.js +33 -3
  114. package/dist/server/routes/transcribeRoutes.js +60 -13
  115. package/dist/server/utils/clientAddress.d.ts +23 -0
  116. package/dist/server/utils/clientAddress.js +65 -0
  117. package/dist/server/utils/clientError.d.ts +35 -0
  118. package/dist/server/utils/clientError.js +129 -0
  119. package/dist/server/utils/pathMatch.d.ts +21 -0
  120. package/dist/server/utils/pathMatch.js +41 -0
  121. package/dist/server/utils/validation.d.ts +9 -0
  122. package/dist/server/utils/validation.js +9 -0
  123. package/dist/server/websocket/WebSocketHandler.js +22 -3
  124. package/dist/server/websocket/frameQueue.d.ts +19 -0
  125. package/dist/server/websocket/frameQueue.js +41 -3
  126. package/dist/server/websocket/transcribeWebSocket.d.ts +8 -1
  127. package/dist/server/websocket/transcribeWebSocket.js +223 -34
  128. package/dist/skills/skillStores.js +5 -0
  129. package/dist/tasks/store/fileTaskStore.d.ts +2 -0
  130. package/dist/tasks/store/fileTaskStore.js +11 -4
  131. package/dist/types/attachments.d.ts +117 -0
  132. package/dist/types/attachments.js +15 -0
  133. package/dist/types/auth.d.ts +6 -0
  134. package/dist/types/cli.d.ts +4 -0
  135. package/dist/types/config.d.ts +56 -0
  136. package/dist/types/file.d.ts +8 -0
  137. package/dist/types/hitl.d.ts +8 -0
  138. package/dist/types/index.d.ts +1 -0
  139. package/dist/types/index.js +1 -0
  140. package/dist/types/mcp.d.ts +98 -0
  141. package/dist/types/multimodal.js +0 -41
  142. package/dist/types/processor.d.ts +14 -1
  143. package/dist/types/proxy.d.ts +62 -3
  144. package/dist/types/rag.d.ts +9 -0
  145. package/dist/types/safeFetch.d.ts +40 -0
  146. package/dist/types/server.d.ts +206 -10
  147. package/dist/types/tools.d.ts +57 -0
  148. package/dist/types/transcribe.d.ts +6 -0
  149. package/dist/utils/attachmentPolicy.d.ts +89 -0
  150. package/dist/utils/attachmentPolicy.js +327 -0
  151. package/dist/utils/dotenvBootstrap.d.ts +48 -19
  152. package/dist/utils/dotenvBootstrap.js +172 -31
  153. package/dist/utils/fileDetector.js +115 -65
  154. package/dist/utils/json/coerce.js +11 -1
  155. package/dist/utils/logSanitize.d.ts +5 -0
  156. package/dist/utils/logSanitize.js +8 -0
  157. package/dist/utils/logger.js +73 -1
  158. package/dist/utils/messageBuilder.d.ts +2 -2
  159. package/dist/utils/messageBuilder.js +118 -105
  160. package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
  161. package/dist/utils/multimodalOptionsBuilder.js +2 -0
  162. package/dist/utils/redis.d.ts +12 -0
  163. package/dist/utils/redis.js +72 -0
  164. package/dist/utils/safeFetch.d.ts +22 -1
  165. package/dist/utils/safeFetch.js +132 -38
  166. package/dist/utils/safeStoreId.d.ts +19 -0
  167. package/dist/utils/safeStoreId.js +29 -0
  168. package/dist/utils/schemaConversion.d.ts +30 -3
  169. package/dist/utils/schemaConversion.js +457 -84
  170. package/dist/utils/sizeGuard.js +2 -2
  171. package/dist/utils/ssrfGuard.d.ts +3 -3
  172. package/dist/utils/ssrfGuard.js +71 -39
  173. package/dist/voice/eval/sttEvalStore.d.ts +26 -3
  174. package/dist/voice/eval/sttEvalStore.js +77 -12
  175. package/dist/voice/livekit/agentDefinition.d.ts +18 -0
  176. package/dist/voice/livekit/agentDefinition.js +22 -0
  177. package/dist/voice/livekit/realtimeMcpTools.d.ts +1 -1
  178. package/dist/voice/livekit/realtimeMcpTools.js +8 -7
  179. package/dist/voice/livekit/realtimeVoiceAgent.d.ts +2 -4
  180. package/dist/voice/livekit/realtimeVoiceAgent.js +18 -7
  181. package/dist/voice/livekit/voiceAgent.d.ts +2 -2
  182. package/dist/voice/livekit/voiceAgent.js +2 -1
  183. package/dist/voice/livekit/xaiSessionWatch.d.ts +0 -5
  184. package/dist/voice/livekit/xaiSessionWatch.js +30 -0
  185. package/dist/voice/providers/GeminiLive.js +21 -6
  186. package/dist/voice/providers/GoogleSTT.js +8 -5
  187. package/dist/voice/transcribe.d.ts +2 -2
  188. package/dist/voice/transcribe.js +34 -18
  189. package/dist/voice/whistle/audio.js +11 -5
  190. package/docs-site/static/search-index.json +71 -49
  191. package/package.json +171 -156
  192. package/scripts/observability/import-openobserve-dashboard.mjs +11 -3
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Arithmetic evaluator for the built-in `calculateMath` tool.
3
+ *
4
+ * The expression is MODEL output. It is parsed by a small recursive-descent
5
+ * parser and evaluated over numbers only: there is no `Function`, no `eval`,
6
+ * no property access and no identifier that is not on the fixed allow-list
7
+ * below. Anything the grammar does not cover is reported as an error.
8
+ *
9
+ * Grammar (precedence low → high):
10
+ *
11
+ * expression := term (("+" | "-") term)*
12
+ * term := unary (("*" | "/" | "%") unary)*
13
+ * unary := ("-" | "+") unary | power
14
+ * power := primary ("**" unary)? right-associative
15
+ * primary := number | constant | function "(" args ")" | "(" expression ")"
16
+ *
17
+ * Numbers are decimal literals (`12`, `3.5`, `.5`, `1e-3`). Functions and
18
+ * constants are the listed `Math` members, accepted with or without the
19
+ * `Math.` prefix. Unary minus binds looser than `**`, so `-2 ** 2` is `-4`,
20
+ * as in mathematics (JavaScript rejects that spelling outright).
21
+ */
22
+ /** Longest expression accepted, in characters. */
23
+ const MAX_EXPRESSION_LENGTH = 1_000;
24
+ /** Deepest nesting of parentheses and function calls accepted. */
25
+ const MAX_NESTING_DEPTH = 64;
26
+ /** Most arguments one function call may take. */
27
+ const MAX_FUNCTION_ARGUMENTS = 16;
28
+ const MATH_FUNCTIONS = Object.freeze({
29
+ abs: { fn: Math.abs, arity: 1 },
30
+ ceil: { fn: Math.ceil, arity: 1 },
31
+ floor: { fn: Math.floor, arity: 1 },
32
+ round: { fn: Math.round, arity: 1 },
33
+ trunc: { fn: Math.trunc, arity: 1 },
34
+ sign: { fn: Math.sign, arity: 1 },
35
+ sqrt: { fn: Math.sqrt, arity: 1 },
36
+ cbrt: { fn: Math.cbrt, arity: 1 },
37
+ pow: { fn: Math.pow, arity: 2 },
38
+ exp: { fn: Math.exp, arity: 1 },
39
+ log: { fn: Math.log, arity: 1 },
40
+ log2: { fn: Math.log2, arity: 1 },
41
+ log10: { fn: Math.log10, arity: 1 },
42
+ sin: { fn: Math.sin, arity: 1 },
43
+ cos: { fn: Math.cos, arity: 1 },
44
+ tan: { fn: Math.tan, arity: 1 },
45
+ asin: { fn: Math.asin, arity: 1 },
46
+ acos: { fn: Math.acos, arity: 1 },
47
+ atan: { fn: Math.atan, arity: 1 },
48
+ atan2: { fn: Math.atan2, arity: 2 },
49
+ sinh: { fn: Math.sinh, arity: 1 },
50
+ cosh: { fn: Math.cosh, arity: 1 },
51
+ tanh: { fn: Math.tanh, arity: 1 },
52
+ hypot: { fn: Math.hypot, arity: null },
53
+ min: { fn: Math.min, arity: null },
54
+ max: { fn: Math.max, arity: null },
55
+ });
56
+ const MATH_CONSTANTS = Object.freeze({
57
+ PI: Math.PI,
58
+ E: Math.E,
59
+ LN2: Math.LN2,
60
+ LN10: Math.LN10,
61
+ LOG2E: Math.LOG2E,
62
+ LOG10E: Math.LOG10E,
63
+ SQRT2: Math.SQRT2,
64
+ SQRT1_2: Math.SQRT1_2,
65
+ });
66
+ /** The names the model may use, for the error text and the tool description. */
67
+ export const ARITHMETIC_FUNCTION_NAMES = Object.freeze(Object.keys(MATH_FUNCTIONS));
68
+ export const ARITHMETIC_CONSTANT_NAMES = Object.freeze(Object.keys(MATH_CONSTANTS));
69
+ class ArithmeticSyntaxError extends Error {
70
+ constructor(message) {
71
+ super(message);
72
+ this.name = "ArithmeticSyntaxError";
73
+ }
74
+ }
75
+ const NUMBER_PATTERN = /^(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/;
76
+ const IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*/;
77
+ function tokenize(expression) {
78
+ const tokens = [];
79
+ let index = 0;
80
+ while (index < expression.length) {
81
+ const char = expression[index];
82
+ if (char === " " || char === "\t" || char === "\n" || char === "\r") {
83
+ index++;
84
+ continue;
85
+ }
86
+ const rest = expression.slice(index);
87
+ const numberMatch = NUMBER_PATTERN.exec(rest);
88
+ if (numberMatch) {
89
+ const value = Number(numberMatch[0]);
90
+ if (!Number.isFinite(value)) {
91
+ throw new ArithmeticSyntaxError(`Number literal at position ${index} is out of range`);
92
+ }
93
+ tokens.push({ kind: "number", value, at: index });
94
+ index += numberMatch[0].length;
95
+ continue;
96
+ }
97
+ const identifierMatch = IDENTIFIER_PATTERN.exec(rest);
98
+ if (identifierMatch) {
99
+ tokens.push({ kind: "identifier", name: identifierMatch[0], at: index });
100
+ index += identifierMatch[0].length;
101
+ continue;
102
+ }
103
+ if (rest.startsWith("**")) {
104
+ tokens.push({ kind: "operator", value: "**", at: index });
105
+ index += 2;
106
+ continue;
107
+ }
108
+ if (char === "+" ||
109
+ char === "-" ||
110
+ char === "*" ||
111
+ char === "/" ||
112
+ char === "%") {
113
+ tokens.push({ kind: "operator", value: char, at: index });
114
+ index++;
115
+ continue;
116
+ }
117
+ if (char === "(" || char === ")") {
118
+ tokens.push({ kind: "paren", value: char, at: index });
119
+ index++;
120
+ continue;
121
+ }
122
+ if (char === ",") {
123
+ tokens.push({ kind: "comma", at: index });
124
+ index++;
125
+ continue;
126
+ }
127
+ throw new ArithmeticSyntaxError(`Unexpected character "${char}" at position ${index}`);
128
+ }
129
+ tokens.push({ kind: "end", at: expression.length });
130
+ return tokens;
131
+ }
132
+ /** `Math.sqrt` and `sqrt` name the same function; anything else is refused. */
133
+ function resolveName(name) {
134
+ return name.startsWith("Math.") ? name.slice("Math.".length) : name;
135
+ }
136
+ class Parser {
137
+ tokens;
138
+ position = 0;
139
+ depth = 0;
140
+ constructor(tokens) {
141
+ this.tokens = tokens;
142
+ }
143
+ parse() {
144
+ const value = this.parseExpression();
145
+ const token = this.peek();
146
+ if (token.kind !== "end") {
147
+ throw new ArithmeticSyntaxError(`Unexpected input at position ${token.at}`);
148
+ }
149
+ return value;
150
+ }
151
+ peek() {
152
+ return this.tokens[this.position];
153
+ }
154
+ next() {
155
+ const token = this.tokens[this.position];
156
+ this.position++;
157
+ return token;
158
+ }
159
+ isOperator(token, value) {
160
+ return token.kind === "operator" && token.value === value;
161
+ }
162
+ enter() {
163
+ this.depth++;
164
+ if (this.depth > MAX_NESTING_DEPTH) {
165
+ throw new ArithmeticSyntaxError(`Expression nests deeper than ${MAX_NESTING_DEPTH} levels`);
166
+ }
167
+ }
168
+ leave() {
169
+ this.depth--;
170
+ }
171
+ parseExpression() {
172
+ let left = this.parseTerm();
173
+ for (;;) {
174
+ const token = this.peek();
175
+ if (this.isOperator(token, "+")) {
176
+ this.next();
177
+ left += this.parseTerm();
178
+ }
179
+ else if (this.isOperator(token, "-")) {
180
+ this.next();
181
+ left -= this.parseTerm();
182
+ }
183
+ else {
184
+ return left;
185
+ }
186
+ }
187
+ }
188
+ parseTerm() {
189
+ let left = this.parseUnary();
190
+ for (;;) {
191
+ const token = this.peek();
192
+ if (this.isOperator(token, "*")) {
193
+ this.next();
194
+ left *= this.parseUnary();
195
+ }
196
+ else if (this.isOperator(token, "/")) {
197
+ this.next();
198
+ left /= this.parseUnary();
199
+ }
200
+ else if (this.isOperator(token, "%")) {
201
+ this.next();
202
+ left %= this.parseUnary();
203
+ }
204
+ else {
205
+ return left;
206
+ }
207
+ }
208
+ }
209
+ parseUnary() {
210
+ const token = this.peek();
211
+ if (this.isOperator(token, "-")) {
212
+ this.next();
213
+ this.enter();
214
+ const value = -this.parseUnary();
215
+ this.leave();
216
+ return value;
217
+ }
218
+ if (this.isOperator(token, "+")) {
219
+ this.next();
220
+ this.enter();
221
+ const value = this.parseUnary();
222
+ this.leave();
223
+ return value;
224
+ }
225
+ return this.parsePower();
226
+ }
227
+ parsePower() {
228
+ const base = this.parsePrimary();
229
+ if (this.isOperator(this.peek(), "**")) {
230
+ this.next();
231
+ this.enter();
232
+ const exponent = this.parseUnary();
233
+ this.leave();
234
+ return base ** exponent;
235
+ }
236
+ return base;
237
+ }
238
+ parsePrimary() {
239
+ const token = this.next();
240
+ if (token.kind === "number") {
241
+ return token.value;
242
+ }
243
+ if (token.kind === "paren" && token.value === "(") {
244
+ this.enter();
245
+ const value = this.parseExpression();
246
+ this.leave();
247
+ this.expectClosingParen();
248
+ return value;
249
+ }
250
+ if (token.kind === "identifier") {
251
+ return this.parseIdentifier(token.name, token.at);
252
+ }
253
+ if (token.kind === "end") {
254
+ throw new ArithmeticSyntaxError("Expression ended unexpectedly");
255
+ }
256
+ throw new ArithmeticSyntaxError(`Unexpected token at position ${token.at}`);
257
+ }
258
+ expectClosingParen() {
259
+ const token = this.next();
260
+ if (token.kind !== "paren" || token.value !== ")") {
261
+ throw new ArithmeticSyntaxError(`Expected ")" at position ${token.at}`);
262
+ }
263
+ }
264
+ parseIdentifier(name, at) {
265
+ const resolved = resolveName(name);
266
+ const following = this.peek();
267
+ const isCall = following.kind === "paren" && following.value === "(";
268
+ if (isCall) {
269
+ const definition = Object.prototype.hasOwnProperty.call(MATH_FUNCTIONS, resolved)
270
+ ? MATH_FUNCTIONS[resolved]
271
+ : undefined;
272
+ if (!definition) {
273
+ throw new ArithmeticSyntaxError(`Unknown function "${name}" at position ${at}. Allowed: ${ARITHMETIC_FUNCTION_NAMES.join(", ")}`);
274
+ }
275
+ this.next();
276
+ this.enter();
277
+ const args = this.parseArguments();
278
+ this.leave();
279
+ if (definition.arity !== null && args.length !== definition.arity) {
280
+ throw new ArithmeticSyntaxError(`"${name}" takes ${definition.arity} argument(s), got ${args.length}`);
281
+ }
282
+ return definition.fn(...args);
283
+ }
284
+ if (Object.prototype.hasOwnProperty.call(MATH_CONSTANTS, resolved)) {
285
+ return MATH_CONSTANTS[resolved];
286
+ }
287
+ throw new ArithmeticSyntaxError(`Unknown identifier "${name}" at position ${at}. Allowed constants: ${ARITHMETIC_CONSTANT_NAMES.join(", ")}`);
288
+ }
289
+ /** Called after the opening parenthesis has been consumed. */
290
+ parseArguments() {
291
+ const args = [];
292
+ if (this.peek().kind === "paren") {
293
+ this.expectClosingParen();
294
+ return args;
295
+ }
296
+ for (;;) {
297
+ args.push(this.parseExpression());
298
+ if (args.length > MAX_FUNCTION_ARGUMENTS) {
299
+ throw new ArithmeticSyntaxError(`A function call may take at most ${MAX_FUNCTION_ARGUMENTS} arguments`);
300
+ }
301
+ const token = this.next();
302
+ if (token.kind === "comma") {
303
+ continue;
304
+ }
305
+ if (token.kind === "paren" && token.value === ")") {
306
+ return args;
307
+ }
308
+ throw new ArithmeticSyntaxError(`Expected "," or ")" at position ${token.at}`);
309
+ }
310
+ }
311
+ }
312
+ /**
313
+ * Evaluate an arithmetic expression without executing it as code.
314
+ *
315
+ * Total: every failure — unsupported syntax, an unknown name, a result that is
316
+ * not a finite number — comes back as `{ ok: false, error }`, never a throw.
317
+ */
318
+ export function evaluateArithmetic(expression) {
319
+ if (typeof expression !== "string") {
320
+ return { ok: false, error: "Expression must be a string" };
321
+ }
322
+ if (expression.trim().length === 0) {
323
+ return { ok: false, error: "Expression is empty" };
324
+ }
325
+ if (expression.length > MAX_EXPRESSION_LENGTH) {
326
+ return {
327
+ ok: false,
328
+ error: `Expression is ${expression.length} characters long; the limit is ${MAX_EXPRESSION_LENGTH}`,
329
+ };
330
+ }
331
+ try {
332
+ const value = new Parser(tokenize(expression)).parse();
333
+ if (!Number.isFinite(value)) {
334
+ return {
335
+ ok: false,
336
+ error: Number.isNaN(value)
337
+ ? "Result is not a number"
338
+ : "Result is not a finite number",
339
+ };
340
+ }
341
+ return { ok: true, value };
342
+ }
343
+ catch (error) {
344
+ return {
345
+ ok: false,
346
+ error: error instanceof Error ? error.message : "Could not parse expression",
347
+ };
348
+ }
349
+ }
@@ -104,21 +104,15 @@ export declare function createDirectAgentTools(policy: FileToolRootPolicy): {
104
104
  }, {
105
105
  success: boolean;
106
106
  error: string;
107
- expression?: undefined;
107
+ expression: string;
108
108
  result?: undefined;
109
109
  type?: undefined;
110
110
  } | {
111
111
  success: boolean;
112
112
  expression: string;
113
- result: any;
114
- type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
113
+ result: number;
114
+ type: string;
115
115
  error?: undefined;
116
- } | {
117
- success: boolean;
118
- error: string;
119
- expression: string;
120
- result?: undefined;
121
- type?: undefined;
122
116
  }>;
123
117
  writeFile: Tool;
124
118
  analyzeCSV: Tool;
@@ -232,21 +226,15 @@ export declare const directAgentTools: {
232
226
  }, {
233
227
  success: boolean;
234
228
  error: string;
235
- expression?: undefined;
229
+ expression: string;
236
230
  result?: undefined;
237
231
  type?: undefined;
238
232
  } | {
239
233
  success: boolean;
240
234
  expression: string;
241
- result: any;
242
- type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function";
235
+ result: number;
236
+ type: string;
243
237
  error?: undefined;
244
- } | {
245
- success: boolean;
246
- error: string;
247
- expression: string;
248
- result?: undefined;
249
- type?: undefined;
250
238
  }>;
251
239
  writeFile: Tool;
252
240
  analyzeCSV: Tool;
@@ -5,6 +5,7 @@ import { execFile } from "child_process";
5
5
  import { logger } from "../utils/logger.js";
6
6
  import { CSVProcessor } from "../utils/csvProcessor.js";
7
7
  import { shouldEnableBashTool } from "../utils/toolUtils.js";
8
+ import { ARITHMETIC_CONSTANT_NAMES, ARITHMETIC_FUNCTION_NAMES, evaluateArithmetic, } from "./arithmeticEvaluator.js";
8
9
  import { tool } from "../utils/tool.js";
9
10
  import { withTimeout } from "../utils/async/withTimeout.js";
10
11
  import { resolvePathWithinRoot, resolveWithinRoot, } from "../utils/pathSandbox.js";
@@ -689,11 +690,11 @@ const policyFreeTools = {
689
690
  },
690
691
  }),
691
692
  calculateMath: tool({
692
- description: "Perform mathematical calculations safely",
693
+ description: "Evaluate an arithmetic expression: numbers, + - * / % **, parentheses and Math functions such as sqrt, pow, sin, log, min, max (with or without the Math. prefix). Not a JavaScript evaluator.",
693
694
  inputSchema: z.object({
694
695
  expression: z
695
696
  .string()
696
- .describe('Mathematical expression to evaluate (e.g., "2 + 2", "Math.sqrt(16)")'),
697
+ .describe('Arithmetic expression to evaluate (e.g., "2 + 2", "Math.sqrt(16)", "pow(2, 10) % 7")'),
697
698
  precision: z
698
699
  .number()
699
700
  .optional()
@@ -701,64 +702,24 @@ const policyFreeTools = {
701
702
  .default(2),
702
703
  }),
703
704
  execute: async ({ expression, precision }) => {
704
- try {
705
- // Simple safe evaluation - only allow basic math operations
706
- const sanitizedExpression = expression.replace(/[^0-9+\-*/().\s]/g, "");
707
- if (sanitizedExpression !== expression) {
708
- // Try Math functions for more complex operations
709
- const allowedMathFunctions = [
710
- "Math.abs",
711
- "Math.ceil",
712
- "Math.floor",
713
- "Math.round",
714
- "Math.sqrt",
715
- "Math.pow",
716
- "Math.sin",
717
- "Math.cos",
718
- "Math.tan",
719
- "Math.log",
720
- "Math.exp",
721
- "Math.PI",
722
- "Math.E",
723
- ];
724
- let safeExpression = expression;
725
- for (const func of allowedMathFunctions) {
726
- safeExpression = safeExpression.replace(new RegExp(func, "g"), func);
727
- }
728
- // Remove remaining non-safe characters except Math functions
729
- const mathSafe = /^[0-9+\-*/().\s]|Math\.(abs|ceil|floor|round|sqrt|pow|sin|cos|tan|log|exp|PI|E)/g;
730
- if (!safeExpression
731
- .split("")
732
- .every((char) => mathSafe.test(char) ||
733
- char === "(" ||
734
- char === ")" ||
735
- char === "," ||
736
- char === " ")) {
737
- return {
738
- success: false,
739
- error: `Unsafe expression: Only basic math operations and Math functions are allowed`,
740
- };
741
- }
742
- }
743
- // Use Function constructor for safe evaluation
744
- const result = new Function(`'use strict'; return (${expression})`)();
745
- const roundedResult = typeof result === "number"
746
- ? Number(result.toFixed(precision))
747
- : result;
748
- return {
749
- success: true,
750
- expression,
751
- result: roundedResult,
752
- type: typeof result,
753
- };
754
- }
755
- catch (error) {
705
+ // The expression is model output. It is parsed and evaluated by
706
+ // `evaluateArithmetic` — a recursive-descent parser over numbers and a
707
+ // fixed list of Math functions — never run as JavaScript.
708
+ const evaluation = evaluateArithmetic(expression);
709
+ if (!evaluation.ok) {
756
710
  return {
757
711
  success: false,
758
- error: error instanceof Error ? error.message : String(error),
712
+ error: `${evaluation.error}. Only numbers, + - * / % ** parentheses and the Math functions ${ARITHMETIC_FUNCTION_NAMES.join(", ")} (constants ${ARITHMETIC_CONSTANT_NAMES.join(", ")}) are supported.`,
759
713
  expression,
760
714
  };
761
715
  }
716
+ const digits = Math.min(Math.max(Math.trunc(precision ?? 2), 0), 100);
717
+ return {
718
+ success: true,
719
+ expression,
720
+ result: Number(evaluation.value.toFixed(digits)),
721
+ type: "number",
722
+ };
762
723
  },
763
724
  }),
764
725
  websearchGrounding: tool({
@@ -907,7 +868,7 @@ export function createDirectAgentTools(policy) {
907
868
  */
908
869
  export function createBashTool(policy) {
909
870
  return tool({
910
- description: "Execute a bash/shell command and return stdout, stderr, and exit code. Supports full shell syntax including pipes, redirects, and variable expansion. Requires HITL confirmation when enabled. The command runs with the full privileges of the process: tool roots constrain only its starting directory (cwd), not where the command itself can reach.",
871
+ description: "Execute a bash/shell command and return stdout, stderr, and exit code. Supports full shell syntax including pipes, redirects, and variable expansion. When HITL is enabled on the NeuroLink instance, every call waits for a human to approve it before it runs. The command runs with the full privileges of the process: tool roots constrain only its starting directory (cwd), not where the command itself can reach.",
911
872
  inputSchema: z.object({
912
873
  command: z
913
874
  .string()
@@ -46,8 +46,13 @@ export declare class LocalTempArtifactStore implements ArtifactStore {
46
46
  private readonly dir;
47
47
  private readonly index;
48
48
  private readonly rehydrateFromDisk;
49
+ /** Resolves once the directory has been created and judged ours. */
50
+ private dirReady;
49
51
  /**
50
- * @param dir - Storage directory; defaults to `tmpdir()/neurolink-artifacts`
52
+ * @param dir - Storage directory; defaults to
53
+ * `tmpdir()/neurolink-artifacts-<uid>` (per unix user, so two accounts on
54
+ * one host never share a directory; plain `neurolink-artifacts` where the
55
+ * platform has no uid)
51
56
  * @param options - `rehydrateFromDisk` (default true) lets `retrieve()` and
52
57
  * `delete()` fall back to the on-disk sidecar index on an in-memory miss,
53
58
  * which makes artifacts READABLE AND DELETABLE ACROSS PROCESSES sharing
@@ -58,6 +63,17 @@ export declare class LocalTempArtifactStore implements ArtifactStore {
58
63
  constructor(dir?: string, options?: {
59
64
  rehydrateFromDisk?: boolean;
60
65
  });
66
+ /**
67
+ * Create the directory if needed and refuse one this user does not own.
68
+ *
69
+ * The OS temp directory is shared, so a pre-existing path with our name may
70
+ * have been planted by another local account: a symlink to somewhere we
71
+ * would then write into, or a directory they can read. Judge the directory
72
+ * with `lstat` (never following a link), require a real directory owned by
73
+ * this uid, and tighten its mode to 0o700 when it is ours but looser. Done
74
+ * once per instance, before the first write or read.
75
+ */
76
+ private ensureOwnedDir;
61
77
  generatePreview(payload: string): string;
62
78
  store(payload: string, meta: Omit<ArtifactMeta, "createdAt">): Promise<ArtifactRef>;
63
79
  retrieve(id: string): Promise<string | null>;
@@ -83,5 +99,10 @@ export declare class LocalTempArtifactStore implements ArtifactStore {
83
99
  * now that a miss probes `join(dir, id + ext)` the id — which arrives from
84
100
  * the model — reaches the path layer. See `isSafeArtifactId`.
85
101
  */
102
+ /**
103
+ * `rehydrate` after the directory has been judged ours: a sidecar in a
104
+ * directory another account controls is not a record to trust.
105
+ */
106
+ private rehydrateChecked;
86
107
  private rehydrate;
87
108
  }
@@ -19,7 +19,7 @@
19
19
  * @module artifacts/artifactStore
20
20
  */
21
21
  import { randomUUID } from "node:crypto";
22
- import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
22
+ import { lstat, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
23
23
  import { tmpdir } from "node:os";
24
24
  import { join } from "node:path";
25
25
  import { logger } from "../utils/logger.js";
@@ -27,6 +27,15 @@ import { generateArtifactPreview, isSafeArtifactId } from "./artifactReader.js";
27
27
  // ---------------------------------------------------------------------------
28
28
  // LocalTempArtifactStore
29
29
  // ---------------------------------------------------------------------------
30
+ /**
31
+ * `tmpdir()/neurolink-artifacts-<uid>`: a per-user directory name, so two
32
+ * accounts on one host never contend for — or read — the same path. Where the
33
+ * platform has no uid the old shared name is kept.
34
+ */
35
+ function defaultArtifactDir() {
36
+ const uid = typeof process.getuid === "function" ? process.getuid() : -1;
37
+ return join(tmpdir(), uid >= 0 ? `neurolink-artifacts-${uid}` : "neurolink-artifacts");
38
+ }
30
39
  /**
31
40
  * Sidecar written beside every payload so a process that never called
32
41
  * `store()` can still resolve the id (see the index-miss path in `retrieve`).
@@ -103,8 +112,13 @@ export class LocalTempArtifactStore {
103
112
  dir;
104
113
  index = new Map();
105
114
  rehydrateFromDisk;
115
+ /** Resolves once the directory has been created and judged ours. */
116
+ dirReady;
106
117
  /**
107
- * @param dir - Storage directory; defaults to `tmpdir()/neurolink-artifacts`
118
+ * @param dir - Storage directory; defaults to
119
+ * `tmpdir()/neurolink-artifacts-<uid>` (per unix user, so two accounts on
120
+ * one host never share a directory; plain `neurolink-artifacts` where the
121
+ * platform has no uid)
108
122
  * @param options - `rehydrateFromDisk` (default true) lets `retrieve()` and
109
123
  * `delete()` fall back to the on-disk sidecar index on an in-memory miss,
110
124
  * which makes artifacts READABLE AND DELETABLE ACROSS PROCESSES sharing
@@ -113,16 +127,48 @@ export class LocalTempArtifactStore {
113
127
  * isolation: ids not stored by this process resolve to nothing.
114
128
  */
115
129
  constructor(dir, options) {
116
- this.dir = dir ?? join(tmpdir(), "neurolink-artifacts");
130
+ this.dir = dir ?? defaultArtifactDir();
117
131
  this.rehydrateFromDisk =
118
132
  options?.rehydrateFromDisk ??
119
133
  process.env.NEUROLINK_ARTIFACT_REHYDRATE !== "false";
120
134
  }
135
+ /**
136
+ * Create the directory if needed and refuse one this user does not own.
137
+ *
138
+ * The OS temp directory is shared, so a pre-existing path with our name may
139
+ * have been planted by another local account: a symlink to somewhere we
140
+ * would then write into, or a directory they can read. Judge the directory
141
+ * with `lstat` (never following a link), require a real directory owned by
142
+ * this uid, and tighten its mode to 0o700 when it is ours but looser. Done
143
+ * once per instance, before the first write or read.
144
+ */
145
+ ensureOwnedDir() {
146
+ this.dirReady ??= (async () => {
147
+ await mkdir(this.dir, { recursive: true, mode: 0o700 });
148
+ const info = await lstat(this.dir);
149
+ if (info.isSymbolicLink() || !info.isDirectory()) {
150
+ throw new Error(`[ArtifactStore] refusing to use "${this.dir}": not a plain directory`);
151
+ }
152
+ const uid = typeof process.getuid === "function" ? process.getuid() : -1;
153
+ if (uid >= 0 && info.uid !== uid) {
154
+ throw new Error(`[ArtifactStore] refusing to use "${this.dir}": owned by uid ${info.uid}, not ${uid}`);
155
+ }
156
+ if (uid >= 0 && (info.mode & 0o077) !== 0) {
157
+ const { chmod } = await import("node:fs/promises");
158
+ await chmod(this.dir, 0o700);
159
+ }
160
+ })().catch((error) => {
161
+ // Let the next call retry rather than caching a transient failure.
162
+ this.dirReady = undefined;
163
+ throw error;
164
+ });
165
+ return this.dirReady;
166
+ }
121
167
  generatePreview(payload) {
122
168
  return generateArtifactPreview(payload);
123
169
  }
124
170
  async store(payload, meta) {
125
- await mkdir(this.dir, { recursive: true, mode: 0o700 });
171
+ await this.ensureOwnedDir();
126
172
  const id = randomUUID();
127
173
  const ext = meta.contentType === "json" ? ".json" : ".txt";
128
174
  const filePath = join(this.dir, `${id}${ext}`);
@@ -145,7 +191,7 @@ export class LocalTempArtifactStore {
145
191
  }
146
192
  async retrieve(id) {
147
193
  const entry = this.index.get(id) ??
148
- (this.rehydrateFromDisk ? await this.rehydrate(id) : undefined);
194
+ (this.rehydrateFromDisk ? await this.rehydrateChecked(id) : undefined);
149
195
  if (!entry) {
150
196
  logger.debug(`[ArtifactStore] Artifact ${id} not found on disk`);
151
197
  return null;
@@ -162,7 +208,7 @@ export class LocalTempArtifactStore {
162
208
  }
163
209
  async delete(id) {
164
210
  const entry = this.index.get(id) ??
165
- (this.rehydrateFromDisk ? await this.rehydrate(id) : undefined);
211
+ (this.rehydrateFromDisk ? await this.rehydrateChecked(id) : undefined);
166
212
  if (!entry) {
167
213
  return;
168
214
  }
@@ -222,6 +268,20 @@ export class LocalTempArtifactStore {
222
268
  * now that a miss probes `join(dir, id + ext)` the id — which arrives from
223
269
  * the model — reaches the path layer. See `isSafeArtifactId`.
224
270
  */
271
+ /**
272
+ * `rehydrate` after the directory has been judged ours: a sidecar in a
273
+ * directory another account controls is not a record to trust.
274
+ */
275
+ async rehydrateChecked(id) {
276
+ try {
277
+ await this.ensureOwnedDir();
278
+ }
279
+ catch (error) {
280
+ logger.warn(`[ArtifactStore] ${error instanceof Error ? error.message : String(error)}`);
281
+ return undefined;
282
+ }
283
+ return this.rehydrate(id);
284
+ }
225
285
  async rehydrate(id) {
226
286
  if (!isSafeArtifactId(id)) {
227
287
  logger.debug(`[ArtifactStore] Rejected unsafe artifact id "${id}"`);