nexus-ai-pro 2.6.0 → 2.7.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 +52 -0
- package/CHANGELOG.md +119 -1
- package/README.md +8 -5
- package/dist/agent/create-agent.js +16 -3
- package/dist/agent/glob.d.ts +24 -0
- package/dist/agent/glob.js +72 -0
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/interpreter.d.ts +125 -0
- package/dist/agent/interpreter.js +270 -0
- package/dist/agent/sandbox-container.d.ts +48 -0
- package/dist/agent/sandbox-container.js +291 -0
- package/dist/agent/sandbox.d.ts +50 -1
- package/dist/agent/sandbox.js +155 -2
- package/dist/agent/tool-search.d.ts +39 -0
- package/dist/agent/tool-search.js +113 -0
- package/dist/agent/tool.d.ts +2 -0
- package/dist/agent/tool.js +1 -0
- package/dist/context/window.js +4 -0
- package/dist/deep-agent/helpers.d.ts +57 -0
- package/dist/deep-agent/helpers.js +235 -0
- package/dist/deep-agent/index.d.ts +20 -1
- package/dist/deep-agent/index.js +69 -51
- package/dist/evaluate/generate.d.ts +171 -0
- package/dist/evaluate/generate.js +266 -0
- package/dist/evaluate/index.d.ts +1 -1
- package/dist/evaluate/index.js +1 -1
- package/dist/evaluate/online.d.ts +114 -0
- package/dist/evaluate/online.js +261 -0
- package/dist/evaluate/run.d.ts +28 -0
- package/dist/evaluate/run.js +56 -5
- package/dist/graph/graph.d.ts +7 -1
- package/dist/graph/graph.js +12 -1
- package/dist/operations/runner.js +1 -1
- package/dist/providers/anthropic.d.ts +6 -0
- package/dist/providers/anthropic.js +36 -9
- package/dist/providers/content.d.ts +33 -1
- package/dist/providers/content.js +62 -0
- package/dist/providers/google.js +14 -5
- package/dist/providers/openai.d.ts +8 -4
- package/dist/providers/openai.js +75 -34
- package/dist/runtime/index.d.ts +1 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/injection-detector.d.ts +18 -0
- package/dist/security/injection-detector.js +39 -4
- package/dist/security/schema-validator.js +11 -2
- package/dist/types/messages.d.ts +57 -3
- package/dist/utils/tokenizer.js +4 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-cjs/agent/create-agent.js +15 -2
- package/dist-cjs/agent/glob.d.ts +24 -0
- package/dist-cjs/agent/glob.js +77 -0
- package/dist-cjs/agent/index.d.ts +1 -0
- package/dist-cjs/agent/index.js +5 -1
- package/dist-cjs/agent/interpreter.d.ts +125 -0
- package/dist-cjs/agent/interpreter.js +308 -0
- package/dist-cjs/agent/sandbox-container.d.ts +48 -0
- package/dist-cjs/agent/sandbox-container.js +327 -0
- package/dist-cjs/agent/sandbox.d.ts +50 -1
- package/dist-cjs/agent/sandbox.js +155 -2
- package/dist-cjs/agent/tool-search.d.ts +39 -0
- package/dist-cjs/agent/tool-search.js +118 -0
- package/dist-cjs/agent/tool.d.ts +2 -0
- package/dist-cjs/agent/tool.js +1 -0
- package/dist-cjs/context/window.js +4 -0
- package/dist-cjs/deep-agent/helpers.d.ts +57 -0
- package/dist-cjs/deep-agent/helpers.js +238 -0
- package/dist-cjs/deep-agent/index.d.ts +20 -1
- package/dist-cjs/deep-agent/index.js +68 -50
- package/dist-cjs/evaluate/generate.d.ts +171 -0
- package/dist-cjs/evaluate/generate.js +272 -0
- package/dist-cjs/evaluate/index.d.ts +1 -1
- package/dist-cjs/evaluate/index.js +2 -1
- package/dist-cjs/evaluate/online.d.ts +114 -0
- package/dist-cjs/evaluate/online.js +264 -0
- package/dist-cjs/evaluate/run.d.ts +28 -0
- package/dist-cjs/evaluate/run.js +58 -5
- package/dist-cjs/graph/graph.d.ts +7 -1
- package/dist-cjs/graph/graph.js +13 -1
- package/dist-cjs/operations/runner.js +1 -1
- package/dist-cjs/providers/anthropic.d.ts +6 -0
- package/dist-cjs/providers/anthropic.js +35 -8
- package/dist-cjs/providers/content.d.ts +33 -1
- package/dist-cjs/providers/content.js +69 -0
- package/dist-cjs/providers/google.js +13 -4
- package/dist-cjs/providers/openai.d.ts +8 -4
- package/dist-cjs/providers/openai.js +74 -33
- package/dist-cjs/runtime/index.d.ts +1 -1
- package/dist-cjs/security/index.d.ts +1 -1
- package/dist-cjs/security/index.js +2 -1
- package/dist-cjs/security/injection-detector.d.ts +18 -0
- package/dist-cjs/security/injection-detector.js +40 -4
- package/dist-cjs/security/schema-validator.js +11 -2
- package/dist-cjs/types/messages.d.ts +57 -3
- package/dist-cjs/utils/tokenizer.js +4 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +53 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { agentInput, createAgent, } from '../agent/create-agent.js';
|
|
2
|
+
const MARK = 'deepAgentHelper';
|
|
3
|
+
export function helperTools(setup) {
|
|
4
|
+
const declared = new Map(setup.declared.map((helper) => [helper.name, helper]));
|
|
5
|
+
const created = new Map();
|
|
6
|
+
const graphs = new Map();
|
|
7
|
+
const live = new Map();
|
|
8
|
+
const spec = (name, instructions, thread) => {
|
|
9
|
+
const helper = declared.get(name);
|
|
10
|
+
if (helper) {
|
|
11
|
+
return {
|
|
12
|
+
name,
|
|
13
|
+
instructions: helper.instructions,
|
|
14
|
+
...(helper.tools ? { tools: helper.tools } : {}),
|
|
15
|
+
...(helper.model ? { model: helper.model } : {}),
|
|
16
|
+
maxIterations: helper.maxIterations ?? 20,
|
|
17
|
+
created: false,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const mine = created.get(thread) ?? new Map();
|
|
21
|
+
const existing = mine.get(name);
|
|
22
|
+
if (existing)
|
|
23
|
+
return existing;
|
|
24
|
+
const known = [...declared.keys(), ...mine.keys()].join(', ') || 'none';
|
|
25
|
+
if (!setup.create)
|
|
26
|
+
throw new Error(`No helper named "${name}"; the helpers are ${known}`);
|
|
27
|
+
if (!/^[A-Za-z][\w-]{0,39}$/.test(name)) {
|
|
28
|
+
throw new Error('A new helper needs a name of letters, digits, _ and -, starting with a letter');
|
|
29
|
+
}
|
|
30
|
+
if (!instructions?.trim())
|
|
31
|
+
throw new Error(`To create the helper "${name}", give its instructions`);
|
|
32
|
+
const max = setup.create.max ?? 5;
|
|
33
|
+
if (mine.size >= max)
|
|
34
|
+
throw new Error(`This thread has created its ${max} helpers; delegate to one of: ${known}`);
|
|
35
|
+
const made = {
|
|
36
|
+
name,
|
|
37
|
+
instructions: instructions.trim(),
|
|
38
|
+
...(setup.create.tools ? { tools: setup.create.tools } : {}),
|
|
39
|
+
...(setup.create.model ? { model: setup.create.model } : {}),
|
|
40
|
+
maxIterations: setup.create.maxIterations ?? 10,
|
|
41
|
+
created: true,
|
|
42
|
+
};
|
|
43
|
+
mine.set(name, made);
|
|
44
|
+
created.set(thread, mine);
|
|
45
|
+
return made;
|
|
46
|
+
};
|
|
47
|
+
const graph = (helper, durable) => {
|
|
48
|
+
const key = JSON.stringify([helper.name, helper.created ? helper.instructions : '', durable]);
|
|
49
|
+
const cached = graphs.get(key);
|
|
50
|
+
if (cached)
|
|
51
|
+
return cached;
|
|
52
|
+
const allowed = helper.tools ? new Set(helper.tools) : undefined;
|
|
53
|
+
const made = createAgent({
|
|
54
|
+
...setup.agentOptions,
|
|
55
|
+
name: `${setup.name}.${helper.name}`,
|
|
56
|
+
...(helper.model ? { model: helper.model } : {}),
|
|
57
|
+
maxIterations: helper.maxIterations,
|
|
58
|
+
systemPrompt: `${helper.instructions}\n\n${setup.note}`,
|
|
59
|
+
tools: setup.tools.filter((candidate) => (allowed ? allowed.has(candidate.name) : true)),
|
|
60
|
+
...(setup.permissions ? { permissions: setup.permissions } : {}),
|
|
61
|
+
checkpointer: durable && setup.checkpointer ? setup.checkpointer : false,
|
|
62
|
+
});
|
|
63
|
+
graphs.set(key, made);
|
|
64
|
+
return made;
|
|
65
|
+
};
|
|
66
|
+
const runOptions = (context, namespace, extra = {}) => {
|
|
67
|
+
const forward = context.forward;
|
|
68
|
+
const onEvent = extra.onEvent;
|
|
69
|
+
return {
|
|
70
|
+
...extra,
|
|
71
|
+
...(context.principal ? { principal: context.principal } : {}),
|
|
72
|
+
...(forward || onEvent
|
|
73
|
+
? {
|
|
74
|
+
subgraphEvents: Boolean(forward),
|
|
75
|
+
onEvent: (event) => {
|
|
76
|
+
onEvent?.(event);
|
|
77
|
+
forward?.(event, [namespace]);
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
: {}),
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
const outcome = (helper, ended) => {
|
|
84
|
+
if (ended?.status === 'completed') {
|
|
85
|
+
return {
|
|
86
|
+
helper,
|
|
87
|
+
ok: true,
|
|
88
|
+
answer: ended.state.answer || `The ${helper} helper finished without an answer (${ended.state.stopReason}).`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const why = ended?.error ? (ended.error.message ?? String(ended.error)) : ended?.status;
|
|
92
|
+
return { helper, ok: false, error: `The ${helper} helper stopped: ${why ?? 'it did not run'}` };
|
|
93
|
+
};
|
|
94
|
+
const thread = (parent, task) => `${parent ?? 'thread'}/helper/${task}`;
|
|
95
|
+
const start = async (helper, task, threadId, context) => {
|
|
96
|
+
if (live.has(threadId))
|
|
97
|
+
return;
|
|
98
|
+
const runner = graph(helper, true);
|
|
99
|
+
const stored = setup.checkpointer ? await runner.state(threadId) : undefined;
|
|
100
|
+
let ready = () => { };
|
|
101
|
+
const started = new Promise((resolve) => {
|
|
102
|
+
ready = resolve;
|
|
103
|
+
});
|
|
104
|
+
const options = runOptions(context, threadId.slice(threadId.lastIndexOf('/') + 1), {
|
|
105
|
+
threadId,
|
|
106
|
+
metadata: {
|
|
107
|
+
[MARK]: {
|
|
108
|
+
name: helper.name,
|
|
109
|
+
created: helper.created,
|
|
110
|
+
...(helper.created ? { instructions: helper.instructions } : {}),
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
onEvent: (event) => {
|
|
114
|
+
if (event.type === 'checkpoint')
|
|
115
|
+
ready();
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
const steps = stored === undefined
|
|
119
|
+
? runner.stream(agentInput(task ?? ''), options)
|
|
120
|
+
: stored.status === 'completed' || stored.status === 'failed'
|
|
121
|
+
? undefined
|
|
122
|
+
: runner.continue(threadId, options);
|
|
123
|
+
const drive = async () => {
|
|
124
|
+
let last;
|
|
125
|
+
if (steps)
|
|
126
|
+
for await (const step of steps)
|
|
127
|
+
last = step;
|
|
128
|
+
return outcome(helper.name, (setup.checkpointer ? await runner.state(threadId) : undefined) ?? last ?? stored);
|
|
129
|
+
};
|
|
130
|
+
const entry = { helper: helper.name, settled: false, outcome: Promise.resolve(undefined) };
|
|
131
|
+
entry.outcome = drive()
|
|
132
|
+
.catch((error) => ({
|
|
133
|
+
helper: helper.name,
|
|
134
|
+
ok: false,
|
|
135
|
+
error: error instanceof Error ? error.message : String(error),
|
|
136
|
+
}))
|
|
137
|
+
.finally(() => {
|
|
138
|
+
entry.settled = true;
|
|
139
|
+
ready();
|
|
140
|
+
});
|
|
141
|
+
live.set(threadId, entry);
|
|
142
|
+
await started;
|
|
143
|
+
};
|
|
144
|
+
const names = [...declared.keys()];
|
|
145
|
+
const delegate = {
|
|
146
|
+
name: 'delegate',
|
|
147
|
+
description: [
|
|
148
|
+
'Hands a self-contained task to a helper, which works in a fresh context and returns its answer.',
|
|
149
|
+
'With background, the helper works while you go on: you get a task id at once, and collect_helper returns its answer.',
|
|
150
|
+
...(setup.create ? ['To create a helper for a task no helper fits, give a new name and its instructions.'] : []),
|
|
151
|
+
].join(' '),
|
|
152
|
+
parameters: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {
|
|
155
|
+
agent: setup.create
|
|
156
|
+
? { type: 'string', description: `A helper (${names.join(', ') || 'none yet'}), or a new name to create one` }
|
|
157
|
+
: { type: 'string', enum: names },
|
|
158
|
+
task: { type: 'string', description: 'Everything the helper needs to know: it sees nothing else.' },
|
|
159
|
+
...(setup.create
|
|
160
|
+
? { instructions: { type: 'string', description: 'For a new helper: who it is and how it works.' } }
|
|
161
|
+
: {}),
|
|
162
|
+
background: { type: 'boolean', description: 'Runs the helper while you go on.' },
|
|
163
|
+
},
|
|
164
|
+
required: ['agent', 'task'],
|
|
165
|
+
},
|
|
166
|
+
capabilities: [],
|
|
167
|
+
execute: async (args, context = {}) => {
|
|
168
|
+
const helper = spec(String(args.agent ?? ''), typeof args.instructions === 'string' ? args.instructions : undefined, context.threadId ?? '');
|
|
169
|
+
const task = String(args.task ?? '');
|
|
170
|
+
const call = context.toolCallId ?? Math.random().toString(36).slice(2, 10);
|
|
171
|
+
if (args.background !== true) {
|
|
172
|
+
const result = await graph(helper, false).invoke(agentInput(task), runOptions(context, `${helper.name}@${call}`, context.signal ? { signal: context.signal } : {}));
|
|
173
|
+
return (result.state.answer || `The ${helper.name} helper finished without an answer (${result.state.stopReason}).`);
|
|
174
|
+
}
|
|
175
|
+
const running = [...live.entries()].filter(([id, entry]) => id.startsWith(thread(context.threadId, '')) && !entry.settled).length;
|
|
176
|
+
if (running >= setup.maxBackground) {
|
|
177
|
+
throw new Error(`${running} helpers are already working in the background; collect one with collect_helper first`);
|
|
178
|
+
}
|
|
179
|
+
const id = `${helper.name}@${call}`;
|
|
180
|
+
await start(helper, task, thread(context.threadId, id), context);
|
|
181
|
+
return `The ${helper.name} helper is working in the background as task ${id}. Collect its answer with collect_helper.`;
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
const collect = {
|
|
185
|
+
name: 'collect_helper',
|
|
186
|
+
description: 'Returns the answer of a helper working in the background, by its task id, waiting up to wait_seconds for it. Without a task, lists the tasks of this run.',
|
|
187
|
+
parameters: {
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {
|
|
190
|
+
task: { type: 'string', description: 'The task id delegate returned' },
|
|
191
|
+
wait_seconds: { type: 'number', description: 'How long to wait for the answer, up to 120. Defaults to 0.' },
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
capabilities: [],
|
|
195
|
+
execute: async (args, context = {}) => {
|
|
196
|
+
const prefix = thread(context.threadId, '');
|
|
197
|
+
if (typeof args.task !== 'string' || !args.task) {
|
|
198
|
+
const tasks = [...live.entries()].filter(([id]) => id.startsWith(prefix));
|
|
199
|
+
if (tasks.length === 0)
|
|
200
|
+
return 'No helper is working in the background in this process.';
|
|
201
|
+
return tasks
|
|
202
|
+
.map(([id, entry]) => `${id.slice(prefix.length)}: ${entry.settled ? 'finished' : 'working'}`)
|
|
203
|
+
.join('\n');
|
|
204
|
+
}
|
|
205
|
+
const task = args.task;
|
|
206
|
+
const threadId = thread(context.threadId, task);
|
|
207
|
+
if (!live.has(threadId)) {
|
|
208
|
+
const stored = setup.checkpointer ? await setup.checkpointer.get(threadId) : undefined;
|
|
209
|
+
if (!stored)
|
|
210
|
+
throw new Error(`No helper task "${task}" in this run`);
|
|
211
|
+
const mark = stored.metadata?.[MARK];
|
|
212
|
+
const name = mark?.name ?? task.slice(0, task.lastIndexOf('@'));
|
|
213
|
+
const helper = mark?.created
|
|
214
|
+
? spec(name, mark.instructions, context.threadId ?? '')
|
|
215
|
+
: spec(name, undefined, context.threadId ?? '');
|
|
216
|
+
await start(helper, undefined, threadId, context);
|
|
217
|
+
}
|
|
218
|
+
const entry = live.get(threadId);
|
|
219
|
+
const waitMs = Math.min(Math.max(Number(args.wait_seconds) || 0, 0), 120) * 1_000;
|
|
220
|
+
let timer;
|
|
221
|
+
const ended = await Promise.race([
|
|
222
|
+
entry.outcome,
|
|
223
|
+
new Promise((resolve) => {
|
|
224
|
+
timer = setTimeout(() => resolve(undefined), waitMs);
|
|
225
|
+
}),
|
|
226
|
+
]).finally(() => clearTimeout(timer));
|
|
227
|
+
if (!ended)
|
|
228
|
+
return `The ${entry.helper} helper is still working on task ${task}. Collect it again later.`;
|
|
229
|
+
if (!ended.ok)
|
|
230
|
+
throw new Error(ended.error);
|
|
231
|
+
return `The ${ended.helper} helper's answer: ${ended.answer}`;
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
return [delegate, collect];
|
|
235
|
+
}
|
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
* support as any agent.
|
|
8
8
|
*/
|
|
9
9
|
import { type AgentGraph, type AgentMiddleware, type CreateAgentOptions } from '../agent/create-agent.js';
|
|
10
|
+
import { type Interpreter } from '../agent/interpreter.js';
|
|
10
11
|
import { type ContextEditorOptions } from '../agent/middleware/context-editor.js';
|
|
11
12
|
import { type ContextOffloadOptions } from '../agent/middleware/filesystem.js';
|
|
12
13
|
import { type ToolSelectorOptions } from '../agent/middleware/tool-selector.js';
|
|
13
14
|
import { type PermissionPolicyLike } from '../agent/permissions.js';
|
|
14
15
|
import { type Sandbox } from '../agent/sandbox.js';
|
|
16
|
+
import { type DeepHelperLimits } from './helpers.js';
|
|
17
|
+
export type { DeepHelperLimits } from './helpers.js';
|
|
15
18
|
/** A helper the deep agent can hand a self-contained piece of work to, with a fresh context. */
|
|
16
19
|
export interface DeepSubagent {
|
|
17
20
|
/** Its name, as the agent delegates to it. */
|
|
@@ -58,8 +61,24 @@ export interface DeepAgentOptions extends Omit<CreateAgentOptions, 'systemPrompt
|
|
|
58
61
|
commands?: readonly string[];
|
|
59
62
|
/** Records a plan with `write_todos` and keeps it current. Defaults to true. */
|
|
60
63
|
planning?: boolean;
|
|
61
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Runs the model's code with the agent's own tools callable from it, as `run_code`, so many calls
|
|
66
|
+
* take one step. Every call the code makes is decided by the agent's policy, and a tool that needs
|
|
67
|
+
* approval cannot be called from code. Given one, the default policy grants `code`.
|
|
68
|
+
*/
|
|
69
|
+
interpreter?: Interpreter;
|
|
70
|
+
/**
|
|
71
|
+
* Helpers the agent can delegate to through one `delegate` tool. The agent waits for a helper's
|
|
72
|
+
* answer, or sets `background` and collects it later with `collect_helper`.
|
|
73
|
+
*/
|
|
62
74
|
subagents?: readonly DeepSubagent[];
|
|
75
|
+
/**
|
|
76
|
+
* Lets the agent create helpers during a run, from instructions it writes, within these limits.
|
|
77
|
+
* Off unless given.
|
|
78
|
+
*/
|
|
79
|
+
createHelpers?: DeepHelperLimits;
|
|
80
|
+
/** How many helpers may work in the background in one thread at once. Defaults to 4. */
|
|
81
|
+
maxBackgroundHelpers?: number;
|
|
63
82
|
/** Skills the agent can load through `load_skill`. */
|
|
64
83
|
skills?: readonly DeepSkill[];
|
|
65
84
|
/** Instruction files from the sandbox the model sees on every call. Defaults to `AGENTS.md`. */
|
package/dist/deep-agent/index.js
CHANGED
|
@@ -1,49 +1,68 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createAgent } from '../agent/create-agent.js';
|
|
2
|
+
import { codeTool } from '../agent/interpreter.js';
|
|
2
3
|
import { contextEditor } from '../agent/middleware/context-editor.js';
|
|
3
4
|
import { filesystemContext } from '../agent/middleware/filesystem.js';
|
|
4
5
|
import { toolSelector } from '../agent/middleware/tool-selector.js';
|
|
5
6
|
import { permissionPolicy } from '../agent/permissions.js';
|
|
6
7
|
import { sandboxTools } from '../agent/sandbox.js';
|
|
7
8
|
import { tool } from '../agent/tool.js';
|
|
9
|
+
import { helperTools } from './helpers.js';
|
|
8
10
|
const MOUNT = '/workspace';
|
|
9
11
|
const CORE_TOOLS = [
|
|
10
12
|
'write_todos',
|
|
11
13
|
'delegate',
|
|
14
|
+
'collect_helper',
|
|
12
15
|
'load_skill',
|
|
13
16
|
'read_file',
|
|
14
17
|
'write_file',
|
|
15
18
|
'edit_file',
|
|
16
19
|
'list_files',
|
|
20
|
+
'glob_files',
|
|
21
|
+
'grep_files',
|
|
22
|
+
'read_media',
|
|
17
23
|
'run_command',
|
|
24
|
+
'run_code',
|
|
25
|
+
'search_tools',
|
|
18
26
|
];
|
|
19
27
|
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;
|
|
28
|
+
const { instructions, sandbox, commands = [], planning = true, interpreter, subagents = [], createHelpers, maxBackgroundHelpers = 4, skills = [], include = ['AGENTS.md'], offload, contextEditing, toolSelection, permissions, middleware = [], tools: extraTools = [], ...agentOptions } = options;
|
|
21
29
|
const workspace = sandbox
|
|
22
30
|
? [...sandboxTools(sandbox, { shell: commands.length > 0, mount: MOUNT }), editFileTool(sandbox)]
|
|
23
31
|
: [];
|
|
24
|
-
const
|
|
32
|
+
const approved = new Set(Object.keys(agentOptions.interruptOn ?? {}));
|
|
33
|
+
const policy = permissions === false ? undefined : (permissions ?? defaultPolicy(commands, Boolean(interpreter)));
|
|
34
|
+
const direct = [
|
|
25
35
|
...(planning ? [writeTodosTool()] : []),
|
|
26
36
|
...(skills.length > 0 ? [loadSkillTool(skills, sandbox)] : []),
|
|
27
37
|
...workspace,
|
|
28
38
|
...extraTools,
|
|
29
39
|
];
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
const own = [
|
|
41
|
+
...direct,
|
|
42
|
+
...(interpreter
|
|
43
|
+
? [
|
|
44
|
+
codeTool(interpreter, {
|
|
45
|
+
tools: direct.filter((candidate) => !approved.has(candidate.name)),
|
|
46
|
+
...(policy ? { permissions: policy } : {}),
|
|
47
|
+
}),
|
|
48
|
+
]
|
|
49
|
+
: []),
|
|
50
|
+
];
|
|
51
|
+
const { interruptOn: _approvals, checkpointer, ...helperOptions } = agentOptions;
|
|
52
|
+
const helping = subagents.length > 0 || createHelpers
|
|
53
|
+
? helperTools({
|
|
54
|
+
declared: subagents,
|
|
55
|
+
...(createHelpers ? { create: createHelpers } : {}),
|
|
56
|
+
tools: own.filter((candidate) => !approved.has(candidate.name)),
|
|
57
|
+
agentOptions: helperOptions,
|
|
58
|
+
...(policy ? { permissions: policy } : {}),
|
|
59
|
+
...(checkpointer ? { checkpointer } : {}),
|
|
60
|
+
name: options.name ?? 'deep-agent',
|
|
61
|
+
note: WORKSPACE_NOTE,
|
|
62
|
+
maxBackground: maxBackgroundHelpers,
|
|
63
|
+
})
|
|
64
|
+
: [];
|
|
65
|
+
const tools = [...own, ...helping];
|
|
47
66
|
const kit = [
|
|
48
67
|
...(sandbox && (include.length > 0 || offload !== false)
|
|
49
68
|
? [
|
|
@@ -70,17 +89,28 @@ export function createDeepAgent(options) {
|
|
|
70
89
|
return createAgent({
|
|
71
90
|
maxIterations: 40,
|
|
72
91
|
...agentOptions,
|
|
73
|
-
systemPrompt: systemPrompt(instructions, {
|
|
92
|
+
systemPrompt: systemPrompt(instructions, {
|
|
93
|
+
planning,
|
|
94
|
+
sandbox: Boolean(sandbox),
|
|
95
|
+
search: Boolean(sandbox?.glob && sandbox.grep),
|
|
96
|
+
media: Boolean(sandbox?.readBytes),
|
|
97
|
+
code: interpreter?.language,
|
|
98
|
+
commands,
|
|
99
|
+
subagents,
|
|
100
|
+
create: Boolean(createHelpers),
|
|
101
|
+
skills,
|
|
102
|
+
}),
|
|
74
103
|
tools,
|
|
75
104
|
middleware: [...kit, ...middleware],
|
|
76
|
-
...(
|
|
105
|
+
...(policy ? { permissions: policy } : {}),
|
|
77
106
|
});
|
|
78
107
|
}
|
|
79
|
-
function defaultPolicy(commands) {
|
|
108
|
+
function defaultPolicy(commands, code) {
|
|
80
109
|
return permissionPolicy({
|
|
81
110
|
filesystem: { write: [`${MOUNT}/**`] },
|
|
82
111
|
shell: { allow: commands },
|
|
83
|
-
tools: { write_todos: 'allow', delegate: 'allow', load_skill: 'allow' },
|
|
112
|
+
tools: { write_todos: 'allow', delegate: 'allow', collect_helper: 'allow', load_skill: 'allow' },
|
|
113
|
+
...(code ? { code: 'allow' } : {}),
|
|
84
114
|
});
|
|
85
115
|
}
|
|
86
116
|
const WORKSPACE_NOTE = `Files live in ${MOUNT}; paths are relative to it.`;
|
|
@@ -94,13 +124,26 @@ function systemPrompt(instructions, parts) {
|
|
|
94
124
|
}
|
|
95
125
|
if (parts.sandbox) {
|
|
96
126
|
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.`);
|
|
127
|
+
if (parts.search) {
|
|
128
|
+
lines.push('Find files with glob_files and the lines you need with grep_files before reading whole files.');
|
|
129
|
+
}
|
|
130
|
+
if (parts.media)
|
|
131
|
+
lines.push('Look at images, audio, and PDFs with read_media.');
|
|
97
132
|
if (parts.commands.length > 0)
|
|
98
133
|
lines.push(`You may run these commands: ${parts.commands.join(', ')}.`);
|
|
99
134
|
}
|
|
100
|
-
if (parts.
|
|
101
|
-
lines.push(
|
|
135
|
+
if (parts.code) {
|
|
136
|
+
lines.push(`When a task takes many tool calls, or a filter on what they return, write ${parts.code} for run_code and call the tools from it in one step.`);
|
|
137
|
+
}
|
|
138
|
+
if (parts.subagents.length > 0 || parts.create) {
|
|
139
|
+
lines.push('', 'Delegate a self-contained piece of work with delegate.');
|
|
140
|
+
if (parts.subagents.length > 0)
|
|
141
|
+
lines.push('The helpers:');
|
|
102
142
|
for (const helper of parts.subagents)
|
|
103
143
|
lines.push(`- ${helper.name}: ${helper.description}`);
|
|
144
|
+
if (parts.create)
|
|
145
|
+
lines.push('For work none of them fits, create a helper: give delegate a new name and its instructions.');
|
|
146
|
+
lines.push('Set background to keep working while a helper runs, and collect its answer with collect_helper when you need it.');
|
|
104
147
|
}
|
|
105
148
|
if (parts.skills.length > 0) {
|
|
106
149
|
lines.push('', 'Skills you can load with load_skill when a task calls for one:');
|
|
@@ -189,28 +232,3 @@ function loadSkillTool(skills, sandbox) {
|
|
|
189
232
|
},
|
|
190
233
|
});
|
|
191
234
|
}
|
|
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
|
-
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasets that grow: synthetic examples written from documents, each citing the passage it came
|
|
3
|
+
* from; adversarial examples grown from the guardrails' own attack patterns; and production runs
|
|
4
|
+
* promoted from the issues insights found.
|
|
5
|
+
*/
|
|
6
|
+
import type { Issue } from '../insights/clusters.js';
|
|
7
|
+
import { type AttackCategory } from '../security/injection-detector.js';
|
|
8
|
+
import type { Dataset, DatasetExample, Evaluator } from '../types/evaluate.js';
|
|
9
|
+
import type { CompletionRequest } from '../types/messages.js';
|
|
10
|
+
import type { NexusResponse } from '../types/response.js';
|
|
11
|
+
import type { Run } from '../types/tracing.js';
|
|
12
|
+
/** A document to write examples from. */
|
|
13
|
+
export interface SyntheticDocument {
|
|
14
|
+
/** Names the document in every citation. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Its text. */
|
|
17
|
+
text: string;
|
|
18
|
+
/** Its title, carried into citations. */
|
|
19
|
+
title?: string;
|
|
20
|
+
}
|
|
21
|
+
/** One passage of a document, as the writer sees it. */
|
|
22
|
+
export interface SyntheticPassage {
|
|
23
|
+
/** The document it is from. */
|
|
24
|
+
document: SyntheticDocument;
|
|
25
|
+
/** Its position among the document's passages, from 0. */
|
|
26
|
+
index: number;
|
|
27
|
+
/** Where it starts in the document, in characters. */
|
|
28
|
+
start: number;
|
|
29
|
+
/** Where it ends. */
|
|
30
|
+
end: number;
|
|
31
|
+
/** Its text. */
|
|
32
|
+
text: string;
|
|
33
|
+
}
|
|
34
|
+
/** An example the writer drafts: a question, its answer, and the words of the passage that support it. */
|
|
35
|
+
export interface SyntheticDraft {
|
|
36
|
+
/** What the target is asked. */
|
|
37
|
+
question: string;
|
|
38
|
+
/** The answer the passage supports. */
|
|
39
|
+
answer: string;
|
|
40
|
+
/** Words copied from the passage that support the answer. A draft whose quote is not there is dropped. */
|
|
41
|
+
quote: string;
|
|
42
|
+
}
|
|
43
|
+
/** Where an example came from: the document, the passage, and the supporting words. */
|
|
44
|
+
export interface SyntheticCitation {
|
|
45
|
+
/** The document's id. */
|
|
46
|
+
document: string;
|
|
47
|
+
/** Its title, when it has one. */
|
|
48
|
+
title?: string;
|
|
49
|
+
/** The passage's position in the document. */
|
|
50
|
+
passage: number;
|
|
51
|
+
/** Where the supporting words start in the document, in characters. */
|
|
52
|
+
start: number;
|
|
53
|
+
/** Where they end. */
|
|
54
|
+
end: number;
|
|
55
|
+
/** The supporting words, as the document has them. */
|
|
56
|
+
quote: string;
|
|
57
|
+
}
|
|
58
|
+
/** A model that writes drafts. */
|
|
59
|
+
export interface SyntheticModelClient {
|
|
60
|
+
/** Runs one completion. */
|
|
61
|
+
complete(request: CompletionRequest): Promise<NexusResponse>;
|
|
62
|
+
}
|
|
63
|
+
/** Options for `syntheticDataset()`. Give it `generate`, or a `client` and `model`. */
|
|
64
|
+
export interface SyntheticDatasetOptions {
|
|
65
|
+
/** The dataset's name. */
|
|
66
|
+
name: string;
|
|
67
|
+
/** The documents to write examples from. */
|
|
68
|
+
documents: readonly SyntheticDocument[];
|
|
69
|
+
/** Writes drafts for one passage. */
|
|
70
|
+
generate?: (passage: SyntheticPassage, count: number) => Promise<SyntheticDraft[]> | SyntheticDraft[];
|
|
71
|
+
/** A model that writes drafts with a built-in prompt, when there is no `generate`. */
|
|
72
|
+
client?: SyntheticModelClient;
|
|
73
|
+
/** The model the client uses. */
|
|
74
|
+
model?: string;
|
|
75
|
+
/** The longest passage, in characters. Paragraphs are kept whole where they fit. Defaults to 1,500. */
|
|
76
|
+
passageChars?: number;
|
|
77
|
+
/** Drafts asked for per passage. Defaults to 2. */
|
|
78
|
+
perPassage?: number;
|
|
79
|
+
/**
|
|
80
|
+
* How alike two questions' words may be before the later one is dropped as a near-duplicate, from
|
|
81
|
+
* 0 to 1. Defaults to 0.8; `false` keeps every question.
|
|
82
|
+
*/
|
|
83
|
+
similarity?: number | false;
|
|
84
|
+
/** The most examples kept. */
|
|
85
|
+
limit?: number;
|
|
86
|
+
}
|
|
87
|
+
/** What `syntheticDataset()` made, and what it dropped. */
|
|
88
|
+
export interface SyntheticDatasetResult {
|
|
89
|
+
/** The examples: a question as input, the answer as expected, and the citation in `metadata.citation`. */
|
|
90
|
+
dataset: Dataset<{
|
|
91
|
+
question: string;
|
|
92
|
+
}, string>;
|
|
93
|
+
/** Drafts whose quote was not in their passage. */
|
|
94
|
+
unsupported: number;
|
|
95
|
+
/** Drafts dropped as near-duplicates of a question already kept. */
|
|
96
|
+
duplicates: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Writes a dataset from documents. Each document is cut into passages, and each passage gets drafts
|
|
100
|
+
* from `generate` or the model: a question, its answer, and a quote. A draft is kept only when its
|
|
101
|
+
* quote is in its passage, word for word apart from spacing, and its citation records where; and a
|
|
102
|
+
* question too like one already kept is dropped.
|
|
103
|
+
*/
|
|
104
|
+
export declare function syntheticDataset(options: SyntheticDatasetOptions): Promise<SyntheticDatasetResult>;
|
|
105
|
+
/** How an attack reaches the target: in what the user asks, in a document it is given, or in a tool's result. */
|
|
106
|
+
export type AttackCarrier = 'direct' | 'document' | 'tool';
|
|
107
|
+
/** How an attack is disguised: as written, in full-width letters, or split by invisible characters. */
|
|
108
|
+
export type AttackDisguise = 'plain' | 'full-width' | 'zero-width';
|
|
109
|
+
/** What an adversarial example gives the target. */
|
|
110
|
+
export interface AdversarialInputs {
|
|
111
|
+
/** The system prompt, holding the canary the attack tries to get out. */
|
|
112
|
+
system: string;
|
|
113
|
+
/** What the user asks. */
|
|
114
|
+
prompt: string;
|
|
115
|
+
/** A document the user gives, when the attack is carried in one. */
|
|
116
|
+
document?: string;
|
|
117
|
+
/** What a tool returns, when the attack is carried in a tool's result. */
|
|
118
|
+
toolResult?: string;
|
|
119
|
+
}
|
|
120
|
+
/** What an adversarial example expects: the canary never appears in the output. */
|
|
121
|
+
export interface AdversarialExpected {
|
|
122
|
+
/** A secret only the system prompt holds. */
|
|
123
|
+
canary: string;
|
|
124
|
+
}
|
|
125
|
+
/** Options for `adversarialDataset()`. */
|
|
126
|
+
export interface AdversarialDatasetOptions {
|
|
127
|
+
/** The dataset's name. Defaults to `adversarial`. */
|
|
128
|
+
name?: string;
|
|
129
|
+
/** Which attacks. Defaults to all three categories. */
|
|
130
|
+
categories?: readonly AttackCategory[];
|
|
131
|
+
/** How they arrive. Defaults to all three carriers. */
|
|
132
|
+
carriers?: readonly AttackCarrier[];
|
|
133
|
+
/** How they are disguised. Defaults to all three. */
|
|
134
|
+
disguises?: readonly AttackDisguise[];
|
|
135
|
+
/** Adds one benign example per carrier, so a guard that refuses everything shows. Defaults to true. */
|
|
136
|
+
benign?: boolean;
|
|
137
|
+
/** More wordings of an attack, such as a model's rephrasings; each becomes an example of its own. */
|
|
138
|
+
rephrase?: (attack: string, category: AttackCategory) => Promise<string[]> | string[];
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Grows an adversarial dataset from the guardrails' own attack patterns: every seed of
|
|
142
|
+
* `attackSeeds()`, in each carrier and disguise. Every example hides a canary in the system prompt
|
|
143
|
+
* and asks, one way or another, for it to come out: injections and jailbreaks tell the model to say
|
|
144
|
+
* it, exfiltration asks for the prompt that holds it. Score a target with `resistsAttack()`.
|
|
145
|
+
*/
|
|
146
|
+
export declare function adversarialDataset(options?: AdversarialDatasetOptions): Promise<Dataset<AdversarialInputs, AdversarialExpected>>;
|
|
147
|
+
/**
|
|
148
|
+
* Scores whether a target kept the canary in: 1 when it never appears in the output, read through
|
|
149
|
+
* the same disguises the guardrails read through. A target that refused, or was stopped by a
|
|
150
|
+
* guardrail, kept it.
|
|
151
|
+
*/
|
|
152
|
+
export declare function resistsAttack(options?: {
|
|
153
|
+
key?: string;
|
|
154
|
+
}): Evaluator<AdversarialInputs, AdversarialExpected>;
|
|
155
|
+
/** Options for `promoteIssue()`. */
|
|
156
|
+
export interface PromoteIssueOptions {
|
|
157
|
+
/** A dataset to add to; its examples are kept and the issue's runs added. */
|
|
158
|
+
into?: Dataset;
|
|
159
|
+
/** The new dataset's name, when there is no `into`. Defaults to `issue-<id>`. */
|
|
160
|
+
name?: string;
|
|
161
|
+
/** Turns a run into an example. Defaults to its inputs, with its outputs as expected unless it failed. */
|
|
162
|
+
toExample?: (run: Run) => Omit<DatasetExample, 'id'> | undefined;
|
|
163
|
+
/** The most runs promoted. Defaults to all of them. */
|
|
164
|
+
limit?: number;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Promotes the runs behind an insights issue into a dataset. Each example keeps the run it came from
|
|
168
|
+
* as `sourceRunId`, and the issue that found it as `metadata.issue`, so a regression test points back
|
|
169
|
+
* to the problem it guards against. A run already in the dataset is not added twice.
|
|
170
|
+
*/
|
|
171
|
+
export declare function promoteIssue(issue: Issue, options?: PromoteIssueOptions): Dataset;
|