nexus-ai-pro 2.3.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 (178) hide show
  1. package/API_STABILITY.md +186 -0
  2. package/CHANGELOG.md +184 -1
  3. package/README.md +14 -5
  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/middleware/pii.js +2 -33
  14. package/dist/core/secure-stream.js +4 -3
  15. package/dist/embeddings/adapters.js +2 -1
  16. package/dist/evals/judge.js +2 -5
  17. package/dist/evaluate/cache.d.ts +0 -14
  18. package/dist/evaluate/cache.js +0 -30
  19. package/dist/evaluate/file-cache.d.ts +15 -0
  20. package/dist/evaluate/file-cache.js +30 -0
  21. package/dist/evaluate/index.d.ts +2 -1
  22. package/dist/evaluate/index.js +2 -1
  23. package/dist/evaluate/review.js +2 -2
  24. package/dist/evaluate/run.js +2 -2
  25. package/dist/evaluate/version.js +3 -3
  26. package/dist/graph/graph.js +2 -2
  27. package/dist/graph/node-cache.js +3 -3
  28. package/dist/hallucination/rag.d.ts +4 -1
  29. package/dist/hallucination/rag.js +6 -1
  30. package/dist/images/asset-stores.js +2 -3
  31. package/dist/images/asset-support.js +2 -2
  32. package/dist/images/assets.js +1 -2
  33. package/dist/images/evals.js +4 -4
  34. package/dist/insights/proposals.js +1 -2
  35. package/dist/insights/regressions.d.ts +26 -2
  36. package/dist/insights/regressions.js +129 -3
  37. package/dist/loaders/git.js +8 -1
  38. package/dist/loaders/web.js +7 -1
  39. package/dist/mcp/client.d.ts +14 -0
  40. package/dist/mcp/client.js +59 -1
  41. package/dist/mcp/registry.d.ts +6 -0
  42. package/dist/mcp/registry.js +7 -0
  43. package/dist/prompts/file.js +28 -11
  44. package/dist/protocols/a2a.d.ts +235 -0
  45. package/dist/protocols/a2a.js +386 -0
  46. package/dist/protocols/acp.d.ts +87 -0
  47. package/dist/protocols/acp.js +322 -0
  48. package/dist/protocols/ag-ui.d.ts +120 -0
  49. package/dist/protocols/ag-ui.js +180 -0
  50. package/dist/protocols/shared.d.ts +80 -0
  51. package/dist/protocols/shared.js +49 -0
  52. package/dist/providers/anthropic.js +1 -0
  53. package/dist/providers/cohere.js +2 -2
  54. package/dist/providers/google.js +2 -11
  55. package/dist/providers/openai.js +1 -0
  56. package/dist/rag/file-ingestion.js +15 -9
  57. package/dist/runtime/detect.d.ts +17 -0
  58. package/dist/runtime/detect.js +16 -0
  59. package/dist/runtime/index.d.ts +19 -0
  60. package/dist/runtime/index.js +7 -0
  61. package/dist/security/injection-detector.d.ts +2 -0
  62. package/dist/security/injection-detector.js +7 -1
  63. package/dist/security/input-guard.js +5 -2
  64. package/dist/security/output-guard.js +8 -4
  65. package/dist/security/pii-detector.js +22 -6
  66. package/dist/security/prompt-hardening.js +6 -2
  67. package/dist/security/semantic-injection-classifier.d.ts +2 -1
  68. package/dist/security/semantic-injection-classifier.js +31 -35
  69. package/dist/security/upload-scanner.d.ts +8 -0
  70. package/dist/security/upload-scanner.js +51 -9
  71. package/dist/server/deployments.d.ts +63 -1
  72. package/dist/server/deployments.js +75 -2
  73. package/dist/server/runs.d.ts +9 -0
  74. package/dist/server/runs.js +20 -0
  75. package/dist/server/server.js +5 -0
  76. package/dist/testing/record.js +2 -2
  77. package/dist/tracing/stores.js +2 -1
  78. package/dist/tracing/tracer.js +2 -2
  79. package/dist/types/config.d.ts +20 -0
  80. package/dist/types/server.d.ts +6 -0
  81. package/dist/utils/base64.d.ts +4 -0
  82. package/dist/utils/base64.js +21 -0
  83. package/dist/utils/checksums.d.ts +22 -0
  84. package/dist/utils/checksums.js +39 -0
  85. package/dist/utils/ids.d.ts +8 -0
  86. package/dist/utils/ids.js +26 -2
  87. package/dist/utils/sha256.d.ts +22 -0
  88. package/dist/utils/sha256.js +120 -0
  89. package/dist/version.d.ts +1 -1
  90. package/dist/version.js +1 -1
  91. package/dist-cjs/adapter-kit/contracts.d.ts +87 -0
  92. package/dist-cjs/adapter-kit/contracts.js +243 -0
  93. package/dist-cjs/adapter-kit/errors.d.ts +39 -0
  94. package/dist-cjs/adapter-kit/errors.js +57 -0
  95. package/dist-cjs/adapter-kit/index.d.ts +144 -0
  96. package/dist-cjs/adapter-kit/index.js +194 -0
  97. package/dist-cjs/adapter-kit/semver.d.ts +12 -0
  98. package/dist-cjs/adapter-kit/semver.js +59 -0
  99. package/dist-cjs/agent/middleware/pii.js +4 -35
  100. package/dist-cjs/core/secure-stream.js +4 -3
  101. package/dist-cjs/embeddings/adapters.js +2 -1
  102. package/dist-cjs/evals/judge.js +2 -5
  103. package/dist-cjs/evaluate/cache.d.ts +0 -14
  104. package/dist-cjs/evaluate/cache.js +1 -65
  105. package/dist-cjs/evaluate/file-cache.d.ts +15 -0
  106. package/dist-cjs/evaluate/file-cache.js +67 -0
  107. package/dist-cjs/evaluate/index.d.ts +2 -1
  108. package/dist-cjs/evaluate/index.js +3 -2
  109. package/dist-cjs/evaluate/review.js +2 -2
  110. package/dist-cjs/evaluate/run.js +2 -2
  111. package/dist-cjs/evaluate/version.js +3 -3
  112. package/dist-cjs/graph/graph.js +2 -2
  113. package/dist-cjs/graph/node-cache.js +3 -3
  114. package/dist-cjs/hallucination/rag.d.ts +4 -1
  115. package/dist-cjs/hallucination/rag.js +6 -1
  116. package/dist-cjs/images/asset-stores.js +2 -3
  117. package/dist-cjs/images/asset-support.js +2 -2
  118. package/dist-cjs/images/assets.js +1 -2
  119. package/dist-cjs/images/evals.js +4 -4
  120. package/dist-cjs/insights/proposals.js +1 -2
  121. package/dist-cjs/insights/regressions.d.ts +26 -2
  122. package/dist-cjs/insights/regressions.js +129 -3
  123. package/dist-cjs/loaders/git.js +8 -1
  124. package/dist-cjs/loaders/web.js +7 -1
  125. package/dist-cjs/mcp/client.d.ts +14 -0
  126. package/dist-cjs/mcp/client.js +60 -1
  127. package/dist-cjs/mcp/registry.d.ts +6 -0
  128. package/dist-cjs/mcp/registry.js +7 -0
  129. package/dist-cjs/prompts/file.js +28 -11
  130. package/dist-cjs/protocols/a2a.d.ts +235 -0
  131. package/dist-cjs/protocols/a2a.js +393 -0
  132. package/dist-cjs/protocols/acp.d.ts +87 -0
  133. package/dist-cjs/protocols/acp.js +326 -0
  134. package/dist-cjs/protocols/ag-ui.d.ts +120 -0
  135. package/dist-cjs/protocols/ag-ui.js +186 -0
  136. package/dist-cjs/protocols/shared.d.ts +80 -0
  137. package/dist-cjs/protocols/shared.js +59 -0
  138. package/dist-cjs/providers/anthropic.js +1 -0
  139. package/dist-cjs/providers/cohere.js +2 -2
  140. package/dist-cjs/providers/google.js +3 -12
  141. package/dist-cjs/providers/openai.js +1 -0
  142. package/dist-cjs/rag/file-ingestion.js +14 -8
  143. package/dist-cjs/runtime/detect.d.ts +17 -0
  144. package/dist-cjs/runtime/detect.js +19 -0
  145. package/dist-cjs/runtime/index.d.ts +19 -0
  146. package/dist-cjs/runtime/index.js +31 -0
  147. package/dist-cjs/security/injection-detector.d.ts +2 -0
  148. package/dist-cjs/security/injection-detector.js +7 -1
  149. package/dist-cjs/security/input-guard.js +5 -2
  150. package/dist-cjs/security/output-guard.js +8 -4
  151. package/dist-cjs/security/pii-detector.js +22 -6
  152. package/dist-cjs/security/prompt-hardening.js +6 -2
  153. package/dist-cjs/security/semantic-injection-classifier.d.ts +2 -1
  154. package/dist-cjs/security/semantic-injection-classifier.js +31 -35
  155. package/dist-cjs/security/upload-scanner.d.ts +8 -0
  156. package/dist-cjs/security/upload-scanner.js +53 -9
  157. package/dist-cjs/server/deployments.d.ts +63 -1
  158. package/dist-cjs/server/deployments.js +75 -2
  159. package/dist-cjs/server/runs.d.ts +9 -0
  160. package/dist-cjs/server/runs.js +20 -0
  161. package/dist-cjs/server/server.js +5 -0
  162. package/dist-cjs/testing/record.js +2 -2
  163. package/dist-cjs/tracing/stores.js +2 -1
  164. package/dist-cjs/tracing/tracer.js +2 -2
  165. package/dist-cjs/types/config.d.ts +20 -0
  166. package/dist-cjs/types/server.d.ts +6 -0
  167. package/dist-cjs/utils/base64.d.ts +4 -0
  168. package/dist-cjs/utils/base64.js +25 -0
  169. package/dist-cjs/utils/checksums.d.ts +22 -0
  170. package/dist-cjs/utils/checksums.js +46 -0
  171. package/dist-cjs/utils/ids.d.ts +8 -0
  172. package/dist-cjs/utils/ids.js +28 -2
  173. package/dist-cjs/utils/sha256.d.ts +22 -0
  174. package/dist-cjs/utils/sha256.js +125 -0
  175. package/dist-cjs/version.d.ts +1 -1
  176. package/dist-cjs/version.js +1 -1
  177. package/llms.txt +5 -2
  178. package/package.json +76 -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;
@@ -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
- }
@@ -0,0 +1,17 @@
1
+ /** A JavaScript runtime this package recognizes. */
2
+ export type RuntimeName = 'node' | 'deno' | 'bun' | 'workerd' | 'edge-light' | 'browser' | 'unknown';
3
+ /** Which runtime the code is running on. */
4
+ export interface RuntimeInfo {
5
+ /**
6
+ * `node`, `deno`, `bun`, `workerd` for Cloudflare Workers, `edge-light` for Vercel's edge
7
+ * runtime, `browser` for a page or a web worker, or `unknown`.
8
+ */
9
+ name: RuntimeName;
10
+ /** The runtime's version, where it reports one. */
11
+ version?: string;
12
+ }
13
+ /**
14
+ * Which runtime the code is running on, from what each one reliably exposes. Deno and Bun are
15
+ * checked before Node.js, since both also present Node's `process`.
16
+ */
17
+ export declare function runtimeInfo(): RuntimeInfo;
@@ -0,0 +1,16 @@
1
+ export function runtimeInfo() {
2
+ const host = globalThis;
3
+ if (host.Deno?.version?.deno)
4
+ return { name: 'deno', version: host.Deno.version.deno };
5
+ if (host.Bun?.version)
6
+ return { name: 'bun', version: host.Bun.version };
7
+ if (typeof host.EdgeRuntime === 'string')
8
+ return { name: 'edge-light' };
9
+ if (host.navigator?.userAgent === 'Cloudflare-Workers')
10
+ return { name: 'workerd' };
11
+ if (host.process?.versions?.node)
12
+ return { name: 'node', version: host.process.versions.node };
13
+ if (host.document !== undefined || typeof host.importScripts === 'function')
14
+ return { name: 'browser' };
15
+ return { name: 'unknown' };
16
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The portable kernel: the contracts every model call and tool call is built on, for any JavaScript
3
+ * runtime. That means Node.js, Deno, Bun, edge runtimes such as Cloudflare Workers and Vercel's,
4
+ * and browsers.
5
+ *
6
+ * Nothing here, nor anything it imports, uses `fs`, `net`, `child_process`, Node's `crypto`, or
7
+ * Node's globals, and CI runs its tests on each runtime. The provider adapters, graphs, agents, and
8
+ * protocols are portable too, on their own entry points.
9
+ */
10
+ export type { AssetContent, AudioContent, BinaryBuffer, CacheHint, CompletionRequest, ContentPart, ImageContent, Message, MessageRole, PromptCacheConfig, ReasoningConfig, TextContent, ToolCallResult, ToolChoice, ToolContext, ToolDefinition, ToolOutput, VideoContent, } from '../types/messages.js';
11
+ export type { CacheOutcome, NexusResponse, NexusStream, ResponseCost, ResponseMeta, StreamChunk, TokenUsage, ToolCall, } from '../types/response.js';
12
+ export type { ProviderCallContext } from '../types/lifecycle.js';
13
+ export { BaseProvider, type ProviderInfo } from '../providers/base.js';
14
+ export { categorizeProviderError, createProviderHttpError, isAbortError, isRetryableProviderError, NexusProviderError, type NexusProviderErrorCategory, type NexusProviderErrorOptions, toNexusProviderError, } from '../providers/errors.js';
15
+ export { isToolOutput, ToolExecutor, tool, toolMessageContent, toolOutput } from '../agent/tool.js';
16
+ export { capabilitiesOf, isSensitiveCapability, type ParsedCapability, parseCapability, } from '../agent/capabilities.js';
17
+ export { type NegotiateOptions, type NegotiationResult, NexusCapabilityError, negotiateCompletionRequest, } from '../capabilities/negotiate.js';
18
+ export { collectStream, createTextStream, mapStream } from '../core/streaming.js';
19
+ export { type RuntimeInfo, type RuntimeName, runtimeInfo } from './detect.js';
@@ -0,0 +1,7 @@
1
+ export { BaseProvider } from '../providers/base.js';
2
+ export { categorizeProviderError, createProviderHttpError, isAbortError, isRetryableProviderError, NexusProviderError, toNexusProviderError, } from '../providers/errors.js';
3
+ export { isToolOutput, ToolExecutor, tool, toolMessageContent, toolOutput } from '../agent/tool.js';
4
+ export { capabilitiesOf, isSensitiveCapability, parseCapability, } from '../agent/capabilities.js';
5
+ export { NexusCapabilityError, negotiateCompletionRequest, } from '../capabilities/negotiate.js';
6
+ export { collectStream, createTextStream, mapStream } from '../core/streaming.js';
7
+ export { runtimeInfo } from './detect.js';
@@ -7,5 +7,7 @@ export declare class InjectionDetector {
7
7
  /** Returns the request with injection markers defused. */
8
8
  neutralize(request: CompletionRequest): CompletionRequest;
9
9
  private extractText;
10
+ /** Defuses what `detect()` finds. Text with nothing to defuse is returned exactly as it was. */
10
11
  private escapeInstructionLikeText;
12
+ private escapeNormalized;
11
13
  }
@@ -1,3 +1,4 @@
1
+ import { normalizeForDetection } from '../utils/checksums.js';
1
2
  const DEFAULT_PATTERNS = [
2
3
  {
3
4
  pattern: /ignore\s+(all\s+)?previous\s+instructions/i,
@@ -38,7 +39,7 @@ export class InjectionDetector {
38
39
  ];
39
40
  const findings = [];
40
41
  request.messages.forEach((message, index) => {
41
- for (const text of this.extractText(message)) {
42
+ for (const text of this.extractText(message).map(normalizeForDetection)) {
42
43
  patterns.forEach(({ pattern, severity, label }) => {
43
44
  const match = text.match(pattern);
44
45
  if (!match)
@@ -72,6 +73,11 @@ export class InjectionDetector {
72
73
  return message.content.filter((part) => part.type === 'text').map((part) => part.text);
73
74
  }
74
75
  escapeInstructionLikeText(text) {
76
+ const normalized = normalizeForDetection(text);
77
+ const escaped = this.escapeNormalized(normalized);
78
+ return escaped === normalized ? text : escaped;
79
+ }
80
+ escapeNormalized(text) {
75
81
  return text
76
82
  .replace(/ignore\s+(all\s+)?previous\s+instructions/gi, '[neutralized instruction override]')
77
83
  .replace(/disregard\s+(all\s+)?(prior|previous)\s+instructions/gi, '[neutralized instruction override]')
@@ -18,7 +18,7 @@ const SECRET_PATTERNS = [
18
18
  ];
19
19
  const SUSPICIOUS_URL_PATTERNS = [
20
20
  {
21
- pattern: /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|169\.254\.169\.254)(?:[:/]|$)/gi,
21
+ pattern: /https?:\/\/(?:localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[::1?\]|169\.254\.169\.254|metadata\.google\.internal|100\.100\.100\.200)(?:[:/]|$)/gi,
22
22
  label: 'local or metadata URL',
23
23
  },
24
24
  { pattern: /https?:\/\/[^\s]*\.(?:onion)(?:[:/]|$)/gi, label: 'onion URL' },
@@ -97,7 +97,7 @@ export class InputGuard {
97
97
  let output = text;
98
98
  patterns.forEach(({ pattern, label, severity }) => {
99
99
  output = output.replace(pattern, (value) => {
100
- findings.push({ type, severity, message: `Detected ${label}`, path, value });
100
+ findings.push({ type, severity, message: `Detected ${label}`, path, value: previewSecret(value) });
101
101
  return action === 'mask' ? '[REDACTED]' : value;
102
102
  });
103
103
  });
@@ -126,3 +126,6 @@ export class InputGuard {
126
126
  return message.content.filter((part) => part.type === 'text').map((part) => part.text);
127
127
  }
128
128
  }
129
+ function previewSecret(value) {
130
+ return `${value.slice(0, 4)}… (${value.length} characters)`;
131
+ }
@@ -1,3 +1,4 @@
1
+ import { luhn, phoneNumber } from '../utils/checksums.js';
1
2
  const OUTPUT_SECRET_PATTERNS = [
2
3
  {
3
4
  pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
@@ -12,17 +13,18 @@ const OUTPUT_SECRET_PATTERNS = [
12
13
  { pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g, label: 'google api key', severity: 'critical' },
13
14
  { pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, label: 'jwt token', severity: 'high' },
14
15
  { pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, label: 'email address', severity: 'high' },
16
+ { pattern: /\b(?:\d[ -]*?){13,19}\b/g, label: 'possible credit card', severity: 'high', valid: luhn },
15
17
  {
16
18
  pattern: /(?:\+?\d{1,3}[\s.-]?)?(?:\(?\d{2,4}\)?[\s.-]?)?\d{3,4}[\s.-]?\d{4}\b/g,
17
19
  label: 'phone number',
18
20
  severity: 'high',
21
+ valid: phoneNumber,
19
22
  },
20
23
  {
21
24
  pattern: /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g,
22
25
  label: 'ip address',
23
26
  severity: 'high',
24
27
  },
25
- { pattern: /\b(?:\d[ -]*?){13,19}\b/g, label: 'possible credit card', severity: 'high' },
26
28
  {
27
29
  pattern: /(?:api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|secret|password|token)\s*[:=]\s*(?:['"][^'"\r\n]+['"]|[^\s,;]{8,})/gi,
28
30
  label: 'secret assignment',
@@ -33,8 +35,8 @@ const OUTPUT_SECRET_PATTERNS = [
33
35
  const CONNECTION_STRING_PATTERN = /\b(?:postgres|postgresql|mysql|mongodb|redis|mssql):\/\/[^\s'"<>]+/gi;
34
36
  export function redactSensitiveText(value) {
35
37
  let redacted = value;
36
- for (const { pattern } of OUTPUT_SECRET_PATTERNS) {
37
- redacted = redacted.replace(pattern, '[REDACTED]');
38
+ for (const { pattern, valid } of OUTPUT_SECRET_PATTERNS) {
39
+ redacted = redacted.replace(pattern, (match) => (valid && !valid(match) ? match : '[REDACTED]'));
38
40
  }
39
41
  return redacted.replace(CONNECTION_STRING_PATTERN, '[REDACTED_CONNECTION_STRING]');
40
42
  }
@@ -64,9 +66,11 @@ export class OutputGuard {
64
66
  }
65
67
  const shouldRedact = config.output?.piiRedaction !== false;
66
68
  if (shouldRedact) {
67
- for (const { pattern, label, severity } of OUTPUT_SECRET_PATTERNS) {
69
+ for (const { pattern, label, severity, valid } of OUTPUT_SECRET_PATTERNS) {
68
70
  for (const target of textTargets) {
69
71
  target.value = target.value.replace(pattern, (value) => {
72
+ if (valid && !valid(value))
73
+ return value;
70
74
  findings.push({
71
75
  type: 'pii',
72
76
  severity,