nexus-ai-pro 1.18.0 → 1.19.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 (42) hide show
  1. package/API_STABILITY.md +13 -0
  2. package/CHANGELOG.md +69 -1
  3. package/README.md +1 -0
  4. package/dist/graph/checkpointer.d.ts +5 -0
  5. package/dist/graph/checkpointer.js +9 -0
  6. package/dist/mcp/client.d.ts +6 -1
  7. package/dist/mcp/client.js +7 -0
  8. package/dist/mcp/server.js +4 -4
  9. package/dist/security/index.js +3 -3
  10. package/dist/security/input-guard.js +6 -1
  11. package/dist/security/pii-detector.d.ts +1 -1
  12. package/dist/security/pii-detector.js +9 -3
  13. package/dist/security/prompt-hardening.d.ts +3 -0
  14. package/dist/security/semantic-injection-classifier.d.ts +6 -0
  15. package/dist/security/semantic-injection-classifier.js +3 -1
  16. package/dist/tracing/instrument.js +2 -0
  17. package/dist/tracing/tracer.d.ts +4 -0
  18. package/dist/tracing/tracer.js +2 -0
  19. package/dist/types/graph.d.ts +2 -0
  20. package/dist/types/security.d.ts +74 -5
  21. package/dist/workflow/chains.d.ts +3 -3
  22. package/dist/workflow/chains.js +18 -7
  23. package/dist-cjs/graph/checkpointer.d.ts +5 -0
  24. package/dist-cjs/graph/checkpointer.js +9 -0
  25. package/dist-cjs/mcp/client.d.ts +6 -1
  26. package/dist-cjs/mcp/client.js +7 -0
  27. package/dist-cjs/mcp/server.js +3 -3
  28. package/dist-cjs/security/index.js +3 -3
  29. package/dist-cjs/security/input-guard.js +6 -1
  30. package/dist-cjs/security/pii-detector.d.ts +1 -1
  31. package/dist-cjs/security/pii-detector.js +9 -3
  32. package/dist-cjs/security/prompt-hardening.d.ts +3 -0
  33. package/dist-cjs/security/semantic-injection-classifier.d.ts +6 -0
  34. package/dist-cjs/security/semantic-injection-classifier.js +3 -1
  35. package/dist-cjs/tracing/instrument.js +2 -0
  36. package/dist-cjs/tracing/tracer.d.ts +4 -0
  37. package/dist-cjs/tracing/tracer.js +2 -0
  38. package/dist-cjs/types/graph.d.ts +2 -0
  39. package/dist-cjs/types/security.d.ts +74 -5
  40. package/dist-cjs/workflow/chains.d.ts +3 -3
  41. package/dist-cjs/workflow/chains.js +18 -7
  42. package/package.json +10 -5
package/API_STABILITY.md CHANGED
@@ -442,6 +442,19 @@ from the root.
442
442
  - Recovery re-runs a whole run rather than the step it died on, so an assistant with side effects
443
443
  should be idempotent or be left at a single attempt.
444
444
 
445
+ ## Studio stage (1.19.0)
446
+
447
+ `nexus-ai-pro-studio` is a separate package, experimental in production readiness. It is versioned
448
+ with nexus-ai-pro and declares the core versions it works with as a peer dependency.
449
+
450
+ - The `nexus-studio` command, its flags, and the shape of the config module's default export follow
451
+ the 1.x rules. A minor release may add sources and flags.
452
+ - `createStudio()`, `startStudio()`, and the exported types follow the 1.x rules.
453
+ - The JSON routes behind the page are for the page and may change in any release; build on the
454
+ stores themselves rather than on the studio's routes.
455
+ - The access rules are a guarantee: loopback binding by default, a token on every request, the token
456
+ in a header for every change, and loopback hosts only unless others are allowed.
457
+
445
458
  ## Deprecation process
446
459
 
447
460
  Deprecated APIs are marked with `@deprecated` in declarations and described in the changelog. Removals
package/CHANGELOG.md CHANGED
@@ -4,6 +4,73 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.19.0] - 2026-09-28
8
+
9
+ A studio to look at it all. `nexus-ai-pro-studio` is a separate package — so the core install never
10
+ carries a UI — that shows what an application records, from its own stores, on the developer's
11
+ machine, with no hosted service.
12
+
13
+ ### Added
14
+
15
+ - **`nexus-ai-pro-studio`**, experimental, with the `nexus-studio` command. Given a config module
16
+ whose default export lists the application's stores, it serves eight views:
17
+ - **Traces**: filtered run lists, run trees with inputs, outputs, cost, and latency, trace
18
+ comparison, and feedback on a run.
19
+ - **Threads**: each graph's diagram with the next nodes highlighted, state at any step, and fork,
20
+ edit, and resume.
21
+ - **Inbox**: every thread waiting for a human and every review item waiting for a grade.
22
+ - **Experiments**: experiment and dataset lists, and comparisons with per-metric intervals and a
23
+ verdict.
24
+ - **Prompts**: versions, labels, history, diffs, gated promotion, rollback, and a playground.
25
+ - **Costs**: cost per day and per model, the most expensive runs, and budgets.
26
+ - **Health**: provider health, local and shared circuit state, metrics, and cache statistics.
27
+ - **Operations**: the operation queue by status, and asset store totals.
28
+ - **Locked down for a local tool.** It binds `127.0.0.1`, requires a token that moves from the URL
29
+ into an HTTP-only same-site cookie, requires the token in a header for every change, refuses
30
+ non-loopback `Host` headers against DNS rebinding, and serves every page under a strict content
31
+ security policy with every value inserted as text.
32
+ - `startStudio()` and `createStudio()` start it from code or mount it in an existing server.
33
+ - `npm run studio:demo` starts it on a demo application with something in every view.
34
+ - The operation-store checkpointer — the one behind Redis and Postgres — lists its threads through
35
+ `threadIds()`, as the in-memory one already did, and `GraphCheckpointer` declares it as optional.
36
+
37
+ ### Fixed
38
+
39
+ - **Secret detection on input now detects under every action.** It only scanned when the action
40
+ was `mask`. Under the default action, `block`, and under `flag`, a request carrying a private
41
+ key, a cloud or payment key, a token, or a secret assignment went out with no finding. Such a
42
+ request is now blocked by default, at every level but `off`. Set `input.secrets.action` to `flag`
43
+ to record secrets without blocking.
44
+ - The PII action `remove` deletes the matched data. It was accepted but behaved like `flag`, so the
45
+ data stayed in the request.
46
+ - The semantic injection check no longer re-embeds its example attacks on every request.
47
+ - `McpServer` answers an unknown method with the JSON-RPC method-not-found code, and an unknown
48
+ resource with invalid-params, instead of an internal error, so a client can tell an unsupported
49
+ feature from a failure.
50
+ - `McpClient` requests made together before the handshake finished each sent `initialize`; they now
51
+ share one handshake.
52
+ - `summarizeVerifyFormat()`'s verify step sent the summary to the model with no instruction, so the
53
+ model answered the summary instead of restating it, and that reply was what got checked against
54
+ the sources. It now asks for a restatement against the sources. The final format is applied only in
55
+ the last step, and a verify step that was skipped is no longer recorded in `steps`.
56
+ - `traceModelClient()` records the model and provider that answered on the run itself. It recorded
57
+ the model the request asked for — often `auto` — and left `provider` empty, so a `provider` query
58
+ never matched a model run and per-model views showed `auto`. `FinishRunOptions` gains `model` and
59
+ `provider` for the same purpose in your own instrumentation.
60
+
61
+ ### Deprecated
62
+
63
+ - `InjectionDetectionConfig.sensitivity` and `ToolPolicyConfig.requiresApproval` have never been
64
+ read. Both will be removed in 2.0; approval before a tool runs belongs to the agent loop.
65
+
66
+ ### Changed
67
+
68
+ - Nineteen guides under `docs/` — agents, batch, caching, core, graphs, grounding, MCP, memory,
69
+ Postgres, prompts, providers, resilience, security, server, telephony, testing, tracing, voice, and
70
+ workflows — now explain each export they list in their text, not only in the generated reference.
71
+ The security configuration's nested settings have doc comments.
72
+ - The documentation coverage check also covers the studio package's exports.
73
+
7
74
  ## [1.18.0] - 2026-09-25
8
75
 
9
76
  A server you can host. Assistants get an HTTP surface — threads, background runs, resumable event
@@ -941,7 +1008,8 @@ shared policy vocabulary is in place for a future release that revisits this.
941
1008
  - URL fetching rejects unsafe private-network targets and limits response reads.
942
1009
  - CLI and audit findings no longer disclose detected secret values.
943
1010
 
944
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.18.0...HEAD
1011
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.19.0...HEAD
1012
+ [1.19.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.18.0...v1.19.0
945
1013
  [1.18.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.17.0...v1.18.0
946
1014
  [1.17.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.16.0...v1.17.0
947
1015
  [1.16.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.15.0...v1.16.0
package/README.md CHANGED
@@ -125,6 +125,7 @@ from the doc comments. The guides live in the repository, so these links go to G
125
125
  | [Realtime voice](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/realtime.md) | Browser and server realtime sessions with barge-in, tools, and exports |
126
126
  | [Telephony](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/telephony.md) | Calls, webhooks, phone numbers, and phone agents on realtime sessions |
127
127
  | [Testing](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/testing.md) | Recording and replaying provider traffic, and conformance suites |
128
+ | [Studio](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/studio.md) | A local UI for traces, threads, approvals, experiments, prompts, costs, and health, as a separate package |
128
129
  | [Agent server](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/server.md) | Self-hosted HTTP server for assistants: threads, durable runs, resumable streams, cron |
129
130
  | [Workflows](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/workflows.md) | Ready-made chains and domain workflows |
130
131
  | [Command line](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/cli.md) | The `nexus` command |
@@ -66,6 +66,11 @@ export declare class OperationStoreCheckpointer implements GraphCheckpointer {
66
66
  history(threadId: string, limit?: number): Promise<GraphCheckpoint[]>;
67
67
  /** Deletes a thread's checkpoints and its head. */
68
68
  delete(threadId: string): Promise<void>;
69
+ /**
70
+ * Every thread with a checkpoint, read from the head records. Empty when the store cannot list,
71
+ * since finding heads any other way would mean scanning every key.
72
+ */
73
+ threadIds(): Promise<string[]>;
69
74
  private readHead;
70
75
  /**
71
76
  * Writes one record, creating it when absent and otherwise updating in place.
@@ -86,6 +86,15 @@ export class OperationStoreCheckpointer {
86
86
  await this.store.delete?.(stepId(threadId, step));
87
87
  await this.store.delete?.(threadId);
88
88
  }
89
+ async threadIds() {
90
+ if (!this.store.list)
91
+ return [];
92
+ const records = await this.store.list();
93
+ return records
94
+ .filter((record) => record.result !== undefined && isHead(record.result))
95
+ .map((record) => record.id)
96
+ .sort();
97
+ }
89
98
  async readHead(threadId) {
90
99
  const record = await this.store.read(threadId);
91
100
  return record?.result && isHead(record.result) ? record.result : undefined;
@@ -62,13 +62,18 @@ export declare class McpClient {
62
62
  private readonly pending;
63
63
  private nextId;
64
64
  private initialized;
65
+ private connecting;
65
66
  private serverInfo;
66
67
  constructor(transport: McpTransport, options?: McpClientOptions);
67
- /** Performs the handshake. Called automatically by the first request that needs it. */
68
+ /**
69
+ * Performs the handshake and returns the server's name and version. Called automatically by the
70
+ * first request that needs it; requests made together share one handshake.
71
+ */
68
72
  connect(): Promise<{
69
73
  name?: string;
70
74
  version?: string;
71
75
  }>;
76
+ private handshake;
72
77
  /** Lists the server's tools. */
73
78
  listTools(): Promise<McpToolDescriptor[]>;
74
79
  /**
@@ -5,6 +5,7 @@ export class McpClient {
5
5
  pending = new Map();
6
6
  nextId = 1;
7
7
  initialized = false;
8
+ connecting;
8
9
  serverInfo = {};
9
10
  constructor(transport, options = {}) {
10
11
  this.transport = transport;
@@ -15,6 +16,12 @@ export class McpClient {
15
16
  async connect() {
16
17
  if (this.initialized)
17
18
  return this.serverInfo;
19
+ this.connecting ??= this.handshake().finally(() => {
20
+ this.connecting = undefined;
21
+ });
22
+ return this.connecting;
23
+ }
24
+ async handshake() {
18
25
  await this.transport.start?.();
19
26
  const result = (await this.request('initialize', {
20
27
  protocolVersion: MCP_PROTOCOL_VERSION,
@@ -1,4 +1,4 @@
1
- import { isNotification, isRequest, JSON_RPC_ERRORS, LineDecoder, MCP_PROTOCOL_VERSION, textContent, } from './protocol.js';
1
+ import { isNotification, isRequest, JSON_RPC_ERRORS, LineDecoder, MCP_PROTOCOL_VERSION, McpError, textContent, } from './protocol.js';
2
2
  export class McpServer {
3
3
  options;
4
4
  tools = new Map();
@@ -35,7 +35,7 @@ export class McpServer {
35
35
  jsonrpc: '2.0',
36
36
  id: message.id,
37
37
  error: {
38
- code: JSON_RPC_ERRORS.internal,
38
+ code: error instanceof McpError ? error.code : JSON_RPC_ERRORS.internal,
39
39
  message: error instanceof Error ? error.message : String(error),
40
40
  },
41
41
  });
@@ -89,7 +89,7 @@ export class McpServer {
89
89
  const uri = String(params.uri ?? '');
90
90
  const resource = this.options.resources?.find((item) => item.uri === uri);
91
91
  if (!resource)
92
- throw new Error(`Unknown resource "${uri}"`);
92
+ throw new McpError(`Unknown resource "${uri}"`, JSON_RPC_ERRORS.invalidParams);
93
93
  const value = await resource.read();
94
94
  return {
95
95
  contents: [
@@ -102,7 +102,7 @@ export class McpServer {
102
102
  };
103
103
  }
104
104
  default:
105
- throw new Error(`Unknown method "${request.method}"`);
105
+ throw new McpError(`Unknown method "${request.method}"`, JSON_RPC_ERRORS.methodNotFound);
106
106
  }
107
107
  }
108
108
  }
@@ -63,9 +63,9 @@ export class SecurityPipeline {
63
63
  findings.push(...piiFindings);
64
64
  guardrailsApplied.push('pii-detection');
65
65
  const action = piiConfig.action || (level === 'strict' || level === 'paranoid' ? 'mask' : 'flag');
66
- if (piiFindings.length > 0 && action === 'mask') {
67
- safeRequest = this.piiDetector.mask(safeRequest, piiConfig);
68
- guardrailsApplied.push('pii-masking');
66
+ if (piiFindings.length > 0 && (action === 'mask' || action === 'remove')) {
67
+ safeRequest = this.piiDetector.mask(safeRequest, { ...piiConfig, action });
68
+ guardrailsApplied.push(action === 'remove' ? 'pii-removal' : 'pii-masking');
69
69
  }
70
70
  }
71
71
  const blockingFindings = this.getBlockingFindings(findings, level, normalized);
@@ -103,8 +103,13 @@ export class InputGuard {
103
103
  });
104
104
  return output;
105
105
  };
106
- if (action !== 'mask')
106
+ if (action !== 'mask') {
107
+ request.messages.forEach((message, index) => {
108
+ for (const text of this.extractText(message))
109
+ maskText(text, `messages.${index}.content`);
110
+ });
107
111
  return request;
112
+ }
108
113
  return {
109
114
  ...request,
110
115
  messages: request.messages.map((message, index) => ({
@@ -4,7 +4,7 @@ import type { PIIConfig, SecurityFinding } from '../types/security.js';
4
4
  export declare class PIIDetector {
5
5
  /** Finds personal data in a request's messages. */
6
6
  detect(request: CompletionRequest, config?: PIIConfig): SecurityFinding[];
7
- /** Returns the request with personal data masked. */
7
+ /** Returns the request with personal data masked, or removed when the action is `remove`. */
8
8
  mask(request: CompletionRequest, config?: PIIConfig): CompletionRequest;
9
9
  private maskText;
10
10
  private extractText;
@@ -35,22 +35,28 @@ export class PIIDetector {
35
35
  mask(request, config = {}) {
36
36
  const enabledTypes = config.detect || ['email', 'phone', 'credit-card', 'ip-address', 'aws-key', 'private-key'];
37
37
  const maskChar = config.maskChar || '█';
38
+ const remove = config.action === 'remove';
38
39
  return {
39
40
  ...request,
40
41
  messages: request.messages.map((message) => ({
41
42
  ...message,
42
43
  content: typeof message.content === 'string'
43
- ? this.maskText(message.content, enabledTypes, maskChar, config.preserveFormat !== false)
44
+ ? this.maskText(message.content, enabledTypes, maskChar, config.preserveFormat !== false, remove)
44
45
  : message.content.map((part) => part.type === 'text'
45
- ? { ...part, text: this.maskText(part.text, enabledTypes, maskChar, config.preserveFormat !== false) }
46
+ ? {
47
+ ...part,
48
+ text: this.maskText(part.text, enabledTypes, maskChar, config.preserveFormat !== false, remove),
49
+ }
46
50
  : part),
47
51
  })),
48
52
  };
49
53
  }
50
- maskText(text, types, maskChar, preserveFormat) {
54
+ maskText(text, types, maskChar, preserveFormat, remove = false) {
51
55
  let masked = text;
52
56
  for (const type of types) {
53
57
  masked = masked.replace(PII_PATTERNS[type], (value) => {
58
+ if (remove)
59
+ return '';
54
60
  if (!preserveFormat)
55
61
  return maskChar.repeat(Math.min(value.length, 12));
56
62
  return value.replace(/[A-Za-z0-9]/g, maskChar);
@@ -1,6 +1,9 @@
1
1
  import type { CompletionRequest } from '../types/messages.js';
2
+ /** Options for `hardenPrompt()`. */
2
3
  export interface PromptHardeningOptions {
4
+ /** What user text is wrapped in. Defaults to `"""`. */
3
5
  delimiter?: string;
6
+ /** The system message placed first, telling the model to treat delimited text as data. */
4
7
  systemInstruction?: string;
5
8
  }
6
9
  /**
@@ -1,10 +1,15 @@
1
1
  import type { CompletionRequest } from '../types/messages.js';
2
2
  import type { SecurityFinding } from '../types/security.js';
3
3
  import { type EmbeddingProvider } from '../hallucination/retrieval.js';
4
+ /** Options for `SemanticInjectionClassifier`. */
4
5
  export interface SemanticInjectionClassifierOptions {
6
+ /** `false` turns `detect()` off. */
5
7
  enabled?: boolean;
8
+ /** Similarity at which a prompt is flagged, from 0 to 1. Defaults to 0.78; above 0.88 is critical. */
6
9
  threshold?: number;
10
+ /** Embeds text for `detect()`. Defaults to hashed term vectors; `detectSync()` always uses those. */
7
11
  embed?: EmbeddingProvider;
12
+ /** Example attacks to compare against, instead of the built-in ones. */
8
13
  examples?: string[];
9
14
  }
10
15
  /**
@@ -16,6 +21,7 @@ export declare class SemanticInjectionClassifier {
16
21
  private examples;
17
22
  private embed;
18
23
  private exampleEmbeddings?;
24
+ private hashedExamples?;
19
25
  constructor(options?: SemanticInjectionClassifierOptions);
20
26
  /** Finds injection attempts, using the configured embedding function. */
21
27
  detect(request: CompletionRequest): Promise<SecurityFinding[]>;
@@ -12,6 +12,7 @@ export class SemanticInjectionClassifier {
12
12
  examples;
13
13
  embed;
14
14
  exampleEmbeddings;
15
+ hashedExamples;
15
16
  constructor(options = {}) {
16
17
  this.options = options;
17
18
  this.examples = options.examples || DEFAULT_ATTACK_EXAMPLES;
@@ -43,7 +44,8 @@ export class SemanticInjectionClassifier {
43
44
  detectSync(request) {
44
45
  const findings = [];
45
46
  const threshold = this.options.threshold ?? 0.78;
46
- const exampleEmbeddings = createHashEmbeddings(this.examples);
47
+ this.hashedExamples ??= createHashEmbeddings(this.examples);
48
+ const exampleEmbeddings = this.hashedExamples;
47
49
  for (const [index, message] of request.messages.entries()) {
48
50
  for (const text of extractText(message)) {
49
51
  const [embedding] = createHashEmbeddings([text]);
@@ -105,6 +105,8 @@ export function traceModelClient(client, tracer, options = {}) {
105
105
  }
106
106
  : {}),
107
107
  ...(response.meta?.cost?.amount === undefined ? {} : { cost: response.meta.cost.amount }),
108
+ ...(response.meta?.modelUsed ? { model: response.meta.modelUsed } : {}),
109
+ ...(response.meta?.providerUsed ? { provider: response.meta.providerUsed } : {}),
108
110
  metadata: {
109
111
  provider: response.meta?.providerUsed,
110
112
  model: response.meta?.modelUsed,
@@ -49,6 +49,10 @@ export interface FinishRunOptions {
49
49
  cost?: number;
50
50
  /** Metadata merged into the run's. */
51
51
  metadata?: Record<string, unknown>;
52
+ /** The model that actually ran, replacing the one the run started with, such as `auto` after routing. */
53
+ model?: string;
54
+ /** The provider that actually ran. */
55
+ provider?: string;
52
56
  }
53
57
  /** A run in progress. Finishing it writes it to the store. */
54
58
  export interface RunHandle {
@@ -78,6 +78,8 @@ export class Tracer {
78
78
  ...(options.error ? { error: describeError(options.error) } : {}),
79
79
  ...(options.usage ? { usage: options.usage } : {}),
80
80
  ...(options.cost === undefined ? {} : { cost: options.cost }),
81
+ ...(options.model ? { model: options.model } : {}),
82
+ ...(options.provider ? { provider: options.provider } : {}),
81
83
  metadata: { ...run.metadata, ...options.metadata },
82
84
  };
83
85
  await this.close(finished, sampled);
@@ -360,6 +360,8 @@ export interface GraphCheckpointer {
360
360
  history(threadId: string, limit?: number): Promise<GraphCheckpoint[]> | GraphCheckpoint[];
361
361
  /** Deletes every checkpoint of a thread. */
362
362
  delete?(threadId: string): Promise<void> | void;
363
+ /** Every thread with a checkpoint, for tools that browse threads, such as the studio. */
364
+ threadIds?(): Promise<string[]> | string[];
363
365
  }
364
366
  /** Options for one run of a compiled graph. */
365
367
  export interface GraphRunOptions {
@@ -1,5 +1,11 @@
1
1
  /** How much protection a client applies, from none to maximal. */
2
2
  export type SecurityLevel = 'off' | 'basic' | 'standard' | 'strict' | 'paranoid';
3
+ /**
4
+ * A preset tuned for a kind of deployment. `developer` is the `standard` level; `startup` is `strict`
5
+ * with output redaction; `enterprise` adds blocking injection detection and PII masking; `healthcare`
6
+ * and `finance` are `paranoid`, blocking or masking PII respectively. Your own settings are merged
7
+ * over the preset.
8
+ */
3
9
  export type SecurityPreset = 'developer' | 'startup' | 'enterprise' | 'healthcare' | 'finance';
4
10
  /**
5
11
  * What a guardrail does with a match: let it through, block the request, mask the match, or record
@@ -12,16 +18,27 @@ export type PIIType = 'email' | 'phone' | 'credit-card' | 'ip-address' | 'aws-ke
12
18
  export interface InjectionDetectionConfig {
13
19
  /** Turns detection on. */
14
20
  enabled?: boolean;
15
- /** How eagerly patterns match, from 0 to 1. */
21
+ /**
22
+ * Ignored: the patterns match or they do not.
23
+ *
24
+ * @deprecated Has never been read. It will be removed in 2.0.
25
+ */
16
26
  sensitivity?: number;
17
27
  /** What a detection does: block the request, record it, or neutralize the text. */
18
28
  onDetection?: 'block' | 'flag' | 'transform';
19
29
  /** Extra patterns to treat as injection. */
20
30
  customPatterns?: RegExp[];
21
- /** Similarity-based detection against example attacks, through an embedder. */
31
+ /**
32
+ * Similarity to example attacks, which catches rephrasings a pattern misses. In the pipeline it
33
+ * compares hashed term vectors, so it needs no provider; for a real embedding model, use
34
+ * `SemanticInjectionClassifier` with `embed` directly.
35
+ */
22
36
  semantic?: {
37
+ /** Turns the semantic check on. Off by default. */
23
38
  enabled?: boolean;
39
+ /** Similarity at which a prompt is flagged, from 0 to 1. Defaults to 0.78. */
24
40
  threshold?: number;
41
+ /** Example attacks to compare against, instead of the built-in ones. */
25
42
  examples?: string[];
26
43
  };
27
44
  }
@@ -31,42 +48,82 @@ export interface PIIConfig {
31
48
  enabled?: boolean;
32
49
  /** Kinds of data to detect. */
33
50
  detect?: PIIType[];
34
- /** What a match does: mask it, remove it, block the request, or record it. */
51
+ /**
52
+ * What a match does: mask it, remove it, block the request, or record it. Defaults to `mask` at the
53
+ * `strict` and `paranoid` levels and `flag` below them; `paranoid` also blocks.
54
+ */
35
55
  action?: 'mask' | 'remove' | 'block' | 'flag';
36
- /** Character used for masking. */
56
+ /** Character used for masking. Defaults to `█`. */
37
57
  maskChar?: string;
38
- /** Keeps the data's shape when masking, such as the last four digits. */
58
+ /**
59
+ * Keeps the data's shape when masking: letters and digits are replaced one for one and punctuation
60
+ * stays. On by default; `false` replaces each match with a run of the mask character instead.
61
+ */
39
62
  preserveFormat?: boolean;
40
63
  }
64
+ /** Detection of credentials in input: private keys, cloud and payment keys, tokens, and secret assignments. */
41
65
  export interface SecretDetectionConfig {
66
+ /** Turns detection on. On by default at every level but `off`. */
42
67
  enabled?: boolean;
68
+ /** What a match does: block the request, mask the secret, or record it. Defaults to `block`. */
43
69
  action?: 'block' | 'mask' | 'flag';
44
70
  }
71
+ /**
72
+ * Detection of risky URLs in input: loopback and cloud-metadata addresses, onion services, and URLs
73
+ * carrying secret-like query parameters.
74
+ */
45
75
  export interface UrlRiskConfig {
76
+ /** Turns detection on. On by default at every level but `off`. */
46
77
  enabled?: boolean;
78
+ /** What a match does: block the request, or record it. Defaults to `flag`. */
47
79
  action?: 'block' | 'flag';
48
80
  }
81
+ /** Which tools a request may offer the model. */
49
82
  export interface ToolPolicyConfig {
83
+ /** The only tool names a request may carry; any other tool blocks it. Unset or empty allows every tool. */
50
84
  allowedNames?: string[];
85
+ /**
86
+ * Ignored: approval before a tool runs belongs to the agent loop.
87
+ *
88
+ * @deprecated Has never been read. It will be removed in 2.0.
89
+ */
51
90
  requiresApproval?: string[];
52
91
  }
92
+ /** Terms a response must not contain. */
53
93
  export interface ModerationConfig {
94
+ /** Turns moderation on. Off by default. */
54
95
  enabled?: boolean;
96
+ /** Terms matched literally, ignoring case. */
55
97
  forbiddenTerms?: string[];
98
+ /** What a match does: block the response, record it, or redact the term. Defaults to recording it. */
56
99
  onViolation?: 'block' | 'flag' | 'redact';
57
100
  }
101
+ /** Data-loss prevention on output. */
58
102
  export interface DLPConfig {
103
+ /** Turns the checks on. On by default. */
59
104
  enabled?: boolean;
105
+ /** Terms that must not leave, such as project code names. A match is recorded as a high finding. */
60
106
  proprietaryTerms?: string[];
107
+ /** Redacts database connection strings. On by default. */
61
108
  connectionStrings?: boolean;
62
109
  }
110
+ /** Topics a response must stay out of, matched as text, ignoring case. */
63
111
  export interface TopicGuardrailConfig {
112
+ /** The topics. */
64
113
  forbiddenTopics?: string[];
114
+ /** What a match does: block the response, or record it. Defaults to recording it. */
65
115
  onViolation?: 'block' | 'flag';
66
116
  }
117
+ /**
118
+ * A cheap grounding check on output: the share of the response's longer words that also appear in
119
+ * the context. A low share is recorded as a medium finding; it never blocks.
120
+ */
67
121
  export interface GroundingConfig {
122
+ /** Turns the check on. Off by default. */
68
123
  enabled?: boolean;
124
+ /** The context the response should draw from. */
69
125
  context?: string[];
126
+ /** Smallest acceptable share, from 0 to 1. Defaults to 0.1. */
70
127
  minOverlapRatio?: number;
71
128
  }
72
129
  /** Guardrails for input and output, from a preset, a level, or detailed settings. */
@@ -77,20 +134,32 @@ export interface SecurityConfig {
77
134
  level?: SecurityLevel;
78
135
  /** Input guardrails: length, injection, PII, secrets, URLs, and tool policy. */
79
136
  input?: {
137
+ /** Longest the request's text may be, in characters; longer blocks it. */
80
138
  maxContentLength?: number;
139
+ /** Prompt-injection detection, from the `standard` level up. */
81
140
  injectionDetection?: InjectionDetectionConfig;
141
+ /** Personal data detection, from the `standard` level up. */
82
142
  pii?: PIIConfig;
143
+ /** Credential detection. */
83
144
  secrets?: SecretDetectionConfig;
145
+ /** Risky URL detection. */
84
146
  urls?: UrlRiskConfig;
147
+ /** Which tools a request may carry. */
85
148
  tools?: ToolPolicyConfig;
86
149
  };
87
150
  /** Output guardrails: length, PII redaction, moderation, DLP, topics, and grounding. */
88
151
  output?: {
152
+ /** Longest the response may be, in characters; longer is cut, and the cut recorded. */
89
153
  maxContentLength?: number;
154
+ /** Redacts secrets and personal data from the response and its tool-call arguments. On by default. */
90
155
  piiRedaction?: boolean;
156
+ /** Forbidden terms. */
91
157
  moderation?: ModerationConfig;
158
+ /** Connection strings and proprietary terms. */
92
159
  dlp?: DLPConfig;
160
+ /** Forbidden topics. */
93
161
  topics?: TopicGuardrailConfig;
162
+ /** Word overlap with a context. */
94
163
  grounding?: GroundingConfig;
95
164
  };
96
165
  }
@@ -32,7 +32,7 @@ export interface SummarizeVerifyFormatOptions {
32
32
  model: string;
33
33
  /** The content to summarize. */
34
34
  input: string;
35
- /** Sources to check the summary against. Without them, verification is skipped. */
35
+ /** Sources to check the summary against. Without them, the verify step is skipped. */
36
36
  verifyContext?: string[];
37
37
  /** Format of the final answer, such as a JSON schema. */
38
38
  responseFormat?: ResponseFormatConfig;
@@ -86,8 +86,8 @@ export interface CompareOptions {
86
86
  criteria?: string[];
87
87
  }
88
88
  /**
89
- * Summarizes content, verifies the summary against sources when given, then formats it: three
90
- * completions, each step returned.
89
+ * Summarizes content, verifies the summary against sources when given, then formats it: two
90
+ * completions, or three to four with verification, each step returned.
91
91
  */
92
92
  export declare function summarizeVerifyFormat(client: WorkflowClient, options: SummarizeVerifyFormatOptions): Promise<WorkflowResult>;
93
93
  /**
@@ -13,17 +13,28 @@ export async function summarizeVerifyFormat(client, options) {
13
13
  ],
14
14
  }, { chainOfThought: 'private' }));
15
15
  steps.push({ step: 'summarize', response: summary });
16
- const verified = options.verifyContext?.length
17
- ? await completeVerified(client, {
16
+ let verified = summary;
17
+ if (options.verifyContext?.length) {
18
+ verified = await completeVerified(client, {
18
19
  model: options.model,
19
- messages: [{ role: 'user', content: summary.content }],
20
- responseFormat: toRequestResponseFormat(options.responseFormat),
20
+ messages: [
21
+ {
22
+ role: 'user',
23
+ content: [
24
+ 'Restate this summary, keeping only what the sources support.',
25
+ 'Sources:',
26
+ ...options.verifyContext.map((item, index) => `[source-${index + 1}] ${item}`),
27
+ 'Summary:',
28
+ summary.content,
29
+ ].join('\n'),
30
+ },
31
+ ],
21
32
  }, {
22
33
  context: options.verifyContext,
23
34
  repair: true,
24
- })
25
- : summary;
26
- steps.push({ step: 'verify', response: verified });
35
+ });
36
+ steps.push({ step: 'verify', response: verified });
37
+ }
27
38
  const formatted = await client.complete({
28
39
  model: options.model,
29
40
  messages: [
@@ -66,6 +66,11 @@ export declare class OperationStoreCheckpointer implements GraphCheckpointer {
66
66
  history(threadId: string, limit?: number): Promise<GraphCheckpoint[]>;
67
67
  /** Deletes a thread's checkpoints and its head. */
68
68
  delete(threadId: string): Promise<void>;
69
+ /**
70
+ * Every thread with a checkpoint, read from the head records. Empty when the store cannot list,
71
+ * since finding heads any other way would mean scanning every key.
72
+ */
73
+ threadIds(): Promise<string[]>;
69
74
  private readHead;
70
75
  /**
71
76
  * Writes one record, creating it when absent and otherwise updating in place.
@@ -90,6 +90,15 @@ class OperationStoreCheckpointer {
90
90
  await this.store.delete?.(stepId(threadId, step));
91
91
  await this.store.delete?.(threadId);
92
92
  }
93
+ async threadIds() {
94
+ if (!this.store.list)
95
+ return [];
96
+ const records = await this.store.list();
97
+ return records
98
+ .filter((record) => record.result !== undefined && isHead(record.result))
99
+ .map((record) => record.id)
100
+ .sort();
101
+ }
93
102
  async readHead(threadId) {
94
103
  const record = await this.store.read(threadId);
95
104
  return record?.result && isHead(record.result) ? record.result : undefined;
@@ -62,13 +62,18 @@ export declare class McpClient {
62
62
  private readonly pending;
63
63
  private nextId;
64
64
  private initialized;
65
+ private connecting;
65
66
  private serverInfo;
66
67
  constructor(transport: McpTransport, options?: McpClientOptions);
67
- /** Performs the handshake. Called automatically by the first request that needs it. */
68
+ /**
69
+ * Performs the handshake and returns the server's name and version. Called automatically by the
70
+ * first request that needs it; requests made together share one handshake.
71
+ */
68
72
  connect(): Promise<{
69
73
  name?: string;
70
74
  version?: string;
71
75
  }>;
76
+ private handshake;
72
77
  /** Lists the server's tools. */
73
78
  listTools(): Promise<McpToolDescriptor[]>;
74
79
  /**
@@ -43,6 +43,7 @@ class McpClient {
43
43
  pending = new Map();
44
44
  nextId = 1;
45
45
  initialized = false;
46
+ connecting;
46
47
  serverInfo = {};
47
48
  constructor(transport, options = {}) {
48
49
  this.transport = transport;
@@ -53,6 +54,12 @@ class McpClient {
53
54
  async connect() {
54
55
  if (this.initialized)
55
56
  return this.serverInfo;
57
+ this.connecting ??= this.handshake().finally(() => {
58
+ this.connecting = undefined;
59
+ });
60
+ return this.connecting;
61
+ }
62
+ async handshake() {
56
63
  await this.transport.start?.();
57
64
  const result = (await this.request('initialize', {
58
65
  protocolVersion: protocol_js_1.MCP_PROTOCOL_VERSION,
@@ -39,7 +39,7 @@ class McpServer {
39
39
  jsonrpc: '2.0',
40
40
  id: message.id,
41
41
  error: {
42
- code: protocol_js_1.JSON_RPC_ERRORS.internal,
42
+ code: error instanceof protocol_js_1.McpError ? error.code : protocol_js_1.JSON_RPC_ERRORS.internal,
43
43
  message: error instanceof Error ? error.message : String(error),
44
44
  },
45
45
  });
@@ -93,7 +93,7 @@ class McpServer {
93
93
  const uri = String(params.uri ?? '');
94
94
  const resource = this.options.resources?.find((item) => item.uri === uri);
95
95
  if (!resource)
96
- throw new Error(`Unknown resource "${uri}"`);
96
+ throw new protocol_js_1.McpError(`Unknown resource "${uri}"`, protocol_js_1.JSON_RPC_ERRORS.invalidParams);
97
97
  const value = await resource.read();
98
98
  return {
99
99
  contents: [
@@ -106,7 +106,7 @@ class McpServer {
106
106
  };
107
107
  }
108
108
  default:
109
- throw new Error(`Unknown method "${request.method}"`);
109
+ throw new protocol_js_1.McpError(`Unknown method "${request.method}"`, protocol_js_1.JSON_RPC_ERRORS.methodNotFound);
110
110
  }
111
111
  }
112
112
  }
@@ -67,9 +67,9 @@ class SecurityPipeline {
67
67
  findings.push(...piiFindings);
68
68
  guardrailsApplied.push('pii-detection');
69
69
  const action = piiConfig.action || (level === 'strict' || level === 'paranoid' ? 'mask' : 'flag');
70
- if (piiFindings.length > 0 && action === 'mask') {
71
- safeRequest = this.piiDetector.mask(safeRequest, piiConfig);
72
- guardrailsApplied.push('pii-masking');
70
+ if (piiFindings.length > 0 && (action === 'mask' || action === 'remove')) {
71
+ safeRequest = this.piiDetector.mask(safeRequest, { ...piiConfig, action });
72
+ guardrailsApplied.push(action === 'remove' ? 'pii-removal' : 'pii-masking');
73
73
  }
74
74
  }
75
75
  const blockingFindings = this.getBlockingFindings(findings, level, normalized);
@@ -106,8 +106,13 @@ class InputGuard {
106
106
  });
107
107
  return output;
108
108
  };
109
- if (action !== 'mask')
109
+ if (action !== 'mask') {
110
+ request.messages.forEach((message, index) => {
111
+ for (const text of this.extractText(message))
112
+ maskText(text, `messages.${index}.content`);
113
+ });
110
114
  return request;
115
+ }
111
116
  return {
112
117
  ...request,
113
118
  messages: request.messages.map((message, index) => ({
@@ -4,7 +4,7 @@ import type { PIIConfig, SecurityFinding } from '../types/security.js';
4
4
  export declare class PIIDetector {
5
5
  /** Finds personal data in a request's messages. */
6
6
  detect(request: CompletionRequest, config?: PIIConfig): SecurityFinding[];
7
- /** Returns the request with personal data masked. */
7
+ /** Returns the request with personal data masked, or removed when the action is `remove`. */
8
8
  mask(request: CompletionRequest, config?: PIIConfig): CompletionRequest;
9
9
  private maskText;
10
10
  private extractText;
@@ -38,22 +38,28 @@ class PIIDetector {
38
38
  mask(request, config = {}) {
39
39
  const enabledTypes = config.detect || ['email', 'phone', 'credit-card', 'ip-address', 'aws-key', 'private-key'];
40
40
  const maskChar = config.maskChar || '█';
41
+ const remove = config.action === 'remove';
41
42
  return {
42
43
  ...request,
43
44
  messages: request.messages.map((message) => ({
44
45
  ...message,
45
46
  content: typeof message.content === 'string'
46
- ? this.maskText(message.content, enabledTypes, maskChar, config.preserveFormat !== false)
47
+ ? this.maskText(message.content, enabledTypes, maskChar, config.preserveFormat !== false, remove)
47
48
  : message.content.map((part) => part.type === 'text'
48
- ? { ...part, text: this.maskText(part.text, enabledTypes, maskChar, config.preserveFormat !== false) }
49
+ ? {
50
+ ...part,
51
+ text: this.maskText(part.text, enabledTypes, maskChar, config.preserveFormat !== false, remove),
52
+ }
49
53
  : part),
50
54
  })),
51
55
  };
52
56
  }
53
- maskText(text, types, maskChar, preserveFormat) {
57
+ maskText(text, types, maskChar, preserveFormat, remove = false) {
54
58
  let masked = text;
55
59
  for (const type of types) {
56
60
  masked = masked.replace(PII_PATTERNS[type], (value) => {
61
+ if (remove)
62
+ return '';
57
63
  if (!preserveFormat)
58
64
  return maskChar.repeat(Math.min(value.length, 12));
59
65
  return value.replace(/[A-Za-z0-9]/g, maskChar);
@@ -1,6 +1,9 @@
1
1
  import type { CompletionRequest } from '../types/messages.js';
2
+ /** Options for `hardenPrompt()`. */
2
3
  export interface PromptHardeningOptions {
4
+ /** What user text is wrapped in. Defaults to `"""`. */
3
5
  delimiter?: string;
6
+ /** The system message placed first, telling the model to treat delimited text as data. */
4
7
  systemInstruction?: string;
5
8
  }
6
9
  /**
@@ -1,10 +1,15 @@
1
1
  import type { CompletionRequest } from '../types/messages.js';
2
2
  import type { SecurityFinding } from '../types/security.js';
3
3
  import { type EmbeddingProvider } from '../hallucination/retrieval.js';
4
+ /** Options for `SemanticInjectionClassifier`. */
4
5
  export interface SemanticInjectionClassifierOptions {
6
+ /** `false` turns `detect()` off. */
5
7
  enabled?: boolean;
8
+ /** Similarity at which a prompt is flagged, from 0 to 1. Defaults to 0.78; above 0.88 is critical. */
6
9
  threshold?: number;
10
+ /** Embeds text for `detect()`. Defaults to hashed term vectors; `detectSync()` always uses those. */
7
11
  embed?: EmbeddingProvider;
12
+ /** Example attacks to compare against, instead of the built-in ones. */
8
13
  examples?: string[];
9
14
  }
10
15
  /**
@@ -16,6 +21,7 @@ export declare class SemanticInjectionClassifier {
16
21
  private examples;
17
22
  private embed;
18
23
  private exampleEmbeddings?;
24
+ private hashedExamples?;
19
25
  constructor(options?: SemanticInjectionClassifierOptions);
20
26
  /** Finds injection attempts, using the configured embedding function. */
21
27
  detect(request: CompletionRequest): Promise<SecurityFinding[]>;
@@ -15,6 +15,7 @@ class SemanticInjectionClassifier {
15
15
  examples;
16
16
  embed;
17
17
  exampleEmbeddings;
18
+ hashedExamples;
18
19
  constructor(options = {}) {
19
20
  this.options = options;
20
21
  this.examples = options.examples || DEFAULT_ATTACK_EXAMPLES;
@@ -46,7 +47,8 @@ class SemanticInjectionClassifier {
46
47
  detectSync(request) {
47
48
  const findings = [];
48
49
  const threshold = this.options.threshold ?? 0.78;
49
- const exampleEmbeddings = (0, retrieval_js_1.createHashEmbeddings)(this.examples);
50
+ this.hashedExamples ??= (0, retrieval_js_1.createHashEmbeddings)(this.examples);
51
+ const exampleEmbeddings = this.hashedExamples;
50
52
  for (const [index, message] of request.messages.entries()) {
51
53
  for (const text of extractText(message)) {
52
54
  const [embedding] = (0, retrieval_js_1.createHashEmbeddings)([text]);
@@ -109,6 +109,8 @@ function traceModelClient(client, tracer, options = {}) {
109
109
  }
110
110
  : {}),
111
111
  ...(response.meta?.cost?.amount === undefined ? {} : { cost: response.meta.cost.amount }),
112
+ ...(response.meta?.modelUsed ? { model: response.meta.modelUsed } : {}),
113
+ ...(response.meta?.providerUsed ? { provider: response.meta.providerUsed } : {}),
112
114
  metadata: {
113
115
  provider: response.meta?.providerUsed,
114
116
  model: response.meta?.modelUsed,
@@ -49,6 +49,10 @@ export interface FinishRunOptions {
49
49
  cost?: number;
50
50
  /** Metadata merged into the run's. */
51
51
  metadata?: Record<string, unknown>;
52
+ /** The model that actually ran, replacing the one the run started with, such as `auto` after routing. */
53
+ model?: string;
54
+ /** The provider that actually ran. */
55
+ provider?: string;
52
56
  }
53
57
  /** A run in progress. Finishing it writes it to the store. */
54
58
  export interface RunHandle {
@@ -82,6 +82,8 @@ class Tracer {
82
82
  ...(options.error ? { error: describeError(options.error) } : {}),
83
83
  ...(options.usage ? { usage: options.usage } : {}),
84
84
  ...(options.cost === undefined ? {} : { cost: options.cost }),
85
+ ...(options.model ? { model: options.model } : {}),
86
+ ...(options.provider ? { provider: options.provider } : {}),
85
87
  metadata: { ...run.metadata, ...options.metadata },
86
88
  };
87
89
  await this.close(finished, sampled);
@@ -360,6 +360,8 @@ export interface GraphCheckpointer {
360
360
  history(threadId: string, limit?: number): Promise<GraphCheckpoint[]> | GraphCheckpoint[];
361
361
  /** Deletes every checkpoint of a thread. */
362
362
  delete?(threadId: string): Promise<void> | void;
363
+ /** Every thread with a checkpoint, for tools that browse threads, such as the studio. */
364
+ threadIds?(): Promise<string[]> | string[];
363
365
  }
364
366
  /** Options for one run of a compiled graph. */
365
367
  export interface GraphRunOptions {
@@ -1,5 +1,11 @@
1
1
  /** How much protection a client applies, from none to maximal. */
2
2
  export type SecurityLevel = 'off' | 'basic' | 'standard' | 'strict' | 'paranoid';
3
+ /**
4
+ * A preset tuned for a kind of deployment. `developer` is the `standard` level; `startup` is `strict`
5
+ * with output redaction; `enterprise` adds blocking injection detection and PII masking; `healthcare`
6
+ * and `finance` are `paranoid`, blocking or masking PII respectively. Your own settings are merged
7
+ * over the preset.
8
+ */
3
9
  export type SecurityPreset = 'developer' | 'startup' | 'enterprise' | 'healthcare' | 'finance';
4
10
  /**
5
11
  * What a guardrail does with a match: let it through, block the request, mask the match, or record
@@ -12,16 +18,27 @@ export type PIIType = 'email' | 'phone' | 'credit-card' | 'ip-address' | 'aws-ke
12
18
  export interface InjectionDetectionConfig {
13
19
  /** Turns detection on. */
14
20
  enabled?: boolean;
15
- /** How eagerly patterns match, from 0 to 1. */
21
+ /**
22
+ * Ignored: the patterns match or they do not.
23
+ *
24
+ * @deprecated Has never been read. It will be removed in 2.0.
25
+ */
16
26
  sensitivity?: number;
17
27
  /** What a detection does: block the request, record it, or neutralize the text. */
18
28
  onDetection?: 'block' | 'flag' | 'transform';
19
29
  /** Extra patterns to treat as injection. */
20
30
  customPatterns?: RegExp[];
21
- /** Similarity-based detection against example attacks, through an embedder. */
31
+ /**
32
+ * Similarity to example attacks, which catches rephrasings a pattern misses. In the pipeline it
33
+ * compares hashed term vectors, so it needs no provider; for a real embedding model, use
34
+ * `SemanticInjectionClassifier` with `embed` directly.
35
+ */
22
36
  semantic?: {
37
+ /** Turns the semantic check on. Off by default. */
23
38
  enabled?: boolean;
39
+ /** Similarity at which a prompt is flagged, from 0 to 1. Defaults to 0.78. */
24
40
  threshold?: number;
41
+ /** Example attacks to compare against, instead of the built-in ones. */
25
42
  examples?: string[];
26
43
  };
27
44
  }
@@ -31,42 +48,82 @@ export interface PIIConfig {
31
48
  enabled?: boolean;
32
49
  /** Kinds of data to detect. */
33
50
  detect?: PIIType[];
34
- /** What a match does: mask it, remove it, block the request, or record it. */
51
+ /**
52
+ * What a match does: mask it, remove it, block the request, or record it. Defaults to `mask` at the
53
+ * `strict` and `paranoid` levels and `flag` below them; `paranoid` also blocks.
54
+ */
35
55
  action?: 'mask' | 'remove' | 'block' | 'flag';
36
- /** Character used for masking. */
56
+ /** Character used for masking. Defaults to `█`. */
37
57
  maskChar?: string;
38
- /** Keeps the data's shape when masking, such as the last four digits. */
58
+ /**
59
+ * Keeps the data's shape when masking: letters and digits are replaced one for one and punctuation
60
+ * stays. On by default; `false` replaces each match with a run of the mask character instead.
61
+ */
39
62
  preserveFormat?: boolean;
40
63
  }
64
+ /** Detection of credentials in input: private keys, cloud and payment keys, tokens, and secret assignments. */
41
65
  export interface SecretDetectionConfig {
66
+ /** Turns detection on. On by default at every level but `off`. */
42
67
  enabled?: boolean;
68
+ /** What a match does: block the request, mask the secret, or record it. Defaults to `block`. */
43
69
  action?: 'block' | 'mask' | 'flag';
44
70
  }
71
+ /**
72
+ * Detection of risky URLs in input: loopback and cloud-metadata addresses, onion services, and URLs
73
+ * carrying secret-like query parameters.
74
+ */
45
75
  export interface UrlRiskConfig {
76
+ /** Turns detection on. On by default at every level but `off`. */
46
77
  enabled?: boolean;
78
+ /** What a match does: block the request, or record it. Defaults to `flag`. */
47
79
  action?: 'block' | 'flag';
48
80
  }
81
+ /** Which tools a request may offer the model. */
49
82
  export interface ToolPolicyConfig {
83
+ /** The only tool names a request may carry; any other tool blocks it. Unset or empty allows every tool. */
50
84
  allowedNames?: string[];
85
+ /**
86
+ * Ignored: approval before a tool runs belongs to the agent loop.
87
+ *
88
+ * @deprecated Has never been read. It will be removed in 2.0.
89
+ */
51
90
  requiresApproval?: string[];
52
91
  }
92
+ /** Terms a response must not contain. */
53
93
  export interface ModerationConfig {
94
+ /** Turns moderation on. Off by default. */
54
95
  enabled?: boolean;
96
+ /** Terms matched literally, ignoring case. */
55
97
  forbiddenTerms?: string[];
98
+ /** What a match does: block the response, record it, or redact the term. Defaults to recording it. */
56
99
  onViolation?: 'block' | 'flag' | 'redact';
57
100
  }
101
+ /** Data-loss prevention on output. */
58
102
  export interface DLPConfig {
103
+ /** Turns the checks on. On by default. */
59
104
  enabled?: boolean;
105
+ /** Terms that must not leave, such as project code names. A match is recorded as a high finding. */
60
106
  proprietaryTerms?: string[];
107
+ /** Redacts database connection strings. On by default. */
61
108
  connectionStrings?: boolean;
62
109
  }
110
+ /** Topics a response must stay out of, matched as text, ignoring case. */
63
111
  export interface TopicGuardrailConfig {
112
+ /** The topics. */
64
113
  forbiddenTopics?: string[];
114
+ /** What a match does: block the response, or record it. Defaults to recording it. */
65
115
  onViolation?: 'block' | 'flag';
66
116
  }
117
+ /**
118
+ * A cheap grounding check on output: the share of the response's longer words that also appear in
119
+ * the context. A low share is recorded as a medium finding; it never blocks.
120
+ */
67
121
  export interface GroundingConfig {
122
+ /** Turns the check on. Off by default. */
68
123
  enabled?: boolean;
124
+ /** The context the response should draw from. */
69
125
  context?: string[];
126
+ /** Smallest acceptable share, from 0 to 1. Defaults to 0.1. */
70
127
  minOverlapRatio?: number;
71
128
  }
72
129
  /** Guardrails for input and output, from a preset, a level, or detailed settings. */
@@ -77,20 +134,32 @@ export interface SecurityConfig {
77
134
  level?: SecurityLevel;
78
135
  /** Input guardrails: length, injection, PII, secrets, URLs, and tool policy. */
79
136
  input?: {
137
+ /** Longest the request's text may be, in characters; longer blocks it. */
80
138
  maxContentLength?: number;
139
+ /** Prompt-injection detection, from the `standard` level up. */
81
140
  injectionDetection?: InjectionDetectionConfig;
141
+ /** Personal data detection, from the `standard` level up. */
82
142
  pii?: PIIConfig;
143
+ /** Credential detection. */
83
144
  secrets?: SecretDetectionConfig;
145
+ /** Risky URL detection. */
84
146
  urls?: UrlRiskConfig;
147
+ /** Which tools a request may carry. */
85
148
  tools?: ToolPolicyConfig;
86
149
  };
87
150
  /** Output guardrails: length, PII redaction, moderation, DLP, topics, and grounding. */
88
151
  output?: {
152
+ /** Longest the response may be, in characters; longer is cut, and the cut recorded. */
89
153
  maxContentLength?: number;
154
+ /** Redacts secrets and personal data from the response and its tool-call arguments. On by default. */
90
155
  piiRedaction?: boolean;
156
+ /** Forbidden terms. */
91
157
  moderation?: ModerationConfig;
158
+ /** Connection strings and proprietary terms. */
92
159
  dlp?: DLPConfig;
160
+ /** Forbidden topics. */
93
161
  topics?: TopicGuardrailConfig;
162
+ /** Word overlap with a context. */
94
163
  grounding?: GroundingConfig;
95
164
  };
96
165
  }
@@ -32,7 +32,7 @@ export interface SummarizeVerifyFormatOptions {
32
32
  model: string;
33
33
  /** The content to summarize. */
34
34
  input: string;
35
- /** Sources to check the summary against. Without them, verification is skipped. */
35
+ /** Sources to check the summary against. Without them, the verify step is skipped. */
36
36
  verifyContext?: string[];
37
37
  /** Format of the final answer, such as a JSON schema. */
38
38
  responseFormat?: ResponseFormatConfig;
@@ -86,8 +86,8 @@ export interface CompareOptions {
86
86
  criteria?: string[];
87
87
  }
88
88
  /**
89
- * Summarizes content, verifies the summary against sources when given, then formats it: three
90
- * completions, each step returned.
89
+ * Summarizes content, verifies the summary against sources when given, then formats it: two
90
+ * completions, or three to four with verification, each step returned.
91
91
  */
92
92
  export declare function summarizeVerifyFormat(client: WorkflowClient, options: SummarizeVerifyFormatOptions): Promise<WorkflowResult>;
93
93
  /**
@@ -20,17 +20,28 @@ async function summarizeVerifyFormat(client, options) {
20
20
  ],
21
21
  }, { chainOfThought: 'private' }));
22
22
  steps.push({ step: 'summarize', response: summary });
23
- const verified = options.verifyContext?.length
24
- ? await (0, verification_js_1.completeVerified)(client, {
23
+ let verified = summary;
24
+ if (options.verifyContext?.length) {
25
+ verified = await (0, verification_js_1.completeVerified)(client, {
25
26
  model: options.model,
26
- messages: [{ role: 'user', content: summary.content }],
27
- responseFormat: toRequestResponseFormat(options.responseFormat),
27
+ messages: [
28
+ {
29
+ role: 'user',
30
+ content: [
31
+ 'Restate this summary, keeping only what the sources support.',
32
+ 'Sources:',
33
+ ...options.verifyContext.map((item, index) => `[source-${index + 1}] ${item}`),
34
+ 'Summary:',
35
+ summary.content,
36
+ ].join('\n'),
37
+ },
38
+ ],
28
39
  }, {
29
40
  context: options.verifyContext,
30
41
  repair: true,
31
- })
32
- : summary;
33
- steps.push({ step: 'verify', response: verified });
42
+ });
43
+ steps.push({ step: 'verify', response: verified });
44
+ }
34
45
  const formatted = await client.complete({
35
46
  model: options.model,
36
47
  messages: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-ai-pro",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "Typed AI framework for TypeScript: provider routing and failover, parallel agent graphs with checkpoints, durable operations, guardrails, and cost control",
5
5
  "type": "module",
6
6
  "main": "dist-cjs/index.js",
@@ -1017,7 +1017,12 @@
1017
1017
  "example:agent": "tsx examples/agent.ts",
1018
1018
  "example:realtime": "tsx examples/realtime-scheduling.ts",
1019
1019
  "size:check": "node scripts/check-subpath-sizes.mjs",
1020
- "docs:check": "node scripts/check-doc-coverage.mjs",
1020
+ "docs:check": "node scripts/check-doc-coverage.mjs && node scripts/check-doc-coverage.mjs --package studio",
1021
+ "studio:build": "node studio/scripts/build.mjs",
1022
+ "studio:typecheck": "tsc -p studio/tsconfig.json",
1023
+ "studio:demo": "node studio/scripts/run.mjs studio/example/start.ts",
1024
+ "test:studio": "node studio/scripts/run.mjs test",
1025
+ "test:studio-install": "node studio/scripts/install-smoke.mjs",
1021
1026
  "docs:guides": "node scripts/check-guide-coverage.mjs",
1022
1027
  "docs:update": "node scripts/check-guide-coverage.mjs --update",
1023
1028
  "size:update": "node scripts/check-subpath-sizes.mjs --update",
@@ -1030,7 +1035,7 @@
1030
1035
  "typecheck:tests": "tsc -p tsconfig.tests.json --noEmit",
1031
1036
  "typecheck:examples": "tsc -p tsconfig.examples.json --noEmit",
1032
1037
  "typecheck:examples:browser": "tsc -p tsconfig.examples.browser.json --noEmit",
1033
- "test": "npm run build && npm run test:unit && npm run test:conformance:mock && npm run test:conformance:images && npm run size:check && npm run docs:check && npm run docs:guides && npm run test:smoke && npm run test:api && npm run test:types",
1038
+ "test": "npm run build && npm run test:unit && npm run test:conformance:mock && npm run test:conformance:images && npm run size:check && npm run docs:check && npm run docs:guides && npm run test:studio && npm run test:smoke && npm run test:api && npm run test:types",
1034
1039
  "test:unit": "node --import tsx --test tests/unit.ts tests/*.test.ts",
1035
1040
  "test:coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-include=src/**/*.ts --test-coverage-lines=82 --test-coverage-branches=67 --test-coverage-functions=73 tests/unit.ts tests/*.test.ts",
1036
1041
  "test:smoke": "node tests/package-smoke.mjs",
@@ -1041,8 +1046,8 @@
1041
1046
  "test:conformance:real": "tsx tests/conformance.real.ts",
1042
1047
  "bench:graph": "node scripts/bench-graph.mjs",
1043
1048
  "pack:dry-run": "npm pack --dry-run --cache .tmp-nexus-ai-tests/npm-cache",
1044
- "check": "npm run format:check && npm run registry:check && npm run lint && npm test && npm run typecheck:tests && npm run typecheck:examples && npm run typecheck:examples:browser && npm run test:coverage",
1045
- "check:release": "npm run check && npm run bench:graph && npm run pack:dry-run && npm run test:clean-install",
1049
+ "check": "npm run format:check && npm run registry:check && npm run lint && npm test && npm run typecheck:tests && npm run studio:typecheck && npm run typecheck:examples && npm run typecheck:examples:browser && npm run test:coverage",
1050
+ "check:release": "npm run check && npm run bench:graph && npm run pack:dry-run && npm run test:clean-install && npm run test:studio-install",
1046
1051
  "prepack": "npm run build",
1047
1052
  "prepublishOnly": "npm run check:release",
1048
1053
  "build:esm": "tsc --removeComments --declaration false && tsc --emitDeclarationOnly",