nexus-ai-pro 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API_STABILITY.md +225 -0
- package/CHANGELOG.md +325 -1
- package/README.md +20 -6
- package/SECURITY.md +140 -0
- package/dist/adapter-kit/contracts.d.ts +87 -0
- package/dist/adapter-kit/contracts.js +237 -0
- package/dist/adapter-kit/errors.d.ts +39 -0
- package/dist/adapter-kit/errors.js +52 -0
- package/dist/adapter-kit/index.d.ts +144 -0
- package/dist/adapter-kit/index.js +178 -0
- package/dist/adapter-kit/semver.d.ts +12 -0
- package/dist/adapter-kit/semver.js +55 -0
- package/dist/agent/capabilities.d.ts +39 -0
- package/dist/agent/capabilities.js +36 -0
- package/dist/agent/create-agent.d.ts +61 -11
- package/dist/agent/create-agent.js +136 -22
- package/dist/agent/index.d.ts +3 -2
- package/dist/agent/index.js +1 -1
- package/dist/agent/middleware/approval.d.ts +26 -0
- package/dist/agent/middleware/approval.js +23 -0
- package/dist/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
- package/dist/agent/{middleware.js → middleware/basic.js} +3 -5
- package/dist/agent/middleware/context-editor.d.ts +52 -0
- package/dist/agent/middleware/context-editor.js +66 -0
- package/dist/agent/middleware/filesystem.d.ts +44 -0
- package/dist/agent/middleware/filesystem.js +83 -0
- package/dist/agent/middleware/index.d.ts +19 -0
- package/dist/agent/middleware/index.js +9 -0
- package/dist/agent/middleware/limits.d.ts +42 -0
- package/dist/agent/middleware/limits.js +52 -0
- package/dist/agent/middleware/models.d.ts +48 -0
- package/dist/agent/middleware/models.js +44 -0
- package/dist/agent/middleware/pii.d.ts +67 -0
- package/dist/agent/middleware/pii.js +142 -0
- package/dist/agent/middleware/retry.d.ts +66 -0
- package/dist/agent/middleware/retry.js +59 -0
- package/dist/agent/middleware/shared.d.ts +41 -0
- package/dist/agent/middleware/shared.js +73 -0
- package/dist/agent/middleware/tool-selector.d.ts +64 -0
- package/dist/agent/middleware/tool-selector.js +106 -0
- package/dist/agent/permissions.d.ts +113 -0
- package/dist/agent/permissions.js +180 -0
- package/dist/agent/sandbox.d.ts +151 -0
- package/dist/agent/sandbox.js +257 -0
- package/dist/agent/tool.d.ts +8 -3
- package/dist/agent/tool.js +14 -3
- package/dist/core/secure-stream.js +4 -3
- package/dist/deep-agent/index.d.ts +109 -0
- package/dist/deep-agent/index.js +216 -0
- package/dist/embeddings/adapters.js +2 -1
- package/dist/evals/judge.js +2 -5
- package/dist/evaluate/cache.d.ts +0 -14
- package/dist/evaluate/cache.js +0 -30
- package/dist/evaluate/file-cache.d.ts +15 -0
- package/dist/evaluate/file-cache.js +30 -0
- package/dist/evaluate/index.d.ts +2 -1
- package/dist/evaluate/index.js +2 -1
- package/dist/evaluate/review.js +2 -2
- package/dist/evaluate/run.js +2 -2
- package/dist/evaluate/version.js +3 -3
- package/dist/graph/functional.d.ts +9 -0
- package/dist/graph/functional.js +20 -3
- package/dist/graph/graph.js +27 -6
- package/dist/graph/lint.d.ts +4 -1
- package/dist/graph/lint.js +35 -0
- package/dist/graph/node-cache.js +3 -3
- package/dist/hallucination/rag.d.ts +4 -1
- package/dist/hallucination/rag.js +6 -1
- package/dist/images/asset-stores.js +2 -3
- package/dist/images/asset-support.js +2 -2
- package/dist/images/assets.js +1 -2
- package/dist/images/evals.js +4 -4
- package/dist/insights/proposals.js +1 -2
- package/dist/insights/regressions.d.ts +26 -2
- package/dist/insights/regressions.js +129 -3
- package/dist/loaders/git.js +8 -1
- package/dist/loaders/web.js +7 -1
- package/dist/mcp/client.d.ts +14 -0
- package/dist/mcp/client.js +59 -1
- package/dist/mcp/registry.d.ts +6 -0
- package/dist/mcp/registry.js +7 -0
- package/dist/postgres/fulltext.d.ts +67 -0
- package/dist/postgres/fulltext.js +91 -0
- package/dist/prompts/file.js +28 -11
- package/dist/protocols/a2a.d.ts +235 -0
- package/dist/protocols/a2a.js +386 -0
- package/dist/protocols/acp.d.ts +87 -0
- package/dist/protocols/acp.js +322 -0
- package/dist/protocols/ag-ui.d.ts +120 -0
- package/dist/protocols/ag-ui.js +180 -0
- package/dist/protocols/shared.d.ts +80 -0
- package/dist/protocols/shared.js +49 -0
- package/dist/providers/anthropic.js +1 -0
- package/dist/providers/cohere.js +2 -2
- package/dist/providers/google.js +2 -11
- package/dist/providers/openai.js +1 -0
- package/dist/rag/elasticsearch.d.ts +71 -0
- package/dist/rag/elasticsearch.js +130 -0
- package/dist/rag/file-ingestion.js +15 -9
- package/dist/rag/pipeline.d.ts +140 -0
- package/dist/rag/pipeline.js +187 -0
- package/dist/rag/rerankers.d.ts +92 -0
- package/dist/rag/rerankers.js +97 -0
- package/dist/rag/retrievers.d.ts +12 -1
- package/dist/runtime/detect.d.ts +17 -0
- package/dist/runtime/detect.js +16 -0
- package/dist/runtime/index.d.ts +19 -0
- package/dist/runtime/index.js +7 -0
- package/dist/security/injection-detector.d.ts +2 -0
- package/dist/security/injection-detector.js +7 -1
- package/dist/security/input-guard.js +5 -2
- package/dist/security/output-guard.js +8 -4
- package/dist/security/pii-detector.js +22 -6
- package/dist/security/prompt-hardening.js +6 -2
- package/dist/security/semantic-injection-classifier.d.ts +2 -1
- package/dist/security/semantic-injection-classifier.js +31 -35
- package/dist/security/upload-scanner.d.ts +8 -0
- package/dist/security/upload-scanner.js +51 -9
- package/dist/server/assistant.d.ts +5 -1
- package/dist/server/assistant.js +4 -2
- package/dist/server/auth.d.ts +250 -0
- package/dist/server/auth.js +392 -0
- package/dist/server/deployments.d.ts +63 -1
- package/dist/server/deployments.js +75 -2
- package/dist/server/runs.d.ts +9 -0
- package/dist/server/runs.js +20 -0
- package/dist/server/server.js +5 -0
- package/dist/store/memory.d.ts +10 -0
- package/dist/store/memory.js +10 -0
- package/dist/testing/record.js +2 -2
- package/dist/tracing/stores.js +2 -1
- package/dist/tracing/tracer.js +2 -2
- package/dist/types/config.d.ts +20 -0
- package/dist/types/graph.d.ts +38 -0
- package/dist/types/messages.d.ts +36 -2
- package/dist/types/principal.d.ts +21 -0
- package/dist/types/principal.js +1 -0
- package/dist/types/server.d.ts +8 -9
- package/dist/utils/base64.d.ts +4 -0
- package/dist/utils/base64.js +21 -0
- package/dist/utils/checksums.d.ts +22 -0
- package/dist/utils/checksums.js +39 -0
- package/dist/utils/ids.d.ts +8 -0
- package/dist/utils/ids.js +26 -2
- package/dist/utils/sha256.d.ts +22 -0
- package/dist/utils/sha256.js +120 -0
- package/dist/utils/tenant.d.ts +11 -0
- package/dist/utils/tenant.js +14 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-cjs/adapter-kit/contracts.d.ts +87 -0
- package/dist-cjs/adapter-kit/contracts.js +243 -0
- package/dist-cjs/adapter-kit/errors.d.ts +39 -0
- package/dist-cjs/adapter-kit/errors.js +57 -0
- package/dist-cjs/adapter-kit/index.d.ts +144 -0
- package/dist-cjs/adapter-kit/index.js +194 -0
- package/dist-cjs/adapter-kit/semver.d.ts +12 -0
- package/dist-cjs/adapter-kit/semver.js +59 -0
- package/dist-cjs/agent/capabilities.d.ts +39 -0
- package/dist-cjs/agent/capabilities.js +41 -0
- package/dist-cjs/agent/create-agent.d.ts +61 -11
- package/dist-cjs/agent/create-agent.js +136 -22
- package/dist-cjs/agent/index.d.ts +3 -2
- package/dist-cjs/agent/index.js +4 -4
- package/dist-cjs/agent/middleware/approval.d.ts +26 -0
- package/dist-cjs/agent/middleware/approval.js +26 -0
- package/dist-cjs/agent/{middleware.d.ts → middleware/basic.d.ts} +5 -4
- package/dist-cjs/agent/{middleware.js → middleware/basic.js} +3 -5
- package/dist-cjs/agent/middleware/context-editor.d.ts +52 -0
- package/dist-cjs/agent/middleware/context-editor.js +70 -0
- package/dist-cjs/agent/middleware/filesystem.d.ts +44 -0
- package/dist-cjs/agent/middleware/filesystem.js +86 -0
- package/dist-cjs/agent/middleware/index.d.ts +19 -0
- package/dist-cjs/agent/middleware/index.js +28 -0
- package/dist-cjs/agent/middleware/limits.d.ts +42 -0
- package/dist-cjs/agent/middleware/limits.js +57 -0
- package/dist-cjs/agent/middleware/models.d.ts +48 -0
- package/dist-cjs/agent/middleware/models.js +48 -0
- package/dist-cjs/agent/middleware/pii.d.ts +67 -0
- package/dist-cjs/agent/middleware/pii.js +147 -0
- package/dist-cjs/agent/middleware/retry.d.ts +66 -0
- package/dist-cjs/agent/middleware/retry.js +63 -0
- package/dist-cjs/agent/middleware/shared.d.ts +41 -0
- package/dist-cjs/agent/middleware/shared.js +83 -0
- package/dist-cjs/agent/middleware/tool-selector.d.ts +64 -0
- package/dist-cjs/agent/middleware/tool-selector.js +109 -0
- package/dist-cjs/agent/permissions.d.ts +113 -0
- package/dist-cjs/agent/permissions.js +189 -0
- package/dist-cjs/agent/sandbox.d.ts +151 -0
- package/dist-cjs/agent/sandbox.js +297 -0
- package/dist-cjs/agent/tool.d.ts +8 -3
- package/dist-cjs/agent/tool.js +14 -3
- package/dist-cjs/core/secure-stream.js +4 -3
- package/dist-cjs/deep-agent/index.d.ts +109 -0
- package/dist-cjs/deep-agent/index.js +219 -0
- package/dist-cjs/embeddings/adapters.js +2 -1
- package/dist-cjs/evals/judge.js +2 -5
- package/dist-cjs/evaluate/cache.d.ts +0 -14
- package/dist-cjs/evaluate/cache.js +1 -65
- package/dist-cjs/evaluate/file-cache.d.ts +15 -0
- package/dist-cjs/evaluate/file-cache.js +67 -0
- package/dist-cjs/evaluate/index.d.ts +2 -1
- package/dist-cjs/evaluate/index.js +3 -2
- package/dist-cjs/evaluate/review.js +2 -2
- package/dist-cjs/evaluate/run.js +2 -2
- package/dist-cjs/evaluate/version.js +3 -3
- package/dist-cjs/graph/functional.d.ts +9 -0
- package/dist-cjs/graph/functional.js +20 -3
- package/dist-cjs/graph/graph.js +27 -6
- package/dist-cjs/graph/lint.d.ts +4 -1
- package/dist-cjs/graph/lint.js +35 -0
- package/dist-cjs/graph/node-cache.js +3 -3
- package/dist-cjs/hallucination/rag.d.ts +4 -1
- package/dist-cjs/hallucination/rag.js +6 -1
- package/dist-cjs/images/asset-stores.js +2 -3
- package/dist-cjs/images/asset-support.js +2 -2
- package/dist-cjs/images/assets.js +1 -2
- package/dist-cjs/images/evals.js +4 -4
- package/dist-cjs/insights/proposals.js +1 -2
- package/dist-cjs/insights/regressions.d.ts +26 -2
- package/dist-cjs/insights/regressions.js +129 -3
- package/dist-cjs/loaders/git.js +8 -1
- package/dist-cjs/loaders/web.js +7 -1
- package/dist-cjs/mcp/client.d.ts +14 -0
- package/dist-cjs/mcp/client.js +60 -1
- package/dist-cjs/mcp/registry.d.ts +6 -0
- package/dist-cjs/mcp/registry.js +7 -0
- package/dist-cjs/postgres/fulltext.d.ts +67 -0
- package/dist-cjs/postgres/fulltext.js +129 -0
- package/dist-cjs/prompts/file.js +28 -11
- package/dist-cjs/protocols/a2a.d.ts +235 -0
- package/dist-cjs/protocols/a2a.js +393 -0
- package/dist-cjs/protocols/acp.d.ts +87 -0
- package/dist-cjs/protocols/acp.js +326 -0
- package/dist-cjs/protocols/ag-ui.d.ts +120 -0
- package/dist-cjs/protocols/ag-ui.js +186 -0
- package/dist-cjs/protocols/shared.d.ts +80 -0
- package/dist-cjs/protocols/shared.js +59 -0
- package/dist-cjs/providers/anthropic.js +1 -0
- package/dist-cjs/providers/cohere.js +2 -2
- package/dist-cjs/providers/google.js +3 -12
- package/dist-cjs/providers/openai.js +1 -0
- package/dist-cjs/rag/elasticsearch.d.ts +71 -0
- package/dist-cjs/rag/elasticsearch.js +135 -0
- package/dist-cjs/rag/file-ingestion.js +14 -8
- package/dist-cjs/rag/pipeline.d.ts +140 -0
- package/dist-cjs/rag/pipeline.js +191 -0
- package/dist-cjs/rag/rerankers.d.ts +92 -0
- package/dist-cjs/rag/rerankers.js +107 -0
- package/dist-cjs/rag/retrievers.d.ts +12 -1
- package/dist-cjs/runtime/detect.d.ts +17 -0
- package/dist-cjs/runtime/detect.js +19 -0
- package/dist-cjs/runtime/index.d.ts +19 -0
- package/dist-cjs/runtime/index.js +31 -0
- package/dist-cjs/security/injection-detector.d.ts +2 -0
- package/dist-cjs/security/injection-detector.js +7 -1
- package/dist-cjs/security/input-guard.js +5 -2
- package/dist-cjs/security/output-guard.js +8 -4
- package/dist-cjs/security/pii-detector.js +22 -6
- package/dist-cjs/security/prompt-hardening.js +6 -2
- package/dist-cjs/security/semantic-injection-classifier.d.ts +2 -1
- package/dist-cjs/security/semantic-injection-classifier.js +31 -35
- package/dist-cjs/security/upload-scanner.d.ts +8 -0
- package/dist-cjs/security/upload-scanner.js +53 -9
- package/dist-cjs/server/assistant.d.ts +5 -1
- package/dist-cjs/server/assistant.js +4 -2
- package/dist-cjs/server/auth.d.ts +250 -0
- package/dist-cjs/server/auth.js +402 -0
- package/dist-cjs/server/deployments.d.ts +63 -1
- package/dist-cjs/server/deployments.js +75 -2
- package/dist-cjs/server/runs.d.ts +9 -0
- package/dist-cjs/server/runs.js +20 -0
- package/dist-cjs/server/server.js +5 -0
- package/dist-cjs/store/memory.d.ts +10 -0
- package/dist-cjs/store/memory.js +10 -0
- package/dist-cjs/testing/record.js +2 -2
- package/dist-cjs/tracing/stores.js +2 -1
- package/dist-cjs/tracing/tracer.js +2 -2
- package/dist-cjs/types/config.d.ts +20 -0
- package/dist-cjs/types/graph.d.ts +38 -0
- package/dist-cjs/types/messages.d.ts +36 -2
- package/dist-cjs/types/principal.d.ts +21 -0
- package/dist-cjs/types/principal.js +2 -0
- package/dist-cjs/types/server.d.ts +8 -9
- package/dist-cjs/utils/base64.d.ts +4 -0
- package/dist-cjs/utils/base64.js +25 -0
- package/dist-cjs/utils/checksums.d.ts +22 -0
- package/dist-cjs/utils/checksums.js +46 -0
- package/dist-cjs/utils/ids.d.ts +8 -0
- package/dist-cjs/utils/ids.js +28 -2
- package/dist-cjs/utils/sha256.d.ts +22 -0
- package/dist-cjs/utils/sha256.js +125 -0
- package/dist-cjs/utils/tenant.d.ts +11 -0
- package/dist-cjs/utils/tenant.js +15 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +7 -3
- package/package.json +193 -6
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
export class SandboxPathError extends Error {
|
|
2
|
+
path;
|
|
3
|
+
code = 'SANDBOX_PATH';
|
|
4
|
+
constructor(path) {
|
|
5
|
+
super(`"${path}" is outside the sandbox`);
|
|
6
|
+
this.path = path;
|
|
7
|
+
this.name = 'SandboxPathError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function processSandbox(options) {
|
|
11
|
+
const ready = (async () => {
|
|
12
|
+
const [{ spawn }, fs, { dirname, isAbsolute, relative, resolve }] = await Promise.all([
|
|
13
|
+
import('node:child_process'),
|
|
14
|
+
import('node:fs/promises'),
|
|
15
|
+
import('node:path'),
|
|
16
|
+
]);
|
|
17
|
+
const root = resolve(options.root);
|
|
18
|
+
await fs.mkdir(root, { recursive: true });
|
|
19
|
+
const inside = (file) => {
|
|
20
|
+
const resolved = resolve(root, file);
|
|
21
|
+
const fromRoot = relative(root, resolved);
|
|
22
|
+
if (fromRoot.startsWith('..') || isAbsolute(fromRoot))
|
|
23
|
+
throw new SandboxPathError(file);
|
|
24
|
+
return resolved;
|
|
25
|
+
};
|
|
26
|
+
return { spawn, fs, dirname, root, inside };
|
|
27
|
+
})();
|
|
28
|
+
ready.catch(() => undefined);
|
|
29
|
+
const maxOutput = options.maxOutputBytes ?? 1_048_576;
|
|
30
|
+
return {
|
|
31
|
+
name: 'process (development only)',
|
|
32
|
+
isolation: { filesystem: false, network: false, processes: false },
|
|
33
|
+
async exec(command, execOptions = {}) {
|
|
34
|
+
const { spawn, root, inside } = await ready;
|
|
35
|
+
const started = Date.now();
|
|
36
|
+
const cwd = inside(execOptions.cwd ?? '.');
|
|
37
|
+
const env = {
|
|
38
|
+
PATH: process.env.PATH ?? '',
|
|
39
|
+
...(process.platform === 'win32' && process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}),
|
|
40
|
+
HOME: root,
|
|
41
|
+
...options.env,
|
|
42
|
+
...execOptions.env,
|
|
43
|
+
};
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const child = spawn(command, {
|
|
46
|
+
cwd,
|
|
47
|
+
env,
|
|
48
|
+
shell: true,
|
|
49
|
+
windowsHide: true,
|
|
50
|
+
detached: process.platform !== 'win32',
|
|
51
|
+
});
|
|
52
|
+
let stdout = '';
|
|
53
|
+
let stderr = '';
|
|
54
|
+
let timedOut = false;
|
|
55
|
+
let settled = false;
|
|
56
|
+
const keep = (current, chunk) => current.length >= maxOutput ? current : (current + chunk.toString('utf8')).slice(0, maxOutput);
|
|
57
|
+
child.stdout?.on('data', (chunk) => {
|
|
58
|
+
stdout = keep(stdout, chunk);
|
|
59
|
+
});
|
|
60
|
+
child.stderr?.on('data', (chunk) => {
|
|
61
|
+
stderr = keep(stderr, chunk);
|
|
62
|
+
});
|
|
63
|
+
const stop = () => killTree(spawn, child.pid);
|
|
64
|
+
const limit = execOptions.timeoutMs ?? options.timeoutMs ?? 30_000;
|
|
65
|
+
const timer = setTimeout(() => {
|
|
66
|
+
timedOut = true;
|
|
67
|
+
stop();
|
|
68
|
+
}, limit);
|
|
69
|
+
const onAbort = () => stop();
|
|
70
|
+
execOptions.signal?.addEventListener('abort', onAbort, { once: true });
|
|
71
|
+
const finish = (exitCode) => {
|
|
72
|
+
if (settled)
|
|
73
|
+
return;
|
|
74
|
+
settled = true;
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
execOptions.signal?.removeEventListener('abort', onAbort);
|
|
77
|
+
resolve({ exitCode: timedOut ? null : exitCode, stdout, stderr, timedOut, durationMs: Date.now() - started });
|
|
78
|
+
};
|
|
79
|
+
child.on('error', (error) => {
|
|
80
|
+
stderr += error.message;
|
|
81
|
+
finish(null);
|
|
82
|
+
});
|
|
83
|
+
child.on('close', (code) => finish(code));
|
|
84
|
+
if (execOptions.stdin !== undefined)
|
|
85
|
+
child.stdin?.end(execOptions.stdin);
|
|
86
|
+
else
|
|
87
|
+
child.stdin?.end();
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
async readFile(file) {
|
|
91
|
+
const { fs, inside } = await ready;
|
|
92
|
+
return fs.readFile(inside(file), 'utf8');
|
|
93
|
+
},
|
|
94
|
+
async writeFile(file, content) {
|
|
95
|
+
const { fs, dirname, inside } = await ready;
|
|
96
|
+
const target = inside(file);
|
|
97
|
+
await fs.mkdir(dirname(target), { recursive: true });
|
|
98
|
+
await fs.writeFile(target, content, 'utf8');
|
|
99
|
+
},
|
|
100
|
+
async listFiles(directory = '.') {
|
|
101
|
+
const { fs, inside } = await ready;
|
|
102
|
+
return (await fs.readdir(inside(directory))).sort();
|
|
103
|
+
},
|
|
104
|
+
async removeFile(file) {
|
|
105
|
+
const { fs, root, inside } = await ready;
|
|
106
|
+
const target = inside(file);
|
|
107
|
+
if (target === root)
|
|
108
|
+
throw new SandboxPathError(file);
|
|
109
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function killTree(spawn, pid) {
|
|
114
|
+
if (pid === undefined)
|
|
115
|
+
return;
|
|
116
|
+
try {
|
|
117
|
+
if (process.platform === 'win32') {
|
|
118
|
+
spawn('taskkill', ['/pid', String(pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
process.kill(-pid, 'SIGKILL');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export function sandboxTools(sandbox, options = {}) {
|
|
128
|
+
const mount = (options.mount ?? '/workspace').replace(/\/+$/, '');
|
|
129
|
+
const local = (file) => {
|
|
130
|
+
const unified = String(file ?? '.').replace(/\\/g, '/');
|
|
131
|
+
const relative = unified === mount ? '.' : unified.startsWith(`${mount}/`) ? unified.slice(mount.length + 1) : unified;
|
|
132
|
+
return relative.replace(/^\/+/, '') || '.';
|
|
133
|
+
};
|
|
134
|
+
const mounted = (file) => `${mount}/${local(file)}`;
|
|
135
|
+
const tools = [];
|
|
136
|
+
if (options.shell !== false) {
|
|
137
|
+
tools.push({
|
|
138
|
+
name: 'run_command',
|
|
139
|
+
description: 'Runs a shell command in the sandbox and returns its exit code and output.',
|
|
140
|
+
parameters: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: { command: { type: 'string', description: 'The command line to run' } },
|
|
143
|
+
required: ['command'],
|
|
144
|
+
},
|
|
145
|
+
capabilities: (args) => [`shell:${String(args.command ?? '')}`],
|
|
146
|
+
execute: async (args) => {
|
|
147
|
+
const result = await sandbox.exec(String(args.command ?? ''), {
|
|
148
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
149
|
+
});
|
|
150
|
+
return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr, timedOut: result.timedOut };
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (options.files !== false) {
|
|
155
|
+
tools.push({
|
|
156
|
+
name: 'read_file',
|
|
157
|
+
description: `Reads a text file in the sandbox. Paths are relative to ${mount}.`,
|
|
158
|
+
parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
|
|
159
|
+
capabilities: (args) => [`filesystem:read:${mounted(args.path)}`],
|
|
160
|
+
execute: async (args) => sandbox.readFile(local(args.path)),
|
|
161
|
+
}, {
|
|
162
|
+
name: 'write_file',
|
|
163
|
+
description: `Writes a text file in the sandbox, creating its directory. Paths are relative to ${mount}.`,
|
|
164
|
+
parameters: {
|
|
165
|
+
type: 'object',
|
|
166
|
+
properties: { path: { type: 'string' }, content: { type: 'string' } },
|
|
167
|
+
required: ['path', 'content'],
|
|
168
|
+
},
|
|
169
|
+
capabilities: (args) => [`filesystem:write:${mounted(args.path)}`],
|
|
170
|
+
execute: async (args) => {
|
|
171
|
+
await sandbox.writeFile(local(args.path), String(args.content ?? ''));
|
|
172
|
+
return { written: local(args.path) };
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
if (sandbox.listFiles) {
|
|
176
|
+
const list = sandbox.listFiles.bind(sandbox);
|
|
177
|
+
tools.push({
|
|
178
|
+
name: 'list_files',
|
|
179
|
+
description: 'Lists the entries of a directory in the sandbox.',
|
|
180
|
+
parameters: { type: 'object', properties: { path: { type: 'string' } } },
|
|
181
|
+
capabilities: (args) => [`filesystem:read:${mounted(args.path)}`],
|
|
182
|
+
execute: async (args) => list(local(args.path)),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return tools;
|
|
187
|
+
}
|
|
188
|
+
export async function runSandboxConformance(create, options = {}) {
|
|
189
|
+
const node = options.node ?? 'node';
|
|
190
|
+
const js = (code) => `${node} -e "${code}"`;
|
|
191
|
+
const checks = [];
|
|
192
|
+
let name = 'sandbox';
|
|
193
|
+
const check = async (label, run) => {
|
|
194
|
+
const sandbox = await create();
|
|
195
|
+
name = sandbox.name;
|
|
196
|
+
try {
|
|
197
|
+
const problem = await run(sandbox);
|
|
198
|
+
checks.push(problem ? { name: label, ok: false, detail: problem } : { name: label, ok: true });
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
checks.push({ name: label, ok: false, detail: error instanceof Error ? error.message : String(error) });
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
await sandbox.dispose?.();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const expect = (actual, expected, what) => actual === expected ? undefined : `${what}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`;
|
|
208
|
+
await check('a command reports its output and a zero exit code', async (sandbox) => {
|
|
209
|
+
const result = await sandbox.exec(js("process.stdout.write('ok')"));
|
|
210
|
+
return expect(result.stdout, 'ok', 'stdout') ?? expect(result.exitCode, 0, 'exit code');
|
|
211
|
+
});
|
|
212
|
+
await check('a failing command reports its exit code and its errors', async (sandbox) => {
|
|
213
|
+
const result = await sandbox.exec(js("process.stderr.write('bad'); process.exit(3)"));
|
|
214
|
+
return expect(result.exitCode, 3, 'exit code') ?? expect(result.stderr, 'bad', 'stderr');
|
|
215
|
+
});
|
|
216
|
+
await check('a command reads its standard input', async (sandbox) => {
|
|
217
|
+
const result = await sandbox.exec(js('process.stdin.pipe(process.stdout)'), { stdin: 'piped' });
|
|
218
|
+
return expect(result.stdout, 'piped', 'stdout');
|
|
219
|
+
});
|
|
220
|
+
await check('a command gets the variables it is given, and none of the host', async (sandbox) => {
|
|
221
|
+
process.env.NEXUS_SANDBOX_HOST_SECRET = 'host-only';
|
|
222
|
+
try {
|
|
223
|
+
const result = await sandbox.exec(js("process.stdout.write((process.env.NEXUS_SANDBOX_GIVEN || '') + '|' + (process.env.NEXUS_SANDBOX_HOST_SECRET || ''))"), { env: { NEXUS_SANDBOX_GIVEN: 'given' } });
|
|
224
|
+
return expect(result.stdout, 'given|', 'environment');
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
delete process.env.NEXUS_SANDBOX_HOST_SECRET;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
await check('a command past its timeout is stopped', async (sandbox) => {
|
|
231
|
+
const limit = options.timeoutMs ?? 300;
|
|
232
|
+
const result = await sandbox.exec(js('setTimeout(() => {}, 20000)'), { timeoutMs: limit });
|
|
233
|
+
if (!result.timedOut)
|
|
234
|
+
return 'the command was not reported as timed out';
|
|
235
|
+
if (result.durationMs > limit + 5_000)
|
|
236
|
+
return `the command ran ${result.durationMs} ms past a ${limit} ms timeout`;
|
|
237
|
+
return undefined;
|
|
238
|
+
});
|
|
239
|
+
await check('files round-trip, and commands run where they are', async (sandbox) => {
|
|
240
|
+
await sandbox.writeFile('conformance/note.txt', 'here');
|
|
241
|
+
const read = await sandbox.readFile('conformance/note.txt');
|
|
242
|
+
const seen = await sandbox.exec(js("process.stdout.write(require('fs').readFileSync('conformance/note.txt', 'utf8'))"));
|
|
243
|
+
const listed = sandbox.listFiles ? await sandbox.listFiles('conformance') : ['note.txt'];
|
|
244
|
+
return (expect(read, 'here', 'readFile') ??
|
|
245
|
+
expect(seen.stdout, 'here', 'a command reading the file') ??
|
|
246
|
+
(listed.includes('note.txt') ? undefined : `listFiles did not list note.txt: ${JSON.stringify(listed)}`));
|
|
247
|
+
});
|
|
248
|
+
await check('a path outside the sandbox is refused', async (sandbox) => {
|
|
249
|
+
for (const attempt of ['../outside.txt', 'conformance/../../outside.txt']) {
|
|
250
|
+
const refused = await sandbox.writeFile(attempt, 'escaped').then(() => false, () => true);
|
|
251
|
+
if (!refused)
|
|
252
|
+
return `writeFile("${attempt}") was allowed`;
|
|
253
|
+
}
|
|
254
|
+
return undefined;
|
|
255
|
+
});
|
|
256
|
+
return { sandbox: name, passed: checks.every((item) => item.ok), checks };
|
|
257
|
+
}
|
package/dist/agent/tool.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import type { ContentPart, Message, ToolDefinition, ToolOutput } from '../types/messages.js';
|
|
1
|
+
import type { ContentPart, Message, ToolContext, ToolDefinition, ToolOutput } from '../types/messages.js';
|
|
2
2
|
import type { ToolExecutionResult } from '../types/agent.js';
|
|
3
3
|
/** Defines a tool the model can call, typing its arguments. */
|
|
4
4
|
export declare function tool<TArgs extends Record<string, unknown> = Record<string, unknown>>(definition: {
|
|
5
5
|
name: string;
|
|
6
6
|
description: string;
|
|
7
7
|
parameters: Record<string, unknown>;
|
|
8
|
-
|
|
8
|
+
/** Runs the tool. In an agent, `context` says who the run is for and carries its signal and store. */
|
|
9
|
+
execute: (args: TArgs, context: ToolContext) => Promise<unknown> | unknown;
|
|
10
|
+
/** What the tool does to the world, for a permission policy; see `ToolDefinition.capabilities`. */
|
|
11
|
+
capabilities?: readonly string[] | ((args: TArgs) => readonly string[]);
|
|
9
12
|
}): ToolDefinition;
|
|
10
13
|
/**
|
|
11
14
|
* Builds a tool's result as content: text, images, and asset references, in order. A string becomes
|
|
@@ -28,8 +31,10 @@ export declare class ToolExecutor {
|
|
|
28
31
|
register(toolDefinition: ToolDefinition): this;
|
|
29
32
|
/** Whether a tool is registered. */
|
|
30
33
|
has(name: string): boolean;
|
|
34
|
+
/** A registered tool, or `undefined`. */
|
|
35
|
+
get(name: string): ToolDefinition | undefined;
|
|
31
36
|
/** Every registered tool. */
|
|
32
37
|
list(): ToolDefinition[];
|
|
33
38
|
/** Runs a tool. An unknown name or a thrown error comes back as a failed result. */
|
|
34
|
-
execute(name: string, args: Record<string, unknown
|
|
39
|
+
execute(name: string, args: Record<string, unknown>, context?: ToolContext): Promise<ToolExecutionResult>;
|
|
35
40
|
}
|
package/dist/agent/tool.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
export function tool(definition) {
|
|
2
|
+
const capabilities = definition.capabilities;
|
|
2
3
|
return {
|
|
3
4
|
name: definition.name,
|
|
4
5
|
description: definition.description,
|
|
5
6
|
parameters: definition.parameters,
|
|
6
|
-
execute: async (args) => definition.execute(args),
|
|
7
|
+
execute: async (args, context) => definition.execute(args, context ?? {}),
|
|
8
|
+
...(capabilities === undefined
|
|
9
|
+
? {}
|
|
10
|
+
: {
|
|
11
|
+
capabilities: typeof capabilities === 'function'
|
|
12
|
+
? (args) => capabilities(args)
|
|
13
|
+
: capabilities,
|
|
14
|
+
}),
|
|
7
15
|
};
|
|
8
16
|
}
|
|
9
17
|
export function toolOutput(...content) {
|
|
@@ -37,10 +45,13 @@ export class ToolExecutor {
|
|
|
37
45
|
has(name) {
|
|
38
46
|
return this.tools.has(name);
|
|
39
47
|
}
|
|
48
|
+
get(name) {
|
|
49
|
+
return this.tools.get(name);
|
|
50
|
+
}
|
|
40
51
|
list() {
|
|
41
52
|
return [...this.tools.values()];
|
|
42
53
|
}
|
|
43
|
-
async execute(name, args) {
|
|
54
|
+
async execute(name, args, context) {
|
|
44
55
|
const toolDefinition = this.tools.get(name);
|
|
45
56
|
if (!toolDefinition) {
|
|
46
57
|
return { ok: false, error: `Tool "${name}" is not registered` };
|
|
@@ -49,7 +60,7 @@ export class ToolExecutor {
|
|
|
49
60
|
return { ok: false, error: `Tool "${name}" does not have an execute function` };
|
|
50
61
|
}
|
|
51
62
|
try {
|
|
52
|
-
const result = await toolDefinition.execute(args);
|
|
63
|
+
const result = await toolDefinition.execute(args, context);
|
|
53
64
|
return { ok: true, result };
|
|
54
65
|
}
|
|
55
66
|
catch (err) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { utf8Length } from '../utils/ids.js';
|
|
1
2
|
import { redactSensitiveText } from '../security/output-guard.js';
|
|
2
3
|
const MAX_BUFFERED_STREAM_BYTES = 1_048_576;
|
|
3
4
|
const MAX_BUFFERED_STREAM_CHUNKS = 100_000;
|
|
@@ -21,13 +22,13 @@ export function protectStreamOutput(stream, security, requestSignal) {
|
|
|
21
22
|
}
|
|
22
23
|
if (chunk.type === 'text' && chunk.content) {
|
|
23
24
|
bufferedText += chunk.content;
|
|
24
|
-
bufferedBytes +=
|
|
25
|
+
bufferedBytes += utf8Length(chunk.content);
|
|
25
26
|
}
|
|
26
27
|
else if (chunk.type === 'error' && chunk.error) {
|
|
27
|
-
bufferedBytes +=
|
|
28
|
+
bufferedBytes += utf8Length(chunk.error);
|
|
28
29
|
}
|
|
29
30
|
else if (chunk.type === 'tool_call' && chunk.toolCall) {
|
|
30
|
-
bufferedBytes +=
|
|
31
|
+
bufferedBytes += utf8Length(chunk.toolCall.function.arguments);
|
|
31
32
|
bufferedToolCalls.push(chunk.toolCall);
|
|
32
33
|
}
|
|
33
34
|
if (bufferedBytes > MAX_BUFFERED_STREAM_BYTES) {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep agents: agents for long, multi-step work — a plan, a workspace, helpers to delegate to, skills
|
|
3
|
+
* to load when needed, and a context that stays small however long the work runs.
|
|
4
|
+
*
|
|
5
|
+
* A preset, not an engine: `createDeepAgent()` composes `createAgent()` with its tools and middleware,
|
|
6
|
+
* and returns the same compiled graph, with the same checkpoints, approvals, events, and server
|
|
7
|
+
* support as any agent.
|
|
8
|
+
*/
|
|
9
|
+
import { type AgentGraph, type AgentMiddleware, type CreateAgentOptions } from '../agent/create-agent.js';
|
|
10
|
+
import { type ContextEditorOptions } from '../agent/middleware/context-editor.js';
|
|
11
|
+
import { type ContextOffloadOptions } from '../agent/middleware/filesystem.js';
|
|
12
|
+
import { type ToolSelectorOptions } from '../agent/middleware/tool-selector.js';
|
|
13
|
+
import { type PermissionPolicyLike } from '../agent/permissions.js';
|
|
14
|
+
import { type Sandbox } from '../agent/sandbox.js';
|
|
15
|
+
/** A helper the deep agent can hand a self-contained piece of work to, with a fresh context. */
|
|
16
|
+
export interface DeepSubagent {
|
|
17
|
+
/** Its name, as the agent delegates to it. */
|
|
18
|
+
name: string;
|
|
19
|
+
/** What it is for, written for the model that decides when to delegate. */
|
|
20
|
+
description: string;
|
|
21
|
+
/** Its own instructions. */
|
|
22
|
+
instructions: string;
|
|
23
|
+
/**
|
|
24
|
+
* The tools it may use, by name, from the deep agent's own. Defaults to all of them except
|
|
25
|
+
* `delegate`, so a helper cannot delegate in turn. A tool named in `interruptOn` is never a
|
|
26
|
+
* helper's: a helper keeps no checkpoints, so it could not pause for the approval.
|
|
27
|
+
*/
|
|
28
|
+
tools?: readonly string[];
|
|
29
|
+
/** Its model. Defaults to the deep agent's. */
|
|
30
|
+
model?: string;
|
|
31
|
+
/** Model calls it may make. Defaults to 20. */
|
|
32
|
+
maxIterations?: number;
|
|
33
|
+
}
|
|
34
|
+
/** Instructions the agent loads only when a task needs them: a procedure, a checklist, a house style. */
|
|
35
|
+
export interface DeepSkill {
|
|
36
|
+
/** Its name, as the agent asks for it. */
|
|
37
|
+
name: string;
|
|
38
|
+
/** When it applies, written for the model. Only this is in the agent's context until it is loaded. */
|
|
39
|
+
description: string;
|
|
40
|
+
/** The instructions themselves. */
|
|
41
|
+
instructions?: string;
|
|
42
|
+
/** A file in the sandbox that holds them, read when the skill is loaded, instead of `instructions`. */
|
|
43
|
+
file?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Options for `createDeepAgent()`. Everything `createAgent()` takes, and the parts of a deep agent. */
|
|
46
|
+
export interface DeepAgentOptions extends Omit<CreateAgentOptions, 'systemPrompt' | 'permissions'> {
|
|
47
|
+
/** What this agent is for. Placed before the deep agent's own working instructions. */
|
|
48
|
+
instructions?: string;
|
|
49
|
+
/**
|
|
50
|
+
* The workspace the agent reads, writes, and runs commands in, through `sandboxTools()`, mounted
|
|
51
|
+
* at `/workspace`. Without one, the agent has no filesystem and no offloading.
|
|
52
|
+
*/
|
|
53
|
+
sandbox?: Sandbox;
|
|
54
|
+
/**
|
|
55
|
+
* Commands the agent may run in the sandbox, as prefixes a permission policy grants: `node`,
|
|
56
|
+
* `npm test`, `git status`. Without any, there is no `run_command` tool.
|
|
57
|
+
*/
|
|
58
|
+
commands?: readonly string[];
|
|
59
|
+
/** Records a plan with `write_todos` and keeps it current. Defaults to true. */
|
|
60
|
+
planning?: boolean;
|
|
61
|
+
/** Helpers the agent can delegate to through one `delegate` tool. */
|
|
62
|
+
subagents?: readonly DeepSubagent[];
|
|
63
|
+
/** Skills the agent can load through `load_skill`. */
|
|
64
|
+
skills?: readonly DeepSkill[];
|
|
65
|
+
/** Instruction files from the sandbox the model sees on every call. Defaults to `AGENTS.md`. */
|
|
66
|
+
include?: readonly string[];
|
|
67
|
+
/**
|
|
68
|
+
* Writes tool results longer than the limit to `.context/` in the sandbox, leaving a preview and the
|
|
69
|
+
* path. Defaults to results over 20,000 characters; `false` keeps them whole.
|
|
70
|
+
*/
|
|
71
|
+
offload?: ContextOffloadOptions | false;
|
|
72
|
+
/**
|
|
73
|
+
* Clears old tool results from what the model is sent once the request grows. Defaults to past
|
|
74
|
+
* 60,000 estimated tokens, never clearing the plan; `false` turns it off.
|
|
75
|
+
*/
|
|
76
|
+
contextEditing?: ContextEditorOptions | false;
|
|
77
|
+
/**
|
|
78
|
+
* Sends the model only the tools relevant to the turn, for an agent with many. The deep agent's own
|
|
79
|
+
* tools are always sent. Off unless given.
|
|
80
|
+
*/
|
|
81
|
+
toolSelection?: ToolSelectorOptions;
|
|
82
|
+
/**
|
|
83
|
+
* Decides every tool call. Defaults to a policy that grants the workspace, the `commands`, the plan,
|
|
84
|
+
* delegation, and skills, and denies everything else, so a tool of your own needs a grant here or
|
|
85
|
+
* its own policy. `false` runs every call.
|
|
86
|
+
*/
|
|
87
|
+
permissions?: PermissionPolicyLike | false;
|
|
88
|
+
/** Middleware run after the deep agent's own. */
|
|
89
|
+
middleware?: AgentMiddleware[];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Builds a deep agent: `createAgent()` with a plan, a sandboxed workspace, subagents, skills, context
|
|
93
|
+
* offloading, optional tool selection, and a permission policy.
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* const agent = createDeepAgent({
|
|
97
|
+
* client: ai,
|
|
98
|
+
* model: 'claude-sonnet-5-5',
|
|
99
|
+
* instructions: 'You maintain this repository.',
|
|
100
|
+
* sandbox,
|
|
101
|
+
* commands: ['node', 'npm test', 'git diff'],
|
|
102
|
+
* subagents: [{ name: 'reviewer', description: 'Reviews a change', instructions: 'Review the diff for bugs.' }],
|
|
103
|
+
* skills: [{ name: 'release-notes', description: 'Writing release notes', file: 'skills/release-notes.md' }],
|
|
104
|
+
* checkpointer,
|
|
105
|
+
* });
|
|
106
|
+
* const run = await agent.invoke(agentInput('Fix the failing test'), { threadId });
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export declare function createDeepAgent(options: DeepAgentOptions): AgentGraph;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { agentInput, createAgent, } from '../agent/create-agent.js';
|
|
2
|
+
import { contextEditor } from '../agent/middleware/context-editor.js';
|
|
3
|
+
import { filesystemContext } from '../agent/middleware/filesystem.js';
|
|
4
|
+
import { toolSelector } from '../agent/middleware/tool-selector.js';
|
|
5
|
+
import { permissionPolicy } from '../agent/permissions.js';
|
|
6
|
+
import { sandboxTools } from '../agent/sandbox.js';
|
|
7
|
+
import { tool } from '../agent/tool.js';
|
|
8
|
+
const MOUNT = '/workspace';
|
|
9
|
+
const CORE_TOOLS = [
|
|
10
|
+
'write_todos',
|
|
11
|
+
'delegate',
|
|
12
|
+
'load_skill',
|
|
13
|
+
'read_file',
|
|
14
|
+
'write_file',
|
|
15
|
+
'edit_file',
|
|
16
|
+
'list_files',
|
|
17
|
+
'run_command',
|
|
18
|
+
];
|
|
19
|
+
export function createDeepAgent(options) {
|
|
20
|
+
const { instructions, sandbox, commands = [], planning = true, subagents = [], skills = [], include = ['AGENTS.md'], offload, contextEditing, toolSelection, permissions, middleware = [], tools: extraTools = [], ...agentOptions } = options;
|
|
21
|
+
const workspace = sandbox
|
|
22
|
+
? [...sandboxTools(sandbox, { shell: commands.length > 0, mount: MOUNT }), editFileTool(sandbox)]
|
|
23
|
+
: [];
|
|
24
|
+
const own = [
|
|
25
|
+
...(planning ? [writeTodosTool()] : []),
|
|
26
|
+
...(skills.length > 0 ? [loadSkillTool(skills, sandbox)] : []),
|
|
27
|
+
...workspace,
|
|
28
|
+
...extraTools,
|
|
29
|
+
];
|
|
30
|
+
const helpers = new Map();
|
|
31
|
+
const approved = new Set(Object.keys(agentOptions.interruptOn ?? {}));
|
|
32
|
+
const { interruptOn: _approvals, ...helperOptions } = agentOptions;
|
|
33
|
+
for (const helper of subagents) {
|
|
34
|
+
const allowed = helper.tools ? new Set(helper.tools) : undefined;
|
|
35
|
+
helpers.set(helper.name, createAgent({
|
|
36
|
+
...helperOptions,
|
|
37
|
+
name: `${options.name ?? 'deep-agent'}.${helper.name}`,
|
|
38
|
+
...(helper.model ? { model: helper.model } : {}),
|
|
39
|
+
maxIterations: helper.maxIterations ?? 20,
|
|
40
|
+
systemPrompt: `${helper.instructions}\n\n${WORKSPACE_NOTE}`,
|
|
41
|
+
tools: own.filter((candidate) => !approved.has(candidate.name) && (allowed ? allowed.has(candidate.name) : true)),
|
|
42
|
+
...(permissions === false ? {} : { permissions: permissions ?? defaultPolicy(commands) }),
|
|
43
|
+
checkpointer: false,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
const tools = [...own, ...(helpers.size > 0 ? [delegateTool(subagents, helpers)] : [])];
|
|
47
|
+
const kit = [
|
|
48
|
+
...(sandbox && (include.length > 0 || offload !== false)
|
|
49
|
+
? [
|
|
50
|
+
filesystemContext({
|
|
51
|
+
source: sandbox,
|
|
52
|
+
include,
|
|
53
|
+
...(offload === false ? {} : { offload: { overChars: 20_000, ...offload } }),
|
|
54
|
+
}),
|
|
55
|
+
]
|
|
56
|
+
: []),
|
|
57
|
+
...(contextEditing === false
|
|
58
|
+
? []
|
|
59
|
+
: [
|
|
60
|
+
contextEditor({
|
|
61
|
+
triggerTokens: 60_000,
|
|
62
|
+
...contextEditing,
|
|
63
|
+
exclude: ['write_todos', ...(contextEditing?.exclude ?? [])],
|
|
64
|
+
}),
|
|
65
|
+
]),
|
|
66
|
+
...(toolSelection
|
|
67
|
+
? [toolSelector({ ...toolSelection, always: [...CORE_TOOLS, ...(toolSelection.always ?? [])] })]
|
|
68
|
+
: []),
|
|
69
|
+
];
|
|
70
|
+
return createAgent({
|
|
71
|
+
maxIterations: 40,
|
|
72
|
+
...agentOptions,
|
|
73
|
+
systemPrompt: systemPrompt(instructions, { planning, sandbox: Boolean(sandbox), commands, subagents, skills }),
|
|
74
|
+
tools,
|
|
75
|
+
middleware: [...kit, ...middleware],
|
|
76
|
+
...(permissions === false ? {} : { permissions: permissions ?? defaultPolicy(commands) }),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function defaultPolicy(commands) {
|
|
80
|
+
return permissionPolicy({
|
|
81
|
+
filesystem: { write: [`${MOUNT}/**`] },
|
|
82
|
+
shell: { allow: commands },
|
|
83
|
+
tools: { write_todos: 'allow', delegate: 'allow', load_skill: 'allow' },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const WORKSPACE_NOTE = `Files live in ${MOUNT}; paths are relative to it.`;
|
|
87
|
+
function systemPrompt(instructions, parts) {
|
|
88
|
+
const lines = [];
|
|
89
|
+
if (instructions)
|
|
90
|
+
lines.push(instructions, '');
|
|
91
|
+
lines.push('Work through the task step by step, checking each step before the next.');
|
|
92
|
+
if (parts.planning) {
|
|
93
|
+
lines.push('For any task of more than two steps, first record a plan with write_todos, then keep it current: mark a step in_progress when you start it and completed when it is done.');
|
|
94
|
+
}
|
|
95
|
+
if (parts.sandbox) {
|
|
96
|
+
lines.push(`${WORKSPACE_NOTE} Read before you change a file; prefer edit_file to rewriting a whole file. A large result may be saved to a file and shown as a preview: read the file when you need the rest.`);
|
|
97
|
+
if (parts.commands.length > 0)
|
|
98
|
+
lines.push(`You may run these commands: ${parts.commands.join(', ')}.`);
|
|
99
|
+
}
|
|
100
|
+
if (parts.subagents.length > 0) {
|
|
101
|
+
lines.push('', 'Delegate a self-contained piece of work with delegate, to one of:');
|
|
102
|
+
for (const helper of parts.subagents)
|
|
103
|
+
lines.push(`- ${helper.name}: ${helper.description}`);
|
|
104
|
+
}
|
|
105
|
+
if (parts.skills.length > 0) {
|
|
106
|
+
lines.push('', 'Skills you can load with load_skill when a task calls for one:');
|
|
107
|
+
for (const skill of parts.skills)
|
|
108
|
+
lines.push(`- ${skill.name}: ${skill.description}`);
|
|
109
|
+
}
|
|
110
|
+
return lines.join('\n');
|
|
111
|
+
}
|
|
112
|
+
function writeTodosTool() {
|
|
113
|
+
return tool({
|
|
114
|
+
name: 'write_todos',
|
|
115
|
+
description: 'Records the plan for this task as a list of steps, each pending, in_progress, or completed. Send the whole list each time.',
|
|
116
|
+
parameters: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
todos: {
|
|
120
|
+
type: 'array',
|
|
121
|
+
items: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
properties: {
|
|
124
|
+
content: { type: 'string' },
|
|
125
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] },
|
|
126
|
+
},
|
|
127
|
+
required: ['content', 'status'],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
required: ['todos'],
|
|
132
|
+
},
|
|
133
|
+
capabilities: [],
|
|
134
|
+
execute: ({ todos }) => {
|
|
135
|
+
const list = Array.isArray(todos) ? todos : [];
|
|
136
|
+
const mark = { pending: '[ ]', in_progress: '[~]', completed: '[x]' };
|
|
137
|
+
const done = list.filter((todo) => todo.status === 'completed').length;
|
|
138
|
+
return `Plan (${done}/${list.length} done):\n${list.map((todo) => `${mark[todo.status] ?? '[ ]'} ${todo.content}`).join('\n')}`;
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function editFileTool(sandbox) {
|
|
143
|
+
const local = (file) => String(file ?? '')
|
|
144
|
+
.replace(/\\/g, '/')
|
|
145
|
+
.replace(new RegExp(`^${MOUNT}/`), '')
|
|
146
|
+
.replace(/^\/+/, '');
|
|
147
|
+
return tool({
|
|
148
|
+
name: 'edit_file',
|
|
149
|
+
description: `Replaces one exact occurrence of old_text with new_text in a file. Paths are relative to ${MOUNT}.`,
|
|
150
|
+
parameters: {
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: { path: { type: 'string' }, old_text: { type: 'string' }, new_text: { type: 'string' } },
|
|
153
|
+
required: ['path', 'old_text', 'new_text'],
|
|
154
|
+
},
|
|
155
|
+
capabilities: (args) => [`filesystem:write:${MOUNT}/${local(args.path)}`],
|
|
156
|
+
execute: async ({ path, old_text: oldText, new_text: newText }) => {
|
|
157
|
+
const file = local(path);
|
|
158
|
+
const text = await sandbox.readFile(file);
|
|
159
|
+
const count = text.split(String(oldText)).length - 1;
|
|
160
|
+
if (count === 0)
|
|
161
|
+
throw new Error(`old_text was not found in ${file}`);
|
|
162
|
+
if (count > 1)
|
|
163
|
+
throw new Error(`old_text appears ${count} times in ${file}; include more context so it appears once`);
|
|
164
|
+
await sandbox.writeFile(file, text.replace(String(oldText), () => String(newText)));
|
|
165
|
+
return { edited: file };
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function loadSkillTool(skills, sandbox) {
|
|
170
|
+
const byName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
171
|
+
return tool({
|
|
172
|
+
name: 'load_skill',
|
|
173
|
+
description: 'Loads the instructions of a skill by name.',
|
|
174
|
+
parameters: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: { name: { type: 'string', enum: skills.map((skill) => skill.name) } },
|
|
177
|
+
required: ['name'],
|
|
178
|
+
},
|
|
179
|
+
capabilities: [],
|
|
180
|
+
execute: async ({ name }) => {
|
|
181
|
+
const skill = byName.get(String(name));
|
|
182
|
+
if (!skill)
|
|
183
|
+
throw new Error(`No skill named "${String(name)}"`);
|
|
184
|
+
if (skill.instructions !== undefined)
|
|
185
|
+
return skill.instructions;
|
|
186
|
+
if (skill.file && sandbox)
|
|
187
|
+
return sandbox.readFile(skill.file);
|
|
188
|
+
throw new Error(`Skill "${skill.name}" has no instructions`);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
function delegateTool(subagents, helpers) {
|
|
193
|
+
return tool({
|
|
194
|
+
name: 'delegate',
|
|
195
|
+
description: 'Hands a self-contained task to a helper, which works in a fresh context and returns its answer.',
|
|
196
|
+
parameters: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
properties: {
|
|
199
|
+
agent: { type: 'string', enum: subagents.map((helper) => helper.name) },
|
|
200
|
+
task: { type: 'string', description: 'Everything the helper needs to know: it sees nothing else.' },
|
|
201
|
+
},
|
|
202
|
+
required: ['agent', 'task'],
|
|
203
|
+
},
|
|
204
|
+
capabilities: [],
|
|
205
|
+
execute: async ({ agent, task }, context) => {
|
|
206
|
+
const helper = helpers.get(String(agent));
|
|
207
|
+
if (!helper)
|
|
208
|
+
throw new Error(`No helper named "${String(agent)}"`);
|
|
209
|
+
const result = await helper.invoke(agentInput(String(task)), {
|
|
210
|
+
...(context.principal ? { principal: context.principal } : {}),
|
|
211
|
+
...(context.signal ? { signal: context.signal } : {}),
|
|
212
|
+
});
|
|
213
|
+
return (result.state.answer || `The ${String(agent)} helper finished without an answer (${result.state.stopReason}).`);
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|