nexus-ai-pro 2.2.0 → 2.4.0

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 (298) hide show
  1. package/API_STABILITY.md +225 -0
  2. package/CHANGELOG.md +325 -1
  3. package/README.md +20 -6
  4. package/SECURITY.md +140 -0
  5. package/dist/adapter-kit/contracts.d.ts +87 -0
  6. package/dist/adapter-kit/contracts.js +237 -0
  7. package/dist/adapter-kit/errors.d.ts +39 -0
  8. package/dist/adapter-kit/errors.js +52 -0
  9. package/dist/adapter-kit/index.d.ts +144 -0
  10. package/dist/adapter-kit/index.js +178 -0
  11. package/dist/adapter-kit/semver.d.ts +12 -0
  12. package/dist/adapter-kit/semver.js +55 -0
  13. package/dist/agent/capabilities.d.ts +39 -0
  14. package/dist/agent/capabilities.js +36 -0
  15. package/dist/agent/create-agent.d.ts +61 -11
  16. package/dist/agent/create-agent.js +136 -22
  17. package/dist/agent/index.d.ts +3 -2
  18. package/dist/agent/index.js +1 -1
  19. package/dist/agent/middleware/approval.d.ts +26 -0
  20. package/dist/agent/middleware/approval.js +23 -0
  21. package/dist/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
  22. package/dist/agent/{middleware.js → middleware/basic.js} +3 -5
  23. package/dist/agent/middleware/context-editor.d.ts +52 -0
  24. package/dist/agent/middleware/context-editor.js +66 -0
  25. package/dist/agent/middleware/filesystem.d.ts +44 -0
  26. package/dist/agent/middleware/filesystem.js +83 -0
  27. package/dist/agent/middleware/index.d.ts +19 -0
  28. package/dist/agent/middleware/index.js +9 -0
  29. package/dist/agent/middleware/limits.d.ts +42 -0
  30. package/dist/agent/middleware/limits.js +52 -0
  31. package/dist/agent/middleware/models.d.ts +48 -0
  32. package/dist/agent/middleware/models.js +44 -0
  33. package/dist/agent/middleware/pii.d.ts +67 -0
  34. package/dist/agent/middleware/pii.js +142 -0
  35. package/dist/agent/middleware/retry.d.ts +66 -0
  36. package/dist/agent/middleware/retry.js +59 -0
  37. package/dist/agent/middleware/shared.d.ts +41 -0
  38. package/dist/agent/middleware/shared.js +73 -0
  39. package/dist/agent/middleware/tool-selector.d.ts +64 -0
  40. package/dist/agent/middleware/tool-selector.js +106 -0
  41. package/dist/agent/permissions.d.ts +113 -0
  42. package/dist/agent/permissions.js +180 -0
  43. package/dist/agent/sandbox.d.ts +151 -0
  44. package/dist/agent/sandbox.js +257 -0
  45. package/dist/agent/tool.d.ts +8 -3
  46. package/dist/agent/tool.js +14 -3
  47. package/dist/core/secure-stream.js +4 -3
  48. package/dist/deep-agent/index.d.ts +109 -0
  49. package/dist/deep-agent/index.js +216 -0
  50. package/dist/embeddings/adapters.js +2 -1
  51. package/dist/evals/judge.js +2 -5
  52. package/dist/evaluate/cache.d.ts +0 -14
  53. package/dist/evaluate/cache.js +0 -30
  54. package/dist/evaluate/file-cache.d.ts +15 -0
  55. package/dist/evaluate/file-cache.js +30 -0
  56. package/dist/evaluate/index.d.ts +2 -1
  57. package/dist/evaluate/index.js +2 -1
  58. package/dist/evaluate/review.js +2 -2
  59. package/dist/evaluate/run.js +2 -2
  60. package/dist/evaluate/version.js +3 -3
  61. package/dist/graph/functional.d.ts +9 -0
  62. package/dist/graph/functional.js +20 -3
  63. package/dist/graph/graph.js +27 -6
  64. package/dist/graph/lint.d.ts +4 -1
  65. package/dist/graph/lint.js +35 -0
  66. package/dist/graph/node-cache.js +3 -3
  67. package/dist/hallucination/rag.d.ts +4 -1
  68. package/dist/hallucination/rag.js +6 -1
  69. package/dist/images/asset-stores.js +2 -3
  70. package/dist/images/asset-support.js +2 -2
  71. package/dist/images/assets.js +1 -2
  72. package/dist/images/evals.js +4 -4
  73. package/dist/insights/proposals.js +1 -2
  74. package/dist/insights/regressions.d.ts +26 -2
  75. package/dist/insights/regressions.js +129 -3
  76. package/dist/loaders/git.js +8 -1
  77. package/dist/loaders/web.js +7 -1
  78. package/dist/mcp/client.d.ts +14 -0
  79. package/dist/mcp/client.js +59 -1
  80. package/dist/mcp/registry.d.ts +6 -0
  81. package/dist/mcp/registry.js +7 -0
  82. package/dist/postgres/fulltext.d.ts +67 -0
  83. package/dist/postgres/fulltext.js +91 -0
  84. package/dist/prompts/file.js +28 -11
  85. package/dist/protocols/a2a.d.ts +235 -0
  86. package/dist/protocols/a2a.js +386 -0
  87. package/dist/protocols/acp.d.ts +87 -0
  88. package/dist/protocols/acp.js +322 -0
  89. package/dist/protocols/ag-ui.d.ts +120 -0
  90. package/dist/protocols/ag-ui.js +180 -0
  91. package/dist/protocols/shared.d.ts +80 -0
  92. package/dist/protocols/shared.js +49 -0
  93. package/dist/providers/anthropic.js +1 -0
  94. package/dist/providers/cohere.js +2 -2
  95. package/dist/providers/google.js +2 -11
  96. package/dist/providers/openai.js +1 -0
  97. package/dist/rag/elasticsearch.d.ts +71 -0
  98. package/dist/rag/elasticsearch.js +130 -0
  99. package/dist/rag/file-ingestion.js +15 -9
  100. package/dist/rag/pipeline.d.ts +140 -0
  101. package/dist/rag/pipeline.js +187 -0
  102. package/dist/rag/rerankers.d.ts +92 -0
  103. package/dist/rag/rerankers.js +97 -0
  104. package/dist/rag/retrievers.d.ts +12 -1
  105. package/dist/runtime/detect.d.ts +17 -0
  106. package/dist/runtime/detect.js +16 -0
  107. package/dist/runtime/index.d.ts +19 -0
  108. package/dist/runtime/index.js +7 -0
  109. package/dist/security/injection-detector.d.ts +2 -0
  110. package/dist/security/injection-detector.js +7 -1
  111. package/dist/security/input-guard.js +5 -2
  112. package/dist/security/output-guard.js +8 -4
  113. package/dist/security/pii-detector.js +22 -6
  114. package/dist/security/prompt-hardening.js +6 -2
  115. package/dist/security/semantic-injection-classifier.d.ts +2 -1
  116. package/dist/security/semantic-injection-classifier.js +31 -35
  117. package/dist/security/upload-scanner.d.ts +8 -0
  118. package/dist/security/upload-scanner.js +51 -9
  119. package/dist/server/assistant.d.ts +5 -1
  120. package/dist/server/assistant.js +4 -2
  121. package/dist/server/auth.d.ts +250 -0
  122. package/dist/server/auth.js +392 -0
  123. package/dist/server/deployments.d.ts +63 -1
  124. package/dist/server/deployments.js +75 -2
  125. package/dist/server/runs.d.ts +9 -0
  126. package/dist/server/runs.js +20 -0
  127. package/dist/server/server.js +5 -0
  128. package/dist/store/memory.d.ts +10 -0
  129. package/dist/store/memory.js +10 -0
  130. package/dist/testing/record.js +2 -2
  131. package/dist/tracing/stores.js +2 -1
  132. package/dist/tracing/tracer.js +2 -2
  133. package/dist/types/config.d.ts +20 -0
  134. package/dist/types/graph.d.ts +38 -0
  135. package/dist/types/messages.d.ts +36 -2
  136. package/dist/types/principal.d.ts +21 -0
  137. package/dist/types/principal.js +1 -0
  138. package/dist/types/server.d.ts +8 -9
  139. package/dist/utils/base64.d.ts +4 -0
  140. package/dist/utils/base64.js +21 -0
  141. package/dist/utils/checksums.d.ts +22 -0
  142. package/dist/utils/checksums.js +39 -0
  143. package/dist/utils/ids.d.ts +8 -0
  144. package/dist/utils/ids.js +26 -2
  145. package/dist/utils/sha256.d.ts +22 -0
  146. package/dist/utils/sha256.js +120 -0
  147. package/dist/utils/tenant.d.ts +11 -0
  148. package/dist/utils/tenant.js +14 -0
  149. package/dist/version.d.ts +1 -1
  150. package/dist/version.js +1 -1
  151. package/dist-cjs/adapter-kit/contracts.d.ts +87 -0
  152. package/dist-cjs/adapter-kit/contracts.js +243 -0
  153. package/dist-cjs/adapter-kit/errors.d.ts +39 -0
  154. package/dist-cjs/adapter-kit/errors.js +57 -0
  155. package/dist-cjs/adapter-kit/index.d.ts +144 -0
  156. package/dist-cjs/adapter-kit/index.js +194 -0
  157. package/dist-cjs/adapter-kit/semver.d.ts +12 -0
  158. package/dist-cjs/adapter-kit/semver.js +59 -0
  159. package/dist-cjs/agent/capabilities.d.ts +39 -0
  160. package/dist-cjs/agent/capabilities.js +41 -0
  161. package/dist-cjs/agent/create-agent.d.ts +61 -11
  162. package/dist-cjs/agent/create-agent.js +136 -22
  163. package/dist-cjs/agent/index.d.ts +3 -2
  164. package/dist-cjs/agent/index.js +4 -4
  165. package/dist-cjs/agent/middleware/approval.d.ts +26 -0
  166. package/dist-cjs/agent/middleware/approval.js +26 -0
  167. package/dist-cjs/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
  168. package/dist-cjs/agent/{middleware.js → middleware/basic.js} +3 -5
  169. package/dist-cjs/agent/middleware/context-editor.d.ts +52 -0
  170. package/dist-cjs/agent/middleware/context-editor.js +70 -0
  171. package/dist-cjs/agent/middleware/filesystem.d.ts +44 -0
  172. package/dist-cjs/agent/middleware/filesystem.js +86 -0
  173. package/dist-cjs/agent/middleware/index.d.ts +19 -0
  174. package/dist-cjs/agent/middleware/index.js +28 -0
  175. package/dist-cjs/agent/middleware/limits.d.ts +42 -0
  176. package/dist-cjs/agent/middleware/limits.js +57 -0
  177. package/dist-cjs/agent/middleware/models.d.ts +48 -0
  178. package/dist-cjs/agent/middleware/models.js +48 -0
  179. package/dist-cjs/agent/middleware/pii.d.ts +67 -0
  180. package/dist-cjs/agent/middleware/pii.js +147 -0
  181. package/dist-cjs/agent/middleware/retry.d.ts +66 -0
  182. package/dist-cjs/agent/middleware/retry.js +63 -0
  183. package/dist-cjs/agent/middleware/shared.d.ts +41 -0
  184. package/dist-cjs/agent/middleware/shared.js +83 -0
  185. package/dist-cjs/agent/middleware/tool-selector.d.ts +64 -0
  186. package/dist-cjs/agent/middleware/tool-selector.js +109 -0
  187. package/dist-cjs/agent/permissions.d.ts +113 -0
  188. package/dist-cjs/agent/permissions.js +189 -0
  189. package/dist-cjs/agent/sandbox.d.ts +151 -0
  190. package/dist-cjs/agent/sandbox.js +297 -0
  191. package/dist-cjs/agent/tool.d.ts +8 -3
  192. package/dist-cjs/agent/tool.js +14 -3
  193. package/dist-cjs/core/secure-stream.js +4 -3
  194. package/dist-cjs/deep-agent/index.d.ts +109 -0
  195. package/dist-cjs/deep-agent/index.js +219 -0
  196. package/dist-cjs/embeddings/adapters.js +2 -1
  197. package/dist-cjs/evals/judge.js +2 -5
  198. package/dist-cjs/evaluate/cache.d.ts +0 -14
  199. package/dist-cjs/evaluate/cache.js +1 -65
  200. package/dist-cjs/evaluate/file-cache.d.ts +15 -0
  201. package/dist-cjs/evaluate/file-cache.js +67 -0
  202. package/dist-cjs/evaluate/index.d.ts +2 -1
  203. package/dist-cjs/evaluate/index.js +3 -2
  204. package/dist-cjs/evaluate/review.js +2 -2
  205. package/dist-cjs/evaluate/run.js +2 -2
  206. package/dist-cjs/evaluate/version.js +3 -3
  207. package/dist-cjs/graph/functional.d.ts +9 -0
  208. package/dist-cjs/graph/functional.js +20 -3
  209. package/dist-cjs/graph/graph.js +27 -6
  210. package/dist-cjs/graph/lint.d.ts +4 -1
  211. package/dist-cjs/graph/lint.js +35 -0
  212. package/dist-cjs/graph/node-cache.js +3 -3
  213. package/dist-cjs/hallucination/rag.d.ts +4 -1
  214. package/dist-cjs/hallucination/rag.js +6 -1
  215. package/dist-cjs/images/asset-stores.js +2 -3
  216. package/dist-cjs/images/asset-support.js +2 -2
  217. package/dist-cjs/images/assets.js +1 -2
  218. package/dist-cjs/images/evals.js +4 -4
  219. package/dist-cjs/insights/proposals.js +1 -2
  220. package/dist-cjs/insights/regressions.d.ts +26 -2
  221. package/dist-cjs/insights/regressions.js +129 -3
  222. package/dist-cjs/loaders/git.js +8 -1
  223. package/dist-cjs/loaders/web.js +7 -1
  224. package/dist-cjs/mcp/client.d.ts +14 -0
  225. package/dist-cjs/mcp/client.js +60 -1
  226. package/dist-cjs/mcp/registry.d.ts +6 -0
  227. package/dist-cjs/mcp/registry.js +7 -0
  228. package/dist-cjs/postgres/fulltext.d.ts +67 -0
  229. package/dist-cjs/postgres/fulltext.js +129 -0
  230. package/dist-cjs/prompts/file.js +28 -11
  231. package/dist-cjs/protocols/a2a.d.ts +235 -0
  232. package/dist-cjs/protocols/a2a.js +393 -0
  233. package/dist-cjs/protocols/acp.d.ts +87 -0
  234. package/dist-cjs/protocols/acp.js +326 -0
  235. package/dist-cjs/protocols/ag-ui.d.ts +120 -0
  236. package/dist-cjs/protocols/ag-ui.js +186 -0
  237. package/dist-cjs/protocols/shared.d.ts +80 -0
  238. package/dist-cjs/protocols/shared.js +59 -0
  239. package/dist-cjs/providers/anthropic.js +1 -0
  240. package/dist-cjs/providers/cohere.js +2 -2
  241. package/dist-cjs/providers/google.js +3 -12
  242. package/dist-cjs/providers/openai.js +1 -0
  243. package/dist-cjs/rag/elasticsearch.d.ts +71 -0
  244. package/dist-cjs/rag/elasticsearch.js +135 -0
  245. package/dist-cjs/rag/file-ingestion.js +14 -8
  246. package/dist-cjs/rag/pipeline.d.ts +140 -0
  247. package/dist-cjs/rag/pipeline.js +191 -0
  248. package/dist-cjs/rag/rerankers.d.ts +92 -0
  249. package/dist-cjs/rag/rerankers.js +107 -0
  250. package/dist-cjs/rag/retrievers.d.ts +12 -1
  251. package/dist-cjs/runtime/detect.d.ts +17 -0
  252. package/dist-cjs/runtime/detect.js +19 -0
  253. package/dist-cjs/runtime/index.d.ts +19 -0
  254. package/dist-cjs/runtime/index.js +31 -0
  255. package/dist-cjs/security/injection-detector.d.ts +2 -0
  256. package/dist-cjs/security/injection-detector.js +7 -1
  257. package/dist-cjs/security/input-guard.js +5 -2
  258. package/dist-cjs/security/output-guard.js +8 -4
  259. package/dist-cjs/security/pii-detector.js +22 -6
  260. package/dist-cjs/security/prompt-hardening.js +6 -2
  261. package/dist-cjs/security/semantic-injection-classifier.d.ts +2 -1
  262. package/dist-cjs/security/semantic-injection-classifier.js +31 -35
  263. package/dist-cjs/security/upload-scanner.d.ts +8 -0
  264. package/dist-cjs/security/upload-scanner.js +53 -9
  265. package/dist-cjs/server/assistant.d.ts +5 -1
  266. package/dist-cjs/server/assistant.js +4 -2
  267. package/dist-cjs/server/auth.d.ts +250 -0
  268. package/dist-cjs/server/auth.js +402 -0
  269. package/dist-cjs/server/deployments.d.ts +63 -1
  270. package/dist-cjs/server/deployments.js +75 -2
  271. package/dist-cjs/server/runs.d.ts +9 -0
  272. package/dist-cjs/server/runs.js +20 -0
  273. package/dist-cjs/server/server.js +5 -0
  274. package/dist-cjs/store/memory.d.ts +10 -0
  275. package/dist-cjs/store/memory.js +10 -0
  276. package/dist-cjs/testing/record.js +2 -2
  277. package/dist-cjs/tracing/stores.js +2 -1
  278. package/dist-cjs/tracing/tracer.js +2 -2
  279. package/dist-cjs/types/config.d.ts +20 -0
  280. package/dist-cjs/types/graph.d.ts +38 -0
  281. package/dist-cjs/types/messages.d.ts +36 -2
  282. package/dist-cjs/types/principal.d.ts +21 -0
  283. package/dist-cjs/types/principal.js +2 -0
  284. package/dist-cjs/types/server.d.ts +8 -9
  285. package/dist-cjs/utils/base64.d.ts +4 -0
  286. package/dist-cjs/utils/base64.js +25 -0
  287. package/dist-cjs/utils/checksums.d.ts +22 -0
  288. package/dist-cjs/utils/checksums.js +46 -0
  289. package/dist-cjs/utils/ids.d.ts +8 -0
  290. package/dist-cjs/utils/ids.js +28 -2
  291. package/dist-cjs/utils/sha256.d.ts +22 -0
  292. package/dist-cjs/utils/sha256.js +125 -0
  293. package/dist-cjs/utils/tenant.d.ts +11 -0
  294. package/dist-cjs/utils/tenant.js +15 -0
  295. package/dist-cjs/version.d.ts +1 -1
  296. package/dist-cjs/version.js +1 -1
  297. package/llms.txt +7 -3
  298. package/package.json +193 -6
@@ -0,0 +1,180 @@
1
+ import { answerOf, asText, messageOf, protocolId, textOf, toolCallOf, } from './shared.js';
2
+ export function agUiMessages(messages) {
3
+ const converted = [];
4
+ for (const message of messages) {
5
+ if (!['user', 'assistant', 'system', 'developer', 'tool'].includes(message.role))
6
+ continue;
7
+ const content = textOf(message.content);
8
+ converted.push(messageOf(message.role, content, {
9
+ ...(message.toolCalls?.length ? { toolCalls: message.toolCalls } : {}),
10
+ ...(message.role === 'tool' && message.toolCallId ? { toolCallId: message.toolCallId } : {}),
11
+ }));
12
+ }
13
+ return converted;
14
+ }
15
+ export async function* agUiEvents(graph, run, options = {}) {
16
+ const queue = [];
17
+ let wake;
18
+ let done = false;
19
+ const push = (event) => {
20
+ queue.push({ ...event, timestamp: Date.now() });
21
+ wake?.();
22
+ };
23
+ const translate = translator(push);
24
+ push({
25
+ type: 'RUN_STARTED',
26
+ threadId: run.threadId,
27
+ runId: run.runId,
28
+ ...(run.parentRunId ? { parentRunId: run.parentRunId } : {}),
29
+ });
30
+ const runOptions = {
31
+ threadId: run.threadId,
32
+ onEvent: translate.event,
33
+ ...(options.signal ? { signal: options.signal } : {}),
34
+ ...(options.principal ? { principal: options.principal } : {}),
35
+ };
36
+ const finished = (async () => {
37
+ try {
38
+ let result;
39
+ if (run.resume?.length) {
40
+ const answer = options.answer ??
41
+ ((entry) => (entry.status === 'cancelled' ? false : (entry.payload ?? true)));
42
+ result = await graph.resumeInterruptsWith(run.threadId, Object.fromEntries(run.resume.map((entry) => [entry.interruptId, answer(entry)])), runOptions);
43
+ }
44
+ else {
45
+ const messages = agUiMessages(run.messages);
46
+ result = await graph.invoke(options.input ? options.input(run, messages) : { messages }, runOptions);
47
+ }
48
+ translate.close();
49
+ if (options.stateSnapshot !== false)
50
+ push({ type: 'STATE_SNAPSHOT', snapshot: result.state });
51
+ push(finishedEvent(run, result));
52
+ }
53
+ catch (error) {
54
+ translate.close();
55
+ push({
56
+ type: 'RUN_ERROR',
57
+ message: error instanceof Error ? error.message : String(error),
58
+ ...(error?.code ? { code: String(error.code) } : {}),
59
+ });
60
+ }
61
+ finally {
62
+ done = true;
63
+ wake?.();
64
+ }
65
+ })();
66
+ while (true) {
67
+ if (queue.length > 0) {
68
+ yield queue.shift();
69
+ continue;
70
+ }
71
+ if (done)
72
+ break;
73
+ await new Promise((resolve) => {
74
+ wake = resolve;
75
+ });
76
+ wake = undefined;
77
+ }
78
+ await finished;
79
+ }
80
+ export function agUiHandler(graph, options = {}) {
81
+ return async (request) => {
82
+ if (request.method !== 'POST')
83
+ return new Response('AG-UI runs are POSTed', { status: 405, headers: { allow: 'POST' } });
84
+ let run;
85
+ try {
86
+ run = (await request.json());
87
+ }
88
+ catch {
89
+ return new Response('The body is not a RunAgentInput', { status: 400 });
90
+ }
91
+ if (typeof run?.threadId !== 'string' || (!Array.isArray(run.messages) && !run.resume)) {
92
+ return new Response('A RunAgentInput needs a threadId and messages', { status: 400 });
93
+ }
94
+ run = { ...run, runId: run.runId || protocolId('run'), messages: run.messages ?? [] };
95
+ const caller = await options.principal?.(request);
96
+ const encoder = new TextEncoder();
97
+ const body = new ReadableStream({
98
+ async start(controller) {
99
+ const { principal: _resolve, ...rest } = options;
100
+ for await (const event of agUiEvents(graph, run, {
101
+ ...rest,
102
+ signal: request.signal,
103
+ ...(caller ? { principal: caller } : {}),
104
+ })) {
105
+ controller.enqueue(encoder.encode(encodeAgUiEvent(event)));
106
+ }
107
+ controller.close();
108
+ },
109
+ });
110
+ return new Response(body, {
111
+ headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache', connection: 'keep-alive' },
112
+ });
113
+ };
114
+ }
115
+ export function encodeAgUiEvent(event) {
116
+ return `data: ${JSON.stringify(event)}\n\n`;
117
+ }
118
+ function translator(push) {
119
+ let open;
120
+ const closeMessage = () => {
121
+ if (open)
122
+ push({ type: 'TEXT_MESSAGE_END', messageId: open });
123
+ open = undefined;
124
+ };
125
+ return {
126
+ event(event) {
127
+ if (event.type === 'task_start' && !event.namespace)
128
+ push({ type: 'STEP_STARTED', stepName: event.node });
129
+ else if (event.type === 'task_end' && !event.namespace) {
130
+ closeMessage();
131
+ push({ type: 'STEP_FINISHED', stepName: event.node });
132
+ }
133
+ else if (event.type === 'message' && event.chunk.kind !== 'reasoning' && event.chunk.content) {
134
+ const messageId = event.chunk.messageId ?? `${event.taskId}:${event.step}`;
135
+ if (open !== messageId) {
136
+ closeMessage();
137
+ open = messageId;
138
+ push({ type: 'TEXT_MESSAGE_START', messageId, role: 'assistant' });
139
+ }
140
+ push({ type: 'TEXT_MESSAGE_CONTENT', messageId, delta: event.chunk.content });
141
+ }
142
+ else if (event.type === 'tool') {
143
+ closeMessage();
144
+ const tool = event.tool;
145
+ if (tool.phase === 'start') {
146
+ push({ type: 'TOOL_CALL_START', toolCallId: tool.id, toolCallName: tool.name });
147
+ push({ type: 'TOOL_CALL_ARGS', toolCallId: tool.id, delta: asText(tool.args ?? {}) });
148
+ push({ type: 'TOOL_CALL_END', toolCallId: tool.id });
149
+ }
150
+ else if (tool.phase === 'result' || tool.phase === 'error') {
151
+ push({
152
+ type: 'TOOL_CALL_RESULT',
153
+ messageId: `${tool.id}:result`,
154
+ toolCallId: tool.id,
155
+ role: 'tool',
156
+ content: tool.phase === 'result' ? asText(tool.result) : (tool.error ?? 'The tool failed'),
157
+ });
158
+ }
159
+ }
160
+ },
161
+ close: closeMessage,
162
+ };
163
+ }
164
+ function finishedEvent(run, result) {
165
+ const base = { type: 'RUN_FINISHED', threadId: run.threadId, runId: run.runId };
166
+ if (result.status === 'awaiting_input' && result.interrupts?.length) {
167
+ return { ...base, outcome: { type: 'interrupt', interrupts: result.interrupts.map(agUiInterrupt) } };
168
+ }
169
+ return { ...base, outcome: { type: 'success' }, result: answerOf(result.state) };
170
+ }
171
+ function agUiInterrupt(interrupt) {
172
+ const call = toolCallOf(interrupt);
173
+ return {
174
+ id: interrupt.id,
175
+ reason: call ? 'tool_call' : 'input_required',
176
+ ...(interrupt.reason ? { message: interrupt.reason } : {}),
177
+ ...(call ? { toolCallId: call.id } : {}),
178
+ ...(interrupt.payload === undefined ? {} : { metadata: { payload: interrupt.payload } }),
179
+ };
180
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * What the protocol adapters share: the part of a compiled graph they drive, and the conversions
3
+ * between a protocol's messages and the graph's. Structural, so a protocol entry point never imports
4
+ * the graph runtime: an agent or any compiled graph already satisfies it.
5
+ */
6
+ import type { GraphEvent } from '../types/graph.js';
7
+ import type { Message } from '../types/messages.js';
8
+ import type { Principal } from '../types/principal.js';
9
+ /** Run options a protocol passes to a graph. */
10
+ export interface ProtocolRunOptions {
11
+ /** The thread: a protocol's conversation, context, or session id. */
12
+ threadId?: string;
13
+ /** Stops the run, as a protocol's cancel does. */
14
+ signal?: AbortSignal;
15
+ /** Receives the run's fine-grained events, which the protocol translates as they happen. */
16
+ onEvent?: (event: GraphEvent) => void;
17
+ /** Who the run is for, when the protocol's transport authenticated the caller. */
18
+ principal?: Principal;
19
+ }
20
+ /** A question a paused run is waiting on. */
21
+ export interface ProtocolInterrupt {
22
+ /** Its id, which an answer names. */
23
+ id: string;
24
+ /** The question. */
25
+ reason?: string;
26
+ /** What it is about, such as the tool call awaiting approval. */
27
+ payload?: unknown;
28
+ }
29
+ /** What a run ended with. */
30
+ export interface ProtocolResult {
31
+ /** The thread it ran on. */
32
+ threadId: string;
33
+ /** `completed`, `awaiting_input`, or another graph status. */
34
+ status: string;
35
+ /** The state it ended with. */
36
+ state: unknown;
37
+ /** Every question a paused run asked. */
38
+ interrupts?: ProtocolInterrupt[];
39
+ }
40
+ /** The part of a compiled graph the protocols drive. An agent from `createAgent()` satisfies it. */
41
+ export interface ProtocolGraph {
42
+ /** Runs an input. */
43
+ invoke(input: unknown, options?: ProtocolRunOptions): Promise<ProtocolResult>;
44
+ /** Answers a paused run's questions, by interrupt id, and continues it. */
45
+ resumeInterruptsWith(threadId: string, answers: Record<string, unknown>, options?: ProtocolRunOptions): Promise<ProtocolResult>;
46
+ }
47
+ /** A role a protocol message may carry. */
48
+ export type ProtocolRole = 'user' | 'assistant' | 'system' | 'developer' | 'tool' | 'agent';
49
+ /** The final answer a run produced: an agent's `answer`, or its state as JSON. */
50
+ export declare function answerOf(state: unknown): string;
51
+ /**
52
+ * The conversation a finished run leaves, to send whole with the next turn: an agent's transcript,
53
+ * tool calls and results included, or the fallback for a graph that keeps none.
54
+ */
55
+ export declare function transcriptOf(state: unknown, fallback: Message[]): Message[];
56
+ /** The text of a protocol message's content, for content as a string or a list of text parts. */
57
+ export declare function textOf(content: unknown): string;
58
+ /** A Nexus message from a protocol role and text. A developer message is a system message. */
59
+ export declare function messageOf(role: ProtocolRole, content: string, extra?: Partial<Message>): Message;
60
+ /** A short random id, for runs and messages the protocol creates. */
61
+ export declare function protocolId(prefix: string): string;
62
+ /** Whether an interrupt asks to approve a tool call, as an agent's approvals do. */
63
+ export declare function toolCallOf(interrupt: ProtocolInterrupt): {
64
+ id: string;
65
+ name: string;
66
+ args: unknown;
67
+ } | undefined;
68
+ /**
69
+ * Words answering an approval, as an approval: yes, ok, allow, approve, or confirm allows, and
70
+ * anything else refuses, with the words as the reason. A protocol that carries only text needs this
71
+ * so that "no" never reads as consent.
72
+ */
73
+ export declare function approvalOf(text: string): {
74
+ approved: true;
75
+ } | {
76
+ approved: false;
77
+ reason?: string;
78
+ };
79
+ /** A value as the text a protocol's text field carries. */
80
+ export declare function asText(value: unknown): string;
@@ -0,0 +1,49 @@
1
+ export function answerOf(state) {
2
+ const answer = state?.answer;
3
+ if (typeof answer === 'string')
4
+ return answer;
5
+ return state === undefined ? '' : JSON.stringify(state);
6
+ }
7
+ export function transcriptOf(state, fallback) {
8
+ const messages = state?.messages;
9
+ return Array.isArray(messages) && messages.every((item) => typeof item?.role === 'string')
10
+ ? messages
11
+ : fallback;
12
+ }
13
+ export function textOf(content) {
14
+ if (typeof content === 'string')
15
+ return content;
16
+ if (Array.isArray(content)) {
17
+ return content
18
+ .map((part) => {
19
+ const value = part;
20
+ return typeof value.text === 'string' ? value.text : '';
21
+ })
22
+ .filter(Boolean)
23
+ .join('\n');
24
+ }
25
+ return '';
26
+ }
27
+ export function messageOf(role, content, extra = {}) {
28
+ const mapped = role === 'developer' ? 'system' : role === 'agent' ? 'assistant' : role;
29
+ return { role: mapped, content, ...extra };
30
+ }
31
+ export function protocolId(prefix) {
32
+ return `${prefix}-${globalThis.crypto.randomUUID()}`;
33
+ }
34
+ export function toolCallOf(interrupt) {
35
+ const payload = interrupt.payload;
36
+ return typeof payload?.id === 'string' && typeof payload.name === 'string'
37
+ ? { id: payload.id, name: payload.name, args: payload.args }
38
+ : undefined;
39
+ }
40
+ export function approvalOf(text) {
41
+ const words = text.trim();
42
+ if (/^(y|yes|ok|okay|allow|allowed|approve|approved|confirm|confirmed|go ahead)\b/i.test(words)) {
43
+ return { approved: true };
44
+ }
45
+ return { approved: false, ...(words ? { reason: words } : {}) };
46
+ }
47
+ export function asText(value) {
48
+ return typeof value === 'string' ? value : JSON.stringify(value ?? null);
49
+ }
@@ -163,6 +163,7 @@ export class AnthropicProvider extends BaseProvider {
163
163
  this.client = new Anthropic({
164
164
  apiKey: this.config.apiKey,
165
165
  baseURL: this.config.baseUrl,
166
+ ...(this.config.fetch ? { fetch: this.config.fetch } : {}),
166
167
  });
167
168
  }
168
169
  return this.client;
@@ -14,7 +14,7 @@ export class CohereProvider extends BaseProvider {
14
14
  try {
15
15
  this.throwIfAborted(request);
16
16
  const startTime = Date.now();
17
- const response = await fetch(`${this.baseUrl()}/v2/chat`, {
17
+ const response = await (this.config.fetch ?? fetch)(`${this.baseUrl()}/v2/chat`, {
18
18
  method: 'POST',
19
19
  headers: {
20
20
  Authorization: `Bearer ${this.config.apiKey}`,
@@ -78,7 +78,7 @@ export class CohereProvider extends BaseProvider {
78
78
  }, request.signal);
79
79
  }
80
80
  async healthCheck() {
81
- const response = await fetch(`${this.baseUrl()}/v2/models`, {
81
+ const response = await (this.config.fetch ?? fetch)(`${this.baseUrl()}/v2/models`, {
82
82
  headers: { Authorization: `Bearer ${this.config.apiKey}` },
83
83
  });
84
84
  return response.ok;
@@ -1,3 +1,4 @@
1
+ import { decodeBase64 } from '../utils/base64.js';
1
2
  import { BaseProvider } from './base.js';
2
3
  import { buildMeta } from '../core/usage.js';
3
4
  import { generateRequestId } from '../utils/ids.js';
@@ -119,7 +120,7 @@ export class GoogleProvider extends BaseProvider {
119
120
  const endpoint = stream ? 'streamGenerateContent' : 'generateContent';
120
121
  const url = `${this.baseUrl()}/models/${encodeURIComponent(model)}:${endpoint}?key=${encodeURIComponent(this.config.apiKey)}${stream ? '&alt=sse' : ''}`;
121
122
  const body = JSON.stringify(this.createBody(request));
122
- const response = await fetch(url, {
123
+ const response = await (this.config.fetch ?? fetch)(url, {
123
124
  method: 'POST',
124
125
  headers: { 'content-type': 'application/json' },
125
126
  body,
@@ -310,13 +311,3 @@ export class GoogleProvider extends BaseProvider {
310
311
  return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
311
312
  }
312
313
  }
313
- function decodeBase64(data) {
314
- const buffer = globalThis.Buffer;
315
- if (buffer)
316
- return new Uint8Array(buffer.from(data, 'base64'));
317
- const binary = atob(data);
318
- const bytes = new Uint8Array(binary.length);
319
- for (let index = 0; index < binary.length; index += 1)
320
- bytes[index] = binary.charCodeAt(index);
321
- return bytes;
322
- }
@@ -25,6 +25,7 @@ export class OpenAIProvider extends BaseProvider {
25
25
  organization: this.config.organization,
26
26
  defaultHeaders: this.config.defaultHeaders,
27
27
  defaultQuery: this.config.defaultQuery,
28
+ ...(this.config.fetch ? { fetch: this.config.fetch } : {}),
28
29
  });
29
30
  }
30
31
  return this.client;
@@ -0,0 +1,71 @@
1
+ import type { RagChunk } from '../hallucination/rag.js';
2
+ import type { VectorSearchResult } from '../hallucination/retrieval.js';
3
+ import type { RetrieveOptions, SparseRetriever } from './retrievers.js';
4
+ /** Options for the Elasticsearch keyword index. */
5
+ export interface ElasticsearchKeywordIndexOptions {
6
+ /** The cluster, such as `http://localhost:9200`, an Elastic Cloud endpoint, or an OpenSearch domain. */
7
+ url: string;
8
+ /** The index chunks are kept in. */
9
+ index: string;
10
+ /** Sent as `Authorization: ApiKey <key>`. */
11
+ apiKey?: string;
12
+ /** Sent as basic authentication, for a cluster with users instead of API keys. */
13
+ username?: string;
14
+ /** The password for `username`. */
15
+ password?: string;
16
+ /** The analyzer `migrate()` gives the `content` field, such as `english`. Defaults to the cluster's standard one. */
17
+ analyzer?: string;
18
+ /**
19
+ * When a write becomes searchable. `wait_for` (the default) returns once a search would see it, as
20
+ * the vector stores do; `false` returns sooner and leaves it to the index's refresh interval.
21
+ */
22
+ refresh?: 'wait_for' | boolean;
23
+ /** Replaces the global `fetch`, for a proxy, retries, signed requests, or tests. */
24
+ fetch?: typeof globalThis.fetch;
25
+ /** Headers added to every request. */
26
+ headers?: Record<string, string>;
27
+ /** Aborts a request that takes longer, in milliseconds. Defaults to 30 seconds. */
28
+ timeoutMs?: number;
29
+ }
30
+ /** Raised when the cluster answers with an error. */
31
+ export declare class ElasticsearchError extends Error {
32
+ /** The HTTP status the cluster answered with. */
33
+ readonly status: number;
34
+ /** The cluster's response body, as text. */
35
+ readonly body?: string | undefined;
36
+ constructor(message: string,
37
+ /** The HTTP status the cluster answered with. */
38
+ status: number,
39
+ /** The cluster's response body, as text. */
40
+ body?: string | undefined);
41
+ }
42
+ /**
43
+ * Keyword search in Elasticsearch or OpenSearch, through their REST API.
44
+ *
45
+ * No client library is a dependency: the index speaks HTTP through `fetch`, as the vector stores do,
46
+ * so it runs wherever `fetch` does. Scores are the engine's own BM25, ranked in the cluster, so only
47
+ * the best matches come back. A `SparseRetriever`, so `hybridRetriever()` fuses it with any vector
48
+ * store. A metadata filter is an exact match on top-level fields, as in the vector stores.
49
+ */
50
+ export declare class ElasticsearchKeywordIndex implements SparseRetriever {
51
+ private readonly options;
52
+ private readonly base;
53
+ private readonly fetchImpl;
54
+ constructor(options: ElasticsearchKeywordIndexOptions);
55
+ /**
56
+ * Creates the index unless it exists: `content` as analyzed text, `id` and `source` as keywords,
57
+ * and every string under `metadata` as a keyword, so a filter matches it exactly. Never runs
58
+ * implicitly.
59
+ */
60
+ migrate(): Promise<void>;
61
+ /** Adds chunks, or replaces those whose id exists, in one bulk request. */
62
+ add(chunks: readonly RagChunk[]): Promise<void>;
63
+ /** Removes chunks by id. An id that is not held is ignored. */
64
+ delete(ids: readonly string[]): Promise<void>;
65
+ /** The chunks that best match a query's terms, best first, scored by the engine's BM25. */
66
+ retrieve(query: string, options?: RetrieveOptions): Promise<VectorSearchResult[]>;
67
+ /** Chunks held. */
68
+ size(): Promise<number>;
69
+ private bulk;
70
+ private request;
71
+ }
@@ -0,0 +1,130 @@
1
+ import { linkSignals } from '../utils/signals.js';
2
+ export class ElasticsearchError extends Error {
3
+ status;
4
+ body;
5
+ constructor(message, status, body) {
6
+ super(message);
7
+ this.status = status;
8
+ this.body = body;
9
+ this.name = 'ElasticsearchError';
10
+ }
11
+ }
12
+ export class ElasticsearchKeywordIndex {
13
+ options;
14
+ base;
15
+ fetchImpl;
16
+ constructor(options) {
17
+ this.options = options;
18
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(options.index)) {
19
+ throw new RangeError(`"${options.index}" is not a valid index name: use lowercase letters, digits, ".", "_", and "-"`);
20
+ }
21
+ this.base = options.url.replace(/\/+$/, '');
22
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
23
+ }
24
+ async migrate() {
25
+ const existing = await this.request('HEAD', `/${this.options.index}`, undefined, { allowNotFound: true });
26
+ if (existing !== undefined)
27
+ return;
28
+ await this.request('PUT', `/${this.options.index}`, {
29
+ mappings: {
30
+ dynamic_templates: [
31
+ {
32
+ metadata_strings: { path_match: 'metadata.*', match_mapping_type: 'string', mapping: { type: 'keyword' } },
33
+ },
34
+ ],
35
+ properties: {
36
+ id: { type: 'keyword' },
37
+ content: { type: 'text', ...(this.options.analyzer ? { analyzer: this.options.analyzer } : {}) },
38
+ source: { type: 'keyword' },
39
+ metadata: { type: 'object', dynamic: true },
40
+ },
41
+ },
42
+ });
43
+ }
44
+ async add(chunks) {
45
+ if (chunks.length === 0)
46
+ return;
47
+ const lines = chunks.flatMap((chunk) => [
48
+ { index: { _index: this.options.index, _id: chunk.id } },
49
+ { id: chunk.id, content: chunk.content, source: chunk.source ?? null, metadata: chunk.metadata ?? null },
50
+ ]);
51
+ await this.bulk(lines);
52
+ }
53
+ async delete(ids) {
54
+ if (ids.length === 0)
55
+ return;
56
+ await this.bulk(ids.map((id) => ({ delete: { _index: this.options.index, _id: id } })));
57
+ }
58
+ async retrieve(query, options = {}) {
59
+ const filter = Object.entries(options.filter ?? {});
60
+ const clauses = [];
61
+ for (const [key, value] of filter) {
62
+ if (value === null)
63
+ clauses.push({ bool: { must_not: { exists: { field: `metadata.${key}` } } } });
64
+ else if (['string', 'number', 'boolean'].includes(typeof value))
65
+ clauses.push({ term: { [`metadata.${key}`]: value } });
66
+ else
67
+ return [];
68
+ }
69
+ const result = (await this.request('POST', `/${this.options.index}/_search`, {
70
+ size: options.topK ?? 5,
71
+ query: { bool: { must: [{ match: { content: { query, operator: 'or' } } }], filter: clauses } },
72
+ _source: ['id', 'content', 'source', 'metadata'],
73
+ }, { signal: options.signal }));
74
+ return (result.hits?.hits ?? []).map((hit) => ({
75
+ id: hit._source?.id ?? hit._id,
76
+ content: hit._source?.content ?? '',
77
+ ...(hit._source?.source ? { source: hit._source.source } : {}),
78
+ ...(hit._source?.metadata ? { metadata: hit._source.metadata } : {}),
79
+ score: hit._score ?? 0,
80
+ }));
81
+ }
82
+ async size() {
83
+ const result = (await this.request('GET', `/${this.options.index}/_count`));
84
+ return result.count ?? 0;
85
+ }
86
+ async bulk(lines) {
87
+ const refresh = this.options.refresh ?? 'wait_for';
88
+ const result = (await this.request('POST', `/_bulk${refresh === false ? '' : `?refresh=${refresh === true ? 'true' : 'wait_for'}`}`, `${lines.map((line) => JSON.stringify(line)).join('\n')}\n`));
89
+ if (!result.errors)
90
+ return;
91
+ const failed = (result.items ?? [])
92
+ .map((item) => Object.entries(item)[0])
93
+ .map((entry) => (entry?.[1]?.error && !(entry[0] === 'delete' && entry[1].status === 404) ? entry[1] : undefined))
94
+ .find((outcome) => outcome !== undefined);
95
+ if (failed)
96
+ throw new ElasticsearchError(`A bulk write failed: ${failed.error?.reason ?? 'unknown reason'}`, failed.status ?? 500);
97
+ }
98
+ async request(method, path, body, options = {}) {
99
+ const timeout = AbortSignal.timeout(this.options.timeoutMs ?? 30_000);
100
+ const linked = linkSignals([timeout, options.signal]);
101
+ const ndjson = typeof body === 'string';
102
+ const authorization = this.options.apiKey
103
+ ? `ApiKey ${this.options.apiKey}`
104
+ : this.options.username !== undefined
105
+ ? `Basic ${btoa(`${this.options.username}:${this.options.password ?? ''}`)}`
106
+ : undefined;
107
+ try {
108
+ const response = await this.fetchImpl(`${this.base}${path}`, {
109
+ method,
110
+ headers: {
111
+ ...(body === undefined ? {} : { 'content-type': ndjson ? 'application/x-ndjson' : 'application/json' }),
112
+ ...(authorization ? { authorization } : {}),
113
+ ...this.options.headers,
114
+ },
115
+ ...(body === undefined ? {} : { body: ndjson ? body : JSON.stringify(body) }),
116
+ signal: linked.signal,
117
+ });
118
+ if (response.status === 404 && options.allowNotFound)
119
+ return undefined;
120
+ const text = method === 'HEAD' ? '' : await response.text();
121
+ if (!response.ok) {
122
+ throw new ElasticsearchError(`Elasticsearch ${method} ${path} failed with ${response.status}`, response.status, text);
123
+ }
124
+ return text ? JSON.parse(text) : {};
125
+ }
126
+ finally {
127
+ linked.dispose();
128
+ }
129
+ }
130
+ }
@@ -1,4 +1,4 @@
1
- import { scanUploads } from '../security/upload-scanner.js';
1
+ import { isTextual, scanUploads, textOf, } from '../security/upload-scanner.js';
2
2
  import { ingestDocuments } from './ingestion.js';
3
3
  export async function ingestFilesAfterScan(files, options = {}) {
4
4
  const scan = scanUploads(files, options.scan);
@@ -10,19 +10,30 @@ export async function ingestFilesAfterScan(files, options = {}) {
10
10
  for (const file of files) {
11
11
  const extractor = options.extractors?.find((item) => item.supports(file));
12
12
  if (extractor) {
13
+ const text = await extractor.extract(file);
14
+ if (options.scan?.scanTextContent !== false) {
15
+ const extracted = scanUploads([{ name: file.name, mimeType: 'text/plain', content: text }], {
16
+ ...(options.scan?.forbiddenPatterns ? { forbiddenPatterns: options.scan.forbiddenPatterns } : {}),
17
+ blockedExtensions: [],
18
+ });
19
+ if (!extracted.ok) {
20
+ throw new Error(`Upload scan failed on extracted text: ${extracted.findings.map((finding) => `${finding.fileName}: ${finding.message}`).join('; ')}`);
21
+ }
22
+ }
13
23
  documents.push({
14
24
  id: file.name,
15
25
  source: file.name,
16
- text: await extractor.extract(file),
26
+ text,
17
27
  metadata: { mimeType: file.mimeType, sizeBytes: file.sizeBytes },
18
28
  });
19
29
  continue;
20
30
  }
21
- if (typeof file.content === 'string' && isTextMime(file.mimeType)) {
31
+ const text = isTextual(file.mimeType) ? textOf(file.content, file.mimeType) : undefined;
32
+ if (text !== undefined) {
22
33
  documents.push({
23
34
  id: file.name,
24
35
  source: file.name,
25
- text: file.content,
36
+ text,
26
37
  metadata: { mimeType: file.mimeType, sizeBytes: file.sizeBytes },
27
38
  });
28
39
  continue;
@@ -48,8 +59,3 @@ export function createOcrExtractor(ocrImage, mimeTypes = ['image/png', 'image/jp
48
59
  extract: ocrImage,
49
60
  };
50
61
  }
51
- function isTextMime(mimeType) {
52
- if (!mimeType)
53
- return true;
54
- return (mimeType.startsWith('text/') || ['application/json', 'application/xml', 'application/markdown'].includes(mimeType));
55
- }