nexus-ai-pro 2.2.0 → 2.3.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 (135) hide show
  1. package/API_STABILITY.md +39 -0
  2. package/CHANGELOG.md +142 -1
  3. package/README.md +10 -5
  4. package/dist/agent/capabilities.d.ts +39 -0
  5. package/dist/agent/capabilities.js +36 -0
  6. package/dist/agent/create-agent.d.ts +61 -11
  7. package/dist/agent/create-agent.js +136 -22
  8. package/dist/agent/index.d.ts +3 -2
  9. package/dist/agent/index.js +1 -1
  10. package/dist/agent/middleware/approval.d.ts +26 -0
  11. package/dist/agent/middleware/approval.js +23 -0
  12. package/dist/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
  13. package/dist/agent/{middleware.js → middleware/basic.js} +3 -5
  14. package/dist/agent/middleware/context-editor.d.ts +52 -0
  15. package/dist/agent/middleware/context-editor.js +66 -0
  16. package/dist/agent/middleware/filesystem.d.ts +44 -0
  17. package/dist/agent/middleware/filesystem.js +83 -0
  18. package/dist/agent/middleware/index.d.ts +19 -0
  19. package/dist/agent/middleware/index.js +9 -0
  20. package/dist/agent/middleware/limits.d.ts +42 -0
  21. package/dist/agent/middleware/limits.js +52 -0
  22. package/dist/agent/middleware/models.d.ts +48 -0
  23. package/dist/agent/middleware/models.js +44 -0
  24. package/dist/agent/middleware/pii.d.ts +67 -0
  25. package/dist/agent/middleware/pii.js +173 -0
  26. package/dist/agent/middleware/retry.d.ts +66 -0
  27. package/dist/agent/middleware/retry.js +59 -0
  28. package/dist/agent/middleware/shared.d.ts +41 -0
  29. package/dist/agent/middleware/shared.js +73 -0
  30. package/dist/agent/middleware/tool-selector.d.ts +64 -0
  31. package/dist/agent/middleware/tool-selector.js +106 -0
  32. package/dist/agent/permissions.d.ts +113 -0
  33. package/dist/agent/permissions.js +180 -0
  34. package/dist/agent/sandbox.d.ts +151 -0
  35. package/dist/agent/sandbox.js +257 -0
  36. package/dist/agent/tool.d.ts +8 -3
  37. package/dist/agent/tool.js +14 -3
  38. package/dist/deep-agent/index.d.ts +109 -0
  39. package/dist/deep-agent/index.js +216 -0
  40. package/dist/graph/functional.d.ts +9 -0
  41. package/dist/graph/functional.js +20 -3
  42. package/dist/graph/graph.js +25 -4
  43. package/dist/graph/lint.d.ts +4 -1
  44. package/dist/graph/lint.js +35 -0
  45. package/dist/postgres/fulltext.d.ts +67 -0
  46. package/dist/postgres/fulltext.js +91 -0
  47. package/dist/rag/elasticsearch.d.ts +71 -0
  48. package/dist/rag/elasticsearch.js +130 -0
  49. package/dist/rag/pipeline.d.ts +140 -0
  50. package/dist/rag/pipeline.js +187 -0
  51. package/dist/rag/rerankers.d.ts +92 -0
  52. package/dist/rag/rerankers.js +97 -0
  53. package/dist/rag/retrievers.d.ts +12 -1
  54. package/dist/server/assistant.d.ts +5 -1
  55. package/dist/server/assistant.js +4 -2
  56. package/dist/server/auth.d.ts +250 -0
  57. package/dist/server/auth.js +392 -0
  58. package/dist/store/memory.d.ts +10 -0
  59. package/dist/store/memory.js +10 -0
  60. package/dist/types/graph.d.ts +38 -0
  61. package/dist/types/messages.d.ts +36 -2
  62. package/dist/types/principal.d.ts +21 -0
  63. package/dist/types/principal.js +1 -0
  64. package/dist/types/server.d.ts +2 -9
  65. package/dist/utils/tenant.d.ts +11 -0
  66. package/dist/utils/tenant.js +14 -0
  67. package/dist/version.d.ts +1 -1
  68. package/dist/version.js +1 -1
  69. package/dist-cjs/agent/capabilities.d.ts +39 -0
  70. package/dist-cjs/agent/capabilities.js +41 -0
  71. package/dist-cjs/agent/create-agent.d.ts +61 -11
  72. package/dist-cjs/agent/create-agent.js +136 -22
  73. package/dist-cjs/agent/index.d.ts +3 -2
  74. package/dist-cjs/agent/index.js +4 -4
  75. package/dist-cjs/agent/middleware/approval.d.ts +26 -0
  76. package/dist-cjs/agent/middleware/approval.js +26 -0
  77. package/dist-cjs/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
  78. package/dist-cjs/agent/{middleware.js → middleware/basic.js} +3 -5
  79. package/dist-cjs/agent/middleware/context-editor.d.ts +52 -0
  80. package/dist-cjs/agent/middleware/context-editor.js +70 -0
  81. package/dist-cjs/agent/middleware/filesystem.d.ts +44 -0
  82. package/dist-cjs/agent/middleware/filesystem.js +86 -0
  83. package/dist-cjs/agent/middleware/index.d.ts +19 -0
  84. package/dist-cjs/agent/middleware/index.js +28 -0
  85. package/dist-cjs/agent/middleware/limits.d.ts +42 -0
  86. package/dist-cjs/agent/middleware/limits.js +57 -0
  87. package/dist-cjs/agent/middleware/models.d.ts +48 -0
  88. package/dist-cjs/agent/middleware/models.js +48 -0
  89. package/dist-cjs/agent/middleware/pii.d.ts +67 -0
  90. package/dist-cjs/agent/middleware/pii.js +178 -0
  91. package/dist-cjs/agent/middleware/retry.d.ts +66 -0
  92. package/dist-cjs/agent/middleware/retry.js +63 -0
  93. package/dist-cjs/agent/middleware/shared.d.ts +41 -0
  94. package/dist-cjs/agent/middleware/shared.js +83 -0
  95. package/dist-cjs/agent/middleware/tool-selector.d.ts +64 -0
  96. package/dist-cjs/agent/middleware/tool-selector.js +109 -0
  97. package/dist-cjs/agent/permissions.d.ts +113 -0
  98. package/dist-cjs/agent/permissions.js +189 -0
  99. package/dist-cjs/agent/sandbox.d.ts +151 -0
  100. package/dist-cjs/agent/sandbox.js +297 -0
  101. package/dist-cjs/agent/tool.d.ts +8 -3
  102. package/dist-cjs/agent/tool.js +14 -3
  103. package/dist-cjs/deep-agent/index.d.ts +109 -0
  104. package/dist-cjs/deep-agent/index.js +219 -0
  105. package/dist-cjs/graph/functional.d.ts +9 -0
  106. package/dist-cjs/graph/functional.js +20 -3
  107. package/dist-cjs/graph/graph.js +25 -4
  108. package/dist-cjs/graph/lint.d.ts +4 -1
  109. package/dist-cjs/graph/lint.js +35 -0
  110. package/dist-cjs/postgres/fulltext.d.ts +67 -0
  111. package/dist-cjs/postgres/fulltext.js +129 -0
  112. package/dist-cjs/rag/elasticsearch.d.ts +71 -0
  113. package/dist-cjs/rag/elasticsearch.js +135 -0
  114. package/dist-cjs/rag/pipeline.d.ts +140 -0
  115. package/dist-cjs/rag/pipeline.js +191 -0
  116. package/dist-cjs/rag/rerankers.d.ts +92 -0
  117. package/dist-cjs/rag/rerankers.js +107 -0
  118. package/dist-cjs/rag/retrievers.d.ts +12 -1
  119. package/dist-cjs/server/assistant.d.ts +5 -1
  120. package/dist-cjs/server/assistant.js +4 -2
  121. package/dist-cjs/server/auth.d.ts +250 -0
  122. package/dist-cjs/server/auth.js +402 -0
  123. package/dist-cjs/store/memory.d.ts +10 -0
  124. package/dist-cjs/store/memory.js +10 -0
  125. package/dist-cjs/types/graph.d.ts +38 -0
  126. package/dist-cjs/types/messages.d.ts +36 -2
  127. package/dist-cjs/types/principal.d.ts +21 -0
  128. package/dist-cjs/types/principal.js +2 -0
  129. package/dist-cjs/types/server.d.ts +2 -9
  130. package/dist-cjs/utils/tenant.d.ts +11 -0
  131. package/dist-cjs/utils/tenant.js +15 -0
  132. package/dist-cjs/version.d.ts +1 -1
  133. package/dist-cjs/version.js +1 -1
  134. package/llms.txt +4 -3
  135. package/package.json +118 -1
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.turnOf = turnOf;
4
+ exports.modelCallsInTurn = modelCallsInTurn;
5
+ exports.callPosition = callPosition;
6
+ exports.textOf = textOf;
7
+ exports.backoffDelay = backoffDelay;
8
+ exports.sleep = sleep;
9
+ exports.isCancellation = isCancellation;
10
+ exports.shortHash = shortHash;
11
+ function turnOf(messages) {
12
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
13
+ if (messages[index]?.role === 'user')
14
+ return messages.slice(index + 1);
15
+ }
16
+ return messages;
17
+ }
18
+ function modelCallsInTurn(messages) {
19
+ return turnOf(messages).filter((message) => message.role === 'assistant').length;
20
+ }
21
+ function callPosition(messages, call) {
22
+ let position = 0;
23
+ for (const message of turnOf(messages)) {
24
+ for (const made of message.toolCalls ?? []) {
25
+ if (made.function.name !== call.name)
26
+ continue;
27
+ position += 1;
28
+ if (made.id === call.id)
29
+ return position;
30
+ }
31
+ }
32
+ return position + 1;
33
+ }
34
+ function textOf(content) {
35
+ if (typeof content === 'string')
36
+ return content;
37
+ return content
38
+ .filter((part) => part.type === 'text')
39
+ .map((part) => part.text)
40
+ .join('\n');
41
+ }
42
+ function backoffDelay(attempt, backoff = {}) {
43
+ const initial = backoff.initialMs ?? 500;
44
+ const delay = Math.min(backoff.maxMs ?? 10_000, initial * (backoff.factor ?? 2) ** Math.max(0, attempt - 1));
45
+ return backoff.jitter === false ? delay : delay / 2 + Math.random() * (delay / 2);
46
+ }
47
+ function sleep(ms, signal) {
48
+ if (ms <= 0)
49
+ return signal?.aborted ? Promise.reject(signal.reason) : Promise.resolve();
50
+ return new Promise((resolve, reject) => {
51
+ if (signal?.aborted) {
52
+ reject(signal.reason);
53
+ return;
54
+ }
55
+ const onAbort = () => {
56
+ clearTimeout(timer);
57
+ reject(signal?.reason);
58
+ };
59
+ const timer = setTimeout(() => {
60
+ signal?.removeEventListener('abort', onAbort);
61
+ resolve();
62
+ }, ms);
63
+ signal?.addEventListener('abort', onAbort, { once: true });
64
+ });
65
+ }
66
+ function isCancellation(error, signal) {
67
+ if (signal?.aborted)
68
+ return true;
69
+ const name = error?.name;
70
+ return name === 'AbortError';
71
+ }
72
+ function shortHash(text) {
73
+ let h1 = 0xdeadbeef;
74
+ let h2 = 0x41c6ce57;
75
+ for (let index = 0; index < text.length; index += 1) {
76
+ const code = text.charCodeAt(index);
77
+ h1 = Math.imul(h1 ^ code, 2654435761);
78
+ h2 = Math.imul(h2 ^ code, 1597334677);
79
+ }
80
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
81
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
82
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(14, '0').slice(-8);
83
+ }
@@ -0,0 +1,64 @@
1
+ import type { CompletionRequest, ToolDefinition } from '../../types/messages.js';
2
+ import type { AgentMiddleware, AgentMiddlewareContext } from '../create-agent.js';
3
+ /** What a rule chooses tools from. */
4
+ export interface ToolSelectionContext extends AgentMiddlewareContext {
5
+ /** The request about to be sent. */
6
+ request: CompletionRequest;
7
+ /** Every tool the request offers. */
8
+ tools: readonly ToolDefinition[];
9
+ /** What the turn is about: the text `query` returned. */
10
+ query: string;
11
+ }
12
+ /** Options for `toolSelector()`. Give it `select`, `embed`, or both. */
13
+ export interface ToolSelectorOptions {
14
+ /**
15
+ * A rule: the names of the tools to send. With `embed` too, the rule narrows the candidates and
16
+ * similarity ranks what is left.
17
+ */
18
+ select?: (context: ToolSelectionContext) => readonly string[] | Promise<readonly string[]>;
19
+ /**
20
+ * Embeds texts, for choosing by similarity: any embedding function, such as one from
21
+ * `toEmbeddingFunction()`. Each tool is embedded once, from `describe`; the query once per turn.
22
+ */
23
+ embed?: (texts: string[]) => Promise<readonly (readonly number[])[]> | readonly (readonly number[])[];
24
+ /** The most tools sent, `always` included. Defaults to 12. */
25
+ maxTools?: number;
26
+ /** The lowest similarity a tool needs to be sent, from -1 to 1. Defaults to none. */
27
+ minScore?: number;
28
+ /** Tools sent on every call, whatever the turn is about. */
29
+ always?: readonly string[];
30
+ /**
31
+ * Keeps every tool the model already called this turn, so a tool it is halfway through using
32
+ * never disappears from under it. Defaults to true.
33
+ */
34
+ keepUsed?: boolean;
35
+ /** What the turn is about. Defaults to the latest user message. */
36
+ query?: (context: AgentMiddlewareContext & {
37
+ request: CompletionRequest;
38
+ }) => string;
39
+ /** The text a tool is embedded from. Defaults to its name and description. */
40
+ describe?: (tool: ToolDefinition) => string;
41
+ /** Called with each selection, for logs and for measuring what the selector saves. */
42
+ onSelect?: (selection: ToolSelection) => void;
43
+ }
44
+ /** What `toolSelector()` sent for one call. */
45
+ export interface ToolSelection {
46
+ /** The query the tools were chosen for. */
47
+ query: string;
48
+ /** The names of the tools sent. */
49
+ selected: string[];
50
+ /** How many tools the request offered. */
51
+ offered: number;
52
+ /** Each ranked tool's similarity to the query, when tools were chosen by similarity. */
53
+ scores?: Record<string, number>;
54
+ }
55
+ /**
56
+ * Sends a model only the tools relevant to the turn.
57
+ *
58
+ * An agent with 150 tools otherwise sends 150 schemas on every call: the tokens are paid each time,
59
+ * the call is slower, and a model choosing among that many picks worse. A rule, embedding
60
+ * similarity, or both choose a dozen; tools named in `always`, the tool a `toolChoice` forces, and
61
+ * the tools the model already used this turn are always kept. The agent can still run every tool —
62
+ * only what the model is offered changes.
63
+ */
64
+ export declare function toolSelector(options: ToolSelectorOptions): AgentMiddleware;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toolSelector = toolSelector;
4
+ const shared_js_1 = require("./shared.js");
5
+ function toolSelector(options) {
6
+ if (!options.select && !options.embed) {
7
+ throw new TypeError('toolSelector() needs a select rule, an embed function, or both');
8
+ }
9
+ const maxTools = Math.max(1, options.maxTools ?? 12);
10
+ const describe = options.describe ?? ((tool) => `${tool.name}: ${tool.description}`);
11
+ const vectors = new Map();
12
+ let lastQuery;
13
+ const embedTools = async (tools) => {
14
+ const missing = [...new Set(tools.map(describe).filter((text) => !vectors.has(text)))];
15
+ if (missing.length === 0 || !options.embed)
16
+ return;
17
+ const embedded = await options.embed(missing);
18
+ missing.forEach((text, index) => {
19
+ const vector = embedded[index];
20
+ if (vector)
21
+ vectors.set(text, normalize(vector));
22
+ });
23
+ };
24
+ const embedQuery = async (text) => {
25
+ if (!options.embed || !text.trim())
26
+ return undefined;
27
+ if (lastQuery?.text === text)
28
+ return lastQuery.vector;
29
+ const [vector] = await options.embed([text]);
30
+ if (!vector)
31
+ return undefined;
32
+ lastQuery = { text, vector: normalize(vector) };
33
+ return lastQuery.vector;
34
+ };
35
+ return {
36
+ name: 'tool-selector',
37
+ async beforeModel(context) {
38
+ const { request } = context;
39
+ const offered = request.tools ?? [];
40
+ if (offered.length === 0 || (!options.select && offered.length <= maxTools))
41
+ return;
42
+ const query = options.query?.(context) ?? latestUserText(context.state.messages);
43
+ const kept = new Set(options.always ?? []);
44
+ if (request.toolChoice && typeof request.toolChoice === 'object')
45
+ kept.add(request.toolChoice.name);
46
+ if (options.keepUsed !== false) {
47
+ for (const message of (0, shared_js_1.turnOf)(context.state.messages)) {
48
+ for (const call of message.toolCalls ?? [])
49
+ kept.add(call.function.name);
50
+ }
51
+ }
52
+ let candidates = offered.filter((tool) => !kept.has(tool.name));
53
+ if (options.select) {
54
+ const chosen = new Set(await options.select({ ...context, tools: offered, query }));
55
+ candidates = candidates.filter((tool) => chosen.has(tool.name));
56
+ }
57
+ let scores;
58
+ const room = Math.max(0, maxTools - kept.size);
59
+ if (options.embed) {
60
+ const vector = await embedQuery(query);
61
+ if (vector) {
62
+ await embedTools(candidates);
63
+ scores = {};
64
+ const ranked = candidates
65
+ .map((tool) => ({ tool, score: dot(vector, vectors.get(describe(tool)) ?? []) }))
66
+ .filter((entry) => options.minScore === undefined || entry.score >= options.minScore)
67
+ .sort((left, right) => right.score - left.score);
68
+ for (const entry of ranked)
69
+ scores[entry.tool.name] = round(entry.score);
70
+ candidates = ranked.map((entry) => entry.tool);
71
+ }
72
+ }
73
+ const names = new Set([...kept, ...candidates.slice(0, room).map((tool) => tool.name)]);
74
+ const tools = offered.filter((tool) => names.has(tool.name));
75
+ options.onSelect?.({
76
+ query,
77
+ selected: tools.map((tool) => tool.name),
78
+ offered: offered.length,
79
+ ...(scores ? { scores } : {}),
80
+ });
81
+ return { ...request, tools };
82
+ },
83
+ };
84
+ }
85
+ function latestUserText(messages) {
86
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
87
+ const message = messages[index];
88
+ if (message?.role === 'user')
89
+ return (0, shared_js_1.textOf)(message.content);
90
+ }
91
+ return '';
92
+ }
93
+ function normalize(vector) {
94
+ let norm = 0;
95
+ for (const value of vector)
96
+ norm += value * value;
97
+ norm = Math.sqrt(norm);
98
+ return norm === 0 ? vector : vector.map((value) => value / norm);
99
+ }
100
+ function dot(left, right) {
101
+ let total = 0;
102
+ const length = Math.min(left.length, right.length);
103
+ for (let index = 0; index < length; index += 1)
104
+ total += left[index] * right[index];
105
+ return total;
106
+ }
107
+ function round(value) {
108
+ return Math.round(value * 1000) / 1000;
109
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Permission policies: what an agent's tools may do, decided per call from what each call declares.
3
+ *
4
+ * Danger belongs to what a tool does, not to its name. A tool declares capabilities — a path it
5
+ * writes, a host it calls, a command it runs — and a policy grants filesystem paths, network hosts,
6
+ * and shell commands, and decides every call: allow it, deny it, or ask a person first. A call is
7
+ * denied when any of its capabilities is, asked about when any needs approval, and allowed only when
8
+ * every one is granted.
9
+ */
10
+ import type { Principal } from '../types/principal.js';
11
+ export { capabilitiesOf, isSensitiveCapability, type ParsedCapability, parseCapability } from './capabilities.js';
12
+ /** What a policy decides for one call: run it, refuse it, or interrupt for a person's approval. */
13
+ export type PermissionDecision = 'allow' | 'ask' | 'deny';
14
+ /** One tool call, as a policy sees it. */
15
+ export interface PermissionRequest {
16
+ /** The tool's name. */
17
+ tool: string;
18
+ /** The call's arguments. */
19
+ args: Record<string, unknown>;
20
+ /** What the call declares it does, or `undefined` when the tool declares nothing. */
21
+ capabilities: readonly string[] | undefined;
22
+ /** Who the run is for, when it has a principal, so a policy of your own can decide by role or scope. */
23
+ principal?: Readonly<Principal>;
24
+ }
25
+ /** A policy's decision, with the reason a person or a model reads. */
26
+ export interface PermissionVerdict {
27
+ /** The decision. */
28
+ decision: PermissionDecision;
29
+ /** Why, naming the capability that decided it. */
30
+ reason: string;
31
+ /** The capability that decided it, when one did. */
32
+ capability?: string;
33
+ }
34
+ /** Anything that decides tool calls. `createAgent({ permissions })` takes one. */
35
+ export interface PermissionPolicyLike {
36
+ /** Decides one call. */
37
+ decide(request: PermissionRequest): PermissionVerdict | Promise<PermissionVerdict>;
38
+ }
39
+ /** What a policy grants. Anything not granted is denied unless `ungranted` says otherwise. */
40
+ export interface PermissionRules {
41
+ /**
42
+ * Paths a tool may read and write, as globs: `*` within one path segment, `**` across segments, so
43
+ * `/workspace/**` is the workspace and everything in it. A write grant also allows reading.
44
+ */
45
+ filesystem?: {
46
+ read?: readonly string[];
47
+ write?: readonly string[];
48
+ };
49
+ /** Hosts a tool may reach: `api.github.com`, `*.openai.com` for its subdomains, or `*` for any. */
50
+ network?: {
51
+ allow?: readonly string[];
52
+ };
53
+ /**
54
+ * Commands a tool may run, each the start of a command line: `git` allows every git command, and
55
+ * `npm test` only that. A command line that chains, pipes, redirects, or substitutes is never
56
+ * granted by these, only by `*`.
57
+ */
58
+ shell?: {
59
+ allow?: readonly string[];
60
+ };
61
+ /** Running code a tool was given. Defaults to `deny`; a sandboxed interpreter is the place for `ask` or `allow`. */
62
+ code?: PermissionDecision;
63
+ /** Decisions for the application's own capabilities, by kind or by full capability: `{ payments: 'ask' }`. */
64
+ custom?: Readonly<Record<string, PermissionDecision>>;
65
+ /**
66
+ * Capabilities that need a person's approval even when granted, as patterns: `shell`,
67
+ * `filesystem:write`, `network:*.stripe.com`, `payments`.
68
+ */
69
+ ask?: readonly string[];
70
+ /** A tool that declares no capabilities. Defaults to `deny`: a policy cannot judge what it cannot see. */
71
+ undeclared?: PermissionDecision;
72
+ /** A capability no grant covers. Defaults to `deny`. */
73
+ ungranted?: 'ask' | 'deny';
74
+ /** Decisions per tool name, applied before anything else. */
75
+ tools?: Readonly<Record<string, PermissionDecision>>;
76
+ /** What a relative path is resolved against. Defaults to `/`. */
77
+ root?: string;
78
+ }
79
+ /**
80
+ * Builds a policy from what it grants.
81
+ *
82
+ * ```ts
83
+ * const permissions = permissionPolicy({
84
+ * filesystem: { read: ['/workspace/**'], write: ['/workspace/output/**'] },
85
+ * network: { allow: ['api.github.com', '*.openai.com'] },
86
+ * shell: { allow: ['git', 'npm test'] },
87
+ * ask: ['shell', 'filesystem:write'],
88
+ * });
89
+ * const agent = createAgent({ client, tools, permissions, checkpointer });
90
+ * ```
91
+ */
92
+ export declare function permissionPolicy(rules: PermissionRules): PermissionPolicy;
93
+ /** A policy built from `PermissionRules`. Deterministic: the same call always gets the same decision. */
94
+ export declare class PermissionPolicy implements PermissionPolicyLike {
95
+ /** What the policy grants. */
96
+ readonly rules: PermissionRules;
97
+ constructor(
98
+ /** What the policy grants. */
99
+ rules: PermissionRules);
100
+ /** Decides one call from its declared capabilities. */
101
+ decide(request: PermissionRequest): PermissionVerdict;
102
+ /** Decides one capability: granted or not, then whether it needs approval. */
103
+ decideOne(capability: string): PermissionVerdict;
104
+ private shown;
105
+ private root;
106
+ private granted;
107
+ }
108
+ /**
109
+ * A path made absolute and normalized, with `.` and `..` resolved, so `/workspace/../etc` is
110
+ * `/etc` before any grant sees it. Backslashes count as separators. `null` for a path that could
111
+ * never be safe to judge, such as one holding a NUL.
112
+ */
113
+ export declare function normalizePath(path: string, root?: string): string | null;
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PermissionPolicy = exports.parseCapability = exports.isSensitiveCapability = exports.capabilitiesOf = void 0;
4
+ exports.permissionPolicy = permissionPolicy;
5
+ exports.normalizePath = normalizePath;
6
+ const capabilities_js_1 = require("./capabilities.js");
7
+ var capabilities_js_2 = require("./capabilities.js");
8
+ Object.defineProperty(exports, "capabilitiesOf", { enumerable: true, get: function () { return capabilities_js_2.capabilitiesOf; } });
9
+ Object.defineProperty(exports, "isSensitiveCapability", { enumerable: true, get: function () { return capabilities_js_2.isSensitiveCapability; } });
10
+ Object.defineProperty(exports, "parseCapability", { enumerable: true, get: function () { return capabilities_js_2.parseCapability; } });
11
+ const ORDER = { allow: 0, ask: 1, deny: 2 };
12
+ const CHAINING = /[;&|`$<>\n\r]/;
13
+ function permissionPolicy(rules) {
14
+ return new PermissionPolicy(rules);
15
+ }
16
+ class PermissionPolicy {
17
+ rules;
18
+ constructor(rules) {
19
+ this.rules = rules;
20
+ }
21
+ decide(request) {
22
+ const forced = this.rules.tools?.[request.tool];
23
+ if (forced)
24
+ return { decision: forced, reason: `"${request.tool}" is set to ${forced} by name` };
25
+ if (request.capabilities === undefined) {
26
+ const decision = this.rules.undeclared ?? 'deny';
27
+ return {
28
+ decision,
29
+ reason: `"${request.tool}" declares no capabilities, so the policy cannot see what it does`,
30
+ };
31
+ }
32
+ let verdict = { decision: 'allow', reason: `every capability of "${request.tool}" is granted` };
33
+ for (const capability of request.capabilities) {
34
+ const next = this.decideOne(capability);
35
+ if (ORDER[next.decision] > ORDER[verdict.decision])
36
+ verdict = next;
37
+ if (verdict.decision === 'deny')
38
+ break;
39
+ }
40
+ return verdict;
41
+ }
42
+ decideOne(capability) {
43
+ const parsed = (0, capabilities_js_1.parseCapability)(capability);
44
+ const shown = this.shown(capability, parsed);
45
+ const granted = this.granted(parsed);
46
+ if (granted === 'deny') {
47
+ return { decision: this.rules.ungranted ?? 'deny', reason: `${shown} is not granted`, capability };
48
+ }
49
+ if (granted === 'ask' ||
50
+ (this.rules.ask ?? []).some((pattern) => capabilityMatches(pattern, parsed, this.root()))) {
51
+ return { decision: 'ask', reason: `${shown} needs approval`, capability };
52
+ }
53
+ return { decision: 'allow', reason: `${shown} is granted`, capability };
54
+ }
55
+ shown(capability, parsed) {
56
+ if (parsed.kind !== 'filesystem' || parsed.target === undefined)
57
+ return capability;
58
+ const resolved = normalizePath(parsed.target, this.root());
59
+ return resolved === null ? capability : `filesystem:${parsed.access ?? 'write'}:${resolved}`;
60
+ }
61
+ root() {
62
+ return this.rules.root ?? '/';
63
+ }
64
+ granted(parsed) {
65
+ switch (parsed.kind) {
66
+ case 'filesystem': {
67
+ const path = parsed.target === undefined ? undefined : normalizePath(parsed.target, this.root());
68
+ if (path === null)
69
+ return 'deny';
70
+ const writes = this.rules.filesystem?.write ?? [];
71
+ const reads = [...(this.rules.filesystem?.read ?? []), ...writes];
72
+ const globs = parsed.access === 'read' ? reads : writes;
73
+ const matches = (glob) => path === undefined
74
+ ? glob === '**' || glob === '/**'
75
+ : globToRegExp(normalizePath(glob, this.root()) ?? '').test(path);
76
+ return globs.some(matches) ? 'allow' : 'deny';
77
+ }
78
+ case 'network': {
79
+ const host = parsed.target === undefined ? undefined : hostOf(parsed.target);
80
+ const allowed = this.rules.network?.allow ?? [];
81
+ return allowed.some((pattern) => hostMatches(pattern, host)) ? 'allow' : 'deny';
82
+ }
83
+ case 'shell': {
84
+ const allowed = this.rules.shell?.allow ?? [];
85
+ if (allowed.includes('*'))
86
+ return 'allow';
87
+ const command = parsed.target?.trim();
88
+ if (!command || CHAINING.test(command))
89
+ return 'deny';
90
+ const tokens = command.split(/\s+/);
91
+ return allowed.some((entry) => {
92
+ const prefix = entry.trim().split(/\s+/);
93
+ return prefix.every((token, index) => tokens[index] === token);
94
+ })
95
+ ? 'allow'
96
+ : 'deny';
97
+ }
98
+ case 'code':
99
+ return this.rules.code ?? 'deny';
100
+ default: {
101
+ const custom = this.rules.custom ?? {};
102
+ const full = parsed.target === undefined ? parsed.kind : `${parsed.kind}:${parsed.target}`;
103
+ return custom[full] ?? custom[parsed.kind] ?? 'deny';
104
+ }
105
+ }
106
+ }
107
+ }
108
+ exports.PermissionPolicy = PermissionPolicy;
109
+ function capabilityMatches(pattern, capability, root) {
110
+ const wanted = (0, capabilities_js_1.parseCapability)(pattern);
111
+ if (wanted.kind !== capability.kind)
112
+ return false;
113
+ if (wanted.access && wanted.access !== capability.access)
114
+ return false;
115
+ if (wanted.target === undefined)
116
+ return true;
117
+ if (capability.target === undefined)
118
+ return false;
119
+ if (capability.kind === 'network')
120
+ return hostMatches(wanted.target, hostOf(capability.target));
121
+ if (capability.kind === 'filesystem') {
122
+ const path = normalizePath(capability.target, root);
123
+ return path !== null && globToRegExp(normalizePath(wanted.target, root) ?? '').test(path);
124
+ }
125
+ return globToRegExp(wanted.target).test(capability.target);
126
+ }
127
+ function normalizePath(path, root = '/') {
128
+ if (path.includes('\0'))
129
+ return null;
130
+ const unified = path.replace(/\\/g, '/');
131
+ const absolute = /^([A-Za-z]:)?\//.test(unified)
132
+ ? unified
133
+ : `${root.replace(/\\/g, '/').replace(/\/+$/, '')}/${unified}`;
134
+ const drive = /^[A-Za-z]:/.exec(absolute)?.[0]?.toLowerCase() ?? '';
135
+ const parts = [];
136
+ for (const segment of absolute.slice(drive.length).split('/')) {
137
+ if (segment === '' || segment === '.')
138
+ continue;
139
+ if (segment === '..')
140
+ parts.pop();
141
+ else
142
+ parts.push(segment);
143
+ }
144
+ return `${drive}/${parts.join('/')}`;
145
+ }
146
+ function globToRegExp(glob) {
147
+ let source = '';
148
+ for (let index = 0; index < glob.length; index += 1) {
149
+ const char = glob[index];
150
+ if (char === '*' && glob[index + 1] === '*') {
151
+ const atEnd = index + 2 === glob.length;
152
+ if (atEnd && source.endsWith('/')) {
153
+ source = `${source.slice(0, -1)}(?:/.*)?`;
154
+ }
155
+ else {
156
+ source += '.*';
157
+ }
158
+ index += 1;
159
+ }
160
+ else if (char === '*') {
161
+ source += '[^/]*';
162
+ }
163
+ else if (char === '?') {
164
+ source += '[^/]';
165
+ }
166
+ else {
167
+ source += char.replace(/[.+^${}()|[\]\\]/g, '\\$&');
168
+ }
169
+ }
170
+ return new RegExp(`^${source}$`, 'i');
171
+ }
172
+ function hostOf(target) {
173
+ try {
174
+ return new URL(target.includes('://') ? target : `https://${target}`).hostname.toLowerCase();
175
+ }
176
+ catch {
177
+ return target.toLowerCase();
178
+ }
179
+ }
180
+ function hostMatches(pattern, host) {
181
+ const wanted = pattern.trim().toLowerCase();
182
+ if (wanted === '*')
183
+ return true;
184
+ if (host === undefined)
185
+ return false;
186
+ if (wanted.startsWith('*.'))
187
+ return host.endsWith(wanted.slice(1)) && host.length > wanted.length - 1;
188
+ return host === wanted;
189
+ }