nexus-ai-pro 2.5.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 +88 -0
- package/CHANGELOG.md +220 -1
- package/README.md +13 -4
- package/dist/agent/create-agent.d.ts +37 -2
- package/dist/agent/create-agent.js +77 -6
- package/dist/agent/glob.d.ts +24 -0
- package/dist/agent/glob.js +72 -0
- package/dist/agent/index.d.ts +3 -1
- 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 +21 -4
- package/dist/agent/tool.js +28 -4
- 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/event-stream.js +2 -0
- package/dist/graph/graph.d.ts +7 -1
- package/dist/graph/graph.js +16 -2
- package/dist/mcp/client.d.ts +9 -1
- package/dist/mcp/client.js +38 -8
- package/dist/mcp/server.js +42 -2
- package/dist/operations/runner.js +1 -1
- package/dist/protocols/a2a.d.ts +166 -1
- package/dist/protocols/a2a.js +499 -132
- package/dist/protocols/acp-editor.d.ts +43 -0
- package/dist/protocols/acp-editor.js +164 -0
- package/dist/protocols/acp.d.ts +76 -3
- package/dist/protocols/acp.js +180 -7
- package/dist/protocols/ag-ui.d.ts +23 -0
- package/dist/protocols/ag-ui.js +128 -8
- 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/tracing/instrument.js +14 -3
- package/dist/types/graph.d.ts +7 -0
- package/dist/types/messages.d.ts +69 -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.d.ts +37 -2
- package/dist-cjs/agent/create-agent.js +76 -5
- package/dist-cjs/agent/glob.d.ts +24 -0
- package/dist-cjs/agent/glob.js +77 -0
- package/dist-cjs/agent/index.d.ts +3 -1
- 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 +21 -4
- package/dist-cjs/agent/tool.js +28 -4
- 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/event-stream.js +2 -0
- package/dist-cjs/graph/graph.d.ts +7 -1
- package/dist-cjs/graph/graph.js +17 -2
- package/dist-cjs/mcp/client.d.ts +9 -1
- package/dist-cjs/mcp/client.js +37 -7
- package/dist-cjs/mcp/server.js +42 -2
- package/dist-cjs/operations/runner.js +1 -1
- package/dist-cjs/protocols/a2a.d.ts +166 -1
- package/dist-cjs/protocols/a2a.js +501 -132
- package/dist-cjs/protocols/acp-editor.d.ts +43 -0
- package/dist-cjs/protocols/acp-editor.js +167 -0
- package/dist-cjs/protocols/acp.d.ts +76 -3
- package/dist-cjs/protocols/acp.js +183 -8
- package/dist-cjs/protocols/ag-ui.d.ts +23 -0
- package/dist-cjs/protocols/ag-ui.js +127 -7
- 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/tracing/instrument.js +14 -3
- package/dist-cjs/types/graph.d.ts +7 -0
- package/dist-cjs/types/messages.d.ts +69 -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 +69 -2
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export const TOOL_SEARCH_NAME = 'search_tools';
|
|
2
|
+
export function searchTools(tools, query, limit = 5) {
|
|
3
|
+
return indexTools(tools)(query, limit);
|
|
4
|
+
}
|
|
5
|
+
export function toolSearchTool(tools, options = {}) {
|
|
6
|
+
const search = indexTools(tools);
|
|
7
|
+
const fallback = Math.max(1, options.limit ?? 5);
|
|
8
|
+
const most = Math.max(fallback, options.maxLimit ?? 20);
|
|
9
|
+
return {
|
|
10
|
+
name: TOOL_SEARCH_NAME,
|
|
11
|
+
description: `Finds tools for a task among ${tools.length} more tools that are not loaded yet, by their names, descriptions, and arguments. Describe what you need to do, such as "create a calendar event"; the tools found can be called right after.`,
|
|
12
|
+
parameters: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
query: { type: 'string', description: 'What the tool should do, in a few words' },
|
|
16
|
+
limit: {
|
|
17
|
+
type: 'integer',
|
|
18
|
+
minimum: 1,
|
|
19
|
+
maximum: most,
|
|
20
|
+
description: `How many tools to return. Defaults to ${fallback}`,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ['query'],
|
|
24
|
+
},
|
|
25
|
+
capabilities: [],
|
|
26
|
+
execute: async (args) => {
|
|
27
|
+
const query = String(args.query ?? '');
|
|
28
|
+
const wanted = Math.min(most, Math.max(1, Math.floor(Number(args.limit) || fallback)));
|
|
29
|
+
const hits = search(query, wanted);
|
|
30
|
+
if (hits.length === 0) {
|
|
31
|
+
return {
|
|
32
|
+
type: 'tool_output',
|
|
33
|
+
content: [{ type: 'text', text: `No tool matches "${query}". Try other words.` }],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const listed = hits.map(({ tool }) => `- ${tool.name}: ${short(tool.description)}`).join('\n');
|
|
37
|
+
const references = hits.map(({ tool }) => ({ type: 'tool_reference', name: tool.name }));
|
|
38
|
+
return {
|
|
39
|
+
type: 'tool_output',
|
|
40
|
+
content: [{ type: 'text', text: `Found ${hits.length}, which you can call now:\n${listed}` }, ...references],
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const short = (text) => (text.length > 200 ? `${text.slice(0, 197)}...` : text);
|
|
46
|
+
function words(text) {
|
|
47
|
+
return (text
|
|
48
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.match(/[a-z0-9]+/g) ?? [])
|
|
51
|
+
.filter((word) => word.length > 1 && !STOP.has(word))
|
|
52
|
+
.map((word) => (word.length > 3 ? word.replace(/(?:ies|es|s)$/, (end) => (end === 'ies' ? 'y' : '')) : word));
|
|
53
|
+
}
|
|
54
|
+
const STOP = new Set([
|
|
55
|
+
'the',
|
|
56
|
+
'an',
|
|
57
|
+
'and',
|
|
58
|
+
'or',
|
|
59
|
+
'of',
|
|
60
|
+
'to',
|
|
61
|
+
'in',
|
|
62
|
+
'on',
|
|
63
|
+
'for',
|
|
64
|
+
'it',
|
|
65
|
+
'is',
|
|
66
|
+
'as',
|
|
67
|
+
'by',
|
|
68
|
+
'with',
|
|
69
|
+
'use',
|
|
70
|
+
'this',
|
|
71
|
+
]);
|
|
72
|
+
function documentOf(tool) {
|
|
73
|
+
const properties = tool.parameters.properties ?? {};
|
|
74
|
+
const argumentText = Object.entries(properties)
|
|
75
|
+
.map(([name, schema]) => `${name} ${typeof schema?.description === 'string' ? schema.description : ''}`)
|
|
76
|
+
.join(' ');
|
|
77
|
+
const name = words(tool.name);
|
|
78
|
+
return [...name, ...name, ...words(tool.description), ...words(argumentText)];
|
|
79
|
+
}
|
|
80
|
+
function indexTools(tools) {
|
|
81
|
+
const documents = tools.map((tool) => {
|
|
82
|
+
const counts = new Map();
|
|
83
|
+
const terms = documentOf(tool);
|
|
84
|
+
for (const term of terms)
|
|
85
|
+
counts.set(term, (counts.get(term) ?? 0) + 1);
|
|
86
|
+
return { tool, counts, length: terms.length };
|
|
87
|
+
});
|
|
88
|
+
const average = documents.reduce((sum, document) => sum + document.length, 0) / Math.max(1, documents.length);
|
|
89
|
+
const frequency = new Map();
|
|
90
|
+
for (const document of documents)
|
|
91
|
+
for (const term of document.counts.keys())
|
|
92
|
+
frequency.set(term, (frequency.get(term) ?? 0) + 1);
|
|
93
|
+
const k1 = 1.2;
|
|
94
|
+
const b = 0.75;
|
|
95
|
+
return (query, limit) => {
|
|
96
|
+
const terms = [...new Set(words(query))];
|
|
97
|
+
const hits = [];
|
|
98
|
+
for (const document of documents) {
|
|
99
|
+
let score = 0;
|
|
100
|
+
for (const term of terms) {
|
|
101
|
+
const count = document.counts.get(term);
|
|
102
|
+
if (!count)
|
|
103
|
+
continue;
|
|
104
|
+
const appears = frequency.get(term) ?? 0;
|
|
105
|
+
const idf = Math.log(1 + (documents.length - appears + 0.5) / (appears + 0.5));
|
|
106
|
+
score += (idf * count * (k1 + 1)) / (count + k1 * (1 - b + (b * document.length) / average));
|
|
107
|
+
}
|
|
108
|
+
if (score > 0)
|
|
109
|
+
hits.push({ tool: document.tool, score });
|
|
110
|
+
}
|
|
111
|
+
return hits.sort((x, y) => y.score - x.score || x.tool.name.localeCompare(y.tool.name)).slice(0, limit);
|
|
112
|
+
};
|
|
113
|
+
}
|
package/dist/agent/tool.d.ts
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import type { ContentPart, Message, ToolContext, ToolDefinition, ToolOutput } from '../types/messages.js';
|
|
2
2
|
import type { ToolExecutionResult } from '../types/agent.js';
|
|
3
|
-
/**
|
|
3
|
+
/** How a tool built with `tool()` runs: once, with `execute`, or reporting progress, with `stream`. */
|
|
4
|
+
export type ToolBody<TArgs> = {
|
|
5
|
+
/** Runs the tool. In an agent, `context` says who the run is for and carries its signal and store. */
|
|
6
|
+
execute: (args: TArgs, context: ToolContext) => Promise<unknown> | unknown;
|
|
7
|
+
stream?: never;
|
|
8
|
+
} | {
|
|
9
|
+
/**
|
|
10
|
+
* Runs the tool, yielding progress while it works and returning the result. See
|
|
11
|
+
* `ToolDefinition.stream`.
|
|
12
|
+
*/
|
|
13
|
+
stream: (args: TArgs, context: ToolContext) => AsyncGenerator<unknown, unknown, undefined>;
|
|
14
|
+
execute?: never;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Defines a tool the model can call, typing its arguments. A tool given `stream` gets an `execute`
|
|
18
|
+
* too, which runs the stream and reports each yield to `context.progress`, so it works wherever a
|
|
19
|
+
* tool is executed.
|
|
20
|
+
*/
|
|
4
21
|
export declare function tool<TArgs extends Record<string, unknown> = Record<string, unknown>>(definition: {
|
|
5
22
|
name: string;
|
|
6
23
|
description: string;
|
|
7
24
|
parameters: Record<string, unknown>;
|
|
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
25
|
/** What the tool does to the world, for a permission policy; see `ToolDefinition.capabilities`. */
|
|
11
26
|
capabilities?: readonly string[] | ((args: TArgs) => readonly string[]);
|
|
12
|
-
|
|
27
|
+
/** Loads the tool only once the model finds it; see `ToolDefinition.deferred`. */
|
|
28
|
+
deferred?: boolean;
|
|
29
|
+
} & ToolBody<TArgs>): ToolDefinition;
|
|
13
30
|
/**
|
|
14
31
|
* Builds a tool's result as content: text, images, and asset references, in order. A string becomes
|
|
15
32
|
* a text part. Returned from `execute`, it is sent as the tool message itself, so an image a tool
|
package/dist/agent/tool.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
export function tool(definition) {
|
|
2
2
|
const capabilities = definition.capabilities;
|
|
3
|
+
const stream = definition.stream;
|
|
4
|
+
const execute = definition.execute;
|
|
3
5
|
return {
|
|
4
6
|
name: definition.name,
|
|
5
7
|
description: definition.description,
|
|
6
8
|
parameters: definition.parameters,
|
|
7
|
-
|
|
9
|
+
...(definition.deferred ? { deferred: true } : {}),
|
|
10
|
+
...(stream
|
|
11
|
+
? {
|
|
12
|
+
stream: (args, context) => stream(args, context ?? {}),
|
|
13
|
+
execute: async (args, context) => drain(stream(args, context ?? {}), context?.progress),
|
|
14
|
+
}
|
|
15
|
+
: {
|
|
16
|
+
execute: async (args, context) => execute(args, context ?? {}),
|
|
17
|
+
}),
|
|
8
18
|
...(capabilities === undefined
|
|
9
19
|
? {}
|
|
10
20
|
: {
|
|
@@ -31,6 +41,18 @@ export function toolMessageContent(result) {
|
|
|
31
41
|
return result.result.content;
|
|
32
42
|
return JSON.stringify(result.ok ? (result.result ?? null) : { error: result.error });
|
|
33
43
|
}
|
|
44
|
+
async function drain(generator, progress) {
|
|
45
|
+
for (;;) {
|
|
46
|
+
const step = await generator.next();
|
|
47
|
+
if (step.done)
|
|
48
|
+
return step.value;
|
|
49
|
+
try {
|
|
50
|
+
progress?.(step.value);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
34
56
|
export class ToolExecutor {
|
|
35
57
|
tools = new Map();
|
|
36
58
|
constructor(tools = []) {
|
|
@@ -56,11 +78,13 @@ export class ToolExecutor {
|
|
|
56
78
|
if (!toolDefinition) {
|
|
57
79
|
return { ok: false, error: `Tool "${name}" is not registered` };
|
|
58
80
|
}
|
|
59
|
-
if (!toolDefinition.execute) {
|
|
60
|
-
return { ok: false, error: `Tool "${name}" does not have an execute function` };
|
|
81
|
+
if (!toolDefinition.execute && !toolDefinition.stream) {
|
|
82
|
+
return { ok: false, error: `Tool "${name}" does not have an execute or stream function` };
|
|
61
83
|
}
|
|
62
84
|
try {
|
|
63
|
-
const result =
|
|
85
|
+
const result = toolDefinition.stream
|
|
86
|
+
? await drain(toolDefinition.stream(args, context), context?.progress)
|
|
87
|
+
: await toolDefinition.execute(args, context);
|
|
64
88
|
return { ok: true, result };
|
|
65
89
|
}
|
|
66
90
|
catch (err) {
|
package/dist/context/window.js
CHANGED
|
@@ -275,6 +275,10 @@ export class ContextWindowManager {
|
|
|
275
275
|
return 'transcript' in part.source ? part.source.transcript : '[audio content]';
|
|
276
276
|
if (part.type === 'video')
|
|
277
277
|
return '[video content]';
|
|
278
|
+
if (part.type === 'document')
|
|
279
|
+
return '[document content]';
|
|
280
|
+
if (part.type === 'tool_reference')
|
|
281
|
+
return `[tool ${part.name}]`;
|
|
278
282
|
if (part.type === 'asset')
|
|
279
283
|
return `[asset ${part.asset.mimeType}]`;
|
|
280
284
|
return '[content]';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers beside a deep agent: helpers it delegates to and waits for, helpers that run in the
|
|
3
|
+
* background while it goes on, and helpers it creates during a run from instructions it writes.
|
|
4
|
+
*
|
|
5
|
+
* A background helper runs on a thread of its own, named after the call that started it, and writes
|
|
6
|
+
* its checkpoints to the deep agent's checkpointer. A crash leaves the helper's finished steps behind
|
|
7
|
+
* it: collecting the task in a new process carries the helper on from its last checkpoint, and a call
|
|
8
|
+
* the agent repeats after the crash finds the same thread instead of starting the helper again.
|
|
9
|
+
*/
|
|
10
|
+
import { type CreateAgentOptions } from '../agent/create-agent.js';
|
|
11
|
+
import type { PermissionPolicyLike } from '../agent/permissions.js';
|
|
12
|
+
import type { GraphCheckpointer } from '../types/graph.js';
|
|
13
|
+
import type { ToolDefinition } from '../types/messages.js';
|
|
14
|
+
/** Limits on the helpers a deep agent creates during a run, from instructions it writes. */
|
|
15
|
+
export interface DeepHelperLimits {
|
|
16
|
+
/**
|
|
17
|
+
* The tools a created helper may use, by name, from the agent's own. Defaults to all of them. A tool
|
|
18
|
+
* that needs approval is never a helper's.
|
|
19
|
+
*/
|
|
20
|
+
tools?: readonly string[];
|
|
21
|
+
/** The model a created helper runs on. Defaults to the agent's. */
|
|
22
|
+
model?: string;
|
|
23
|
+
/** Model calls a created helper may make. Defaults to 10. */
|
|
24
|
+
maxIterations?: number;
|
|
25
|
+
/** How many helpers the agent may create in one thread. Defaults to 5. */
|
|
26
|
+
max?: number;
|
|
27
|
+
}
|
|
28
|
+
/** What the deep agent hands the helper tools. */
|
|
29
|
+
export interface HelperSetup {
|
|
30
|
+
/** The helpers the application declared. */
|
|
31
|
+
declared: ReadonlyArray<{
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
instructions: string;
|
|
35
|
+
tools?: readonly string[];
|
|
36
|
+
model?: string;
|
|
37
|
+
maxIterations?: number;
|
|
38
|
+
}>;
|
|
39
|
+
/** Limits on created helpers, when the agent may create them. */
|
|
40
|
+
create?: DeepHelperLimits;
|
|
41
|
+
/** The tools a helper may be given: the agent's own, less any that need approval. */
|
|
42
|
+
tools: readonly ToolDefinition[];
|
|
43
|
+
/** `createAgent()` options every helper shares. */
|
|
44
|
+
agentOptions: Omit<CreateAgentOptions, 'interruptOn' | 'tools' | 'systemPrompt' | 'permissions' | 'checkpointer'>;
|
|
45
|
+
/** The policy every helper runs under. */
|
|
46
|
+
permissions?: PermissionPolicyLike;
|
|
47
|
+
/** Where background helpers keep their checkpoints. */
|
|
48
|
+
checkpointer?: GraphCheckpointer;
|
|
49
|
+
/** The deep agent's name, for the helpers' names. */
|
|
50
|
+
name: string;
|
|
51
|
+
/** Appended to every helper's instructions. */
|
|
52
|
+
note: string;
|
|
53
|
+
/** How many helpers may run in the background in one thread at once. */
|
|
54
|
+
maxBackground: number;
|
|
55
|
+
}
|
|
56
|
+
/** `delegate` and `collect_helper`, sharing the helpers they run. */
|
|
57
|
+
export declare function helperTools(setup: HelperSetup): ToolDefinition[];
|
|
@@ -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`. */
|