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.
Files changed (101) hide show
  1. package/API_STABILITY.md +52 -0
  2. package/CHANGELOG.md +119 -1
  3. package/README.md +8 -5
  4. package/dist/agent/create-agent.js +16 -3
  5. package/dist/agent/glob.d.ts +24 -0
  6. package/dist/agent/glob.js +72 -0
  7. package/dist/agent/index.d.ts +1 -0
  8. package/dist/agent/index.js +1 -0
  9. package/dist/agent/interpreter.d.ts +125 -0
  10. package/dist/agent/interpreter.js +270 -0
  11. package/dist/agent/sandbox-container.d.ts +48 -0
  12. package/dist/agent/sandbox-container.js +291 -0
  13. package/dist/agent/sandbox.d.ts +50 -1
  14. package/dist/agent/sandbox.js +155 -2
  15. package/dist/agent/tool-search.d.ts +39 -0
  16. package/dist/agent/tool-search.js +113 -0
  17. package/dist/agent/tool.d.ts +2 -0
  18. package/dist/agent/tool.js +1 -0
  19. package/dist/context/window.js +4 -0
  20. package/dist/deep-agent/helpers.d.ts +57 -0
  21. package/dist/deep-agent/helpers.js +235 -0
  22. package/dist/deep-agent/index.d.ts +20 -1
  23. package/dist/deep-agent/index.js +69 -51
  24. package/dist/evaluate/generate.d.ts +171 -0
  25. package/dist/evaluate/generate.js +266 -0
  26. package/dist/evaluate/index.d.ts +1 -1
  27. package/dist/evaluate/index.js +1 -1
  28. package/dist/evaluate/online.d.ts +114 -0
  29. package/dist/evaluate/online.js +261 -0
  30. package/dist/evaluate/run.d.ts +28 -0
  31. package/dist/evaluate/run.js +56 -5
  32. package/dist/graph/graph.d.ts +7 -1
  33. package/dist/graph/graph.js +12 -1
  34. package/dist/operations/runner.js +1 -1
  35. package/dist/providers/anthropic.d.ts +6 -0
  36. package/dist/providers/anthropic.js +36 -9
  37. package/dist/providers/content.d.ts +33 -1
  38. package/dist/providers/content.js +62 -0
  39. package/dist/providers/google.js +14 -5
  40. package/dist/providers/openai.d.ts +8 -4
  41. package/dist/providers/openai.js +75 -34
  42. package/dist/runtime/index.d.ts +1 -1
  43. package/dist/security/index.d.ts +1 -1
  44. package/dist/security/index.js +1 -1
  45. package/dist/security/injection-detector.d.ts +18 -0
  46. package/dist/security/injection-detector.js +39 -4
  47. package/dist/security/schema-validator.js +11 -2
  48. package/dist/types/messages.d.ts +57 -3
  49. package/dist/utils/tokenizer.js +4 -0
  50. package/dist/version.d.ts +1 -1
  51. package/dist/version.js +1 -1
  52. package/dist-cjs/agent/create-agent.js +15 -2
  53. package/dist-cjs/agent/glob.d.ts +24 -0
  54. package/dist-cjs/agent/glob.js +77 -0
  55. package/dist-cjs/agent/index.d.ts +1 -0
  56. package/dist-cjs/agent/index.js +5 -1
  57. package/dist-cjs/agent/interpreter.d.ts +125 -0
  58. package/dist-cjs/agent/interpreter.js +308 -0
  59. package/dist-cjs/agent/sandbox-container.d.ts +48 -0
  60. package/dist-cjs/agent/sandbox-container.js +327 -0
  61. package/dist-cjs/agent/sandbox.d.ts +50 -1
  62. package/dist-cjs/agent/sandbox.js +155 -2
  63. package/dist-cjs/agent/tool-search.d.ts +39 -0
  64. package/dist-cjs/agent/tool-search.js +118 -0
  65. package/dist-cjs/agent/tool.d.ts +2 -0
  66. package/dist-cjs/agent/tool.js +1 -0
  67. package/dist-cjs/context/window.js +4 -0
  68. package/dist-cjs/deep-agent/helpers.d.ts +57 -0
  69. package/dist-cjs/deep-agent/helpers.js +238 -0
  70. package/dist-cjs/deep-agent/index.d.ts +20 -1
  71. package/dist-cjs/deep-agent/index.js +68 -50
  72. package/dist-cjs/evaluate/generate.d.ts +171 -0
  73. package/dist-cjs/evaluate/generate.js +272 -0
  74. package/dist-cjs/evaluate/index.d.ts +1 -1
  75. package/dist-cjs/evaluate/index.js +2 -1
  76. package/dist-cjs/evaluate/online.d.ts +114 -0
  77. package/dist-cjs/evaluate/online.js +264 -0
  78. package/dist-cjs/evaluate/run.d.ts +28 -0
  79. package/dist-cjs/evaluate/run.js +58 -5
  80. package/dist-cjs/graph/graph.d.ts +7 -1
  81. package/dist-cjs/graph/graph.js +13 -1
  82. package/dist-cjs/operations/runner.js +1 -1
  83. package/dist-cjs/providers/anthropic.d.ts +6 -0
  84. package/dist-cjs/providers/anthropic.js +35 -8
  85. package/dist-cjs/providers/content.d.ts +33 -1
  86. package/dist-cjs/providers/content.js +69 -0
  87. package/dist-cjs/providers/google.js +13 -4
  88. package/dist-cjs/providers/openai.d.ts +8 -4
  89. package/dist-cjs/providers/openai.js +74 -33
  90. package/dist-cjs/runtime/index.d.ts +1 -1
  91. package/dist-cjs/security/index.d.ts +1 -1
  92. package/dist-cjs/security/index.js +2 -1
  93. package/dist-cjs/security/injection-detector.d.ts +18 -0
  94. package/dist-cjs/security/injection-detector.js +40 -4
  95. package/dist-cjs/security/schema-validator.js +11 -2
  96. package/dist-cjs/types/messages.d.ts +57 -3
  97. package/dist-cjs/utils/tokenizer.js +4 -0
  98. package/dist-cjs/version.d.ts +1 -1
  99. package/dist-cjs/version.js +1 -1
  100. package/llms.txt +1 -1
  101. package/package.json +53 -1
@@ -33,6 +33,33 @@ export interface SandboxExecResult {
33
33
  /** How long it ran, in milliseconds. */
34
34
  durationMs: number;
35
35
  }
36
+ /** Options for `Sandbox.glob()`. */
37
+ export interface SandboxGlobOptions {
38
+ /** The directory to search, inside the sandbox. Defaults to its root. */
39
+ cwd?: string;
40
+ /** The most paths returned. Defaults to 1,000. */
41
+ limit?: number;
42
+ }
43
+ /** Options for `Sandbox.grep()`. */
44
+ export interface SandboxGrepOptions {
45
+ /** The directory to search, inside the sandbox. Defaults to its root. */
46
+ cwd?: string;
47
+ /** Searches only the files whose paths, relative to `cwd`, match this glob. */
48
+ glob?: string;
49
+ /** Matches without regard to case. */
50
+ ignoreCase?: boolean;
51
+ /** The most matching lines returned. Defaults to 200. */
52
+ limit?: number;
53
+ }
54
+ /** One line `Sandbox.grep()` matched. */
55
+ export interface SandboxMatch {
56
+ /** The file, relative to the sandbox's root. */
57
+ file: string;
58
+ /** The line's number, counting from 1. */
59
+ line: number;
60
+ /** The line, cut to 300 characters. */
61
+ text: string;
62
+ }
36
63
  /** What a sandbox claims to isolate. The conformance suite checks the claims it can observe. */
37
64
  export interface SandboxIsolation {
38
65
  /** Commands cannot read or write the host's files. */
@@ -58,6 +85,19 @@ export interface Sandbox {
58
85
  listFiles?(directory?: string): Promise<string[]>;
59
86
  /** Removes a file or directory. */
60
87
  removeFile?(file: string): Promise<void>;
88
+ /**
89
+ * The files under a directory whose paths match a glob, such as `src/**\/*.ts`, sorted. The glob
90
+ * matches paths relative to `cwd`; the paths returned are relative to the root, as `readFile`
91
+ * takes them. Returns at most `limit`.
92
+ */
93
+ glob?(pattern: string, options?: SandboxGlobOptions): Promise<string[]>;
94
+ /**
95
+ * The lines matching a regular expression in the files under a directory, at most `limit`. Large
96
+ * files and binary files are skipped.
97
+ */
98
+ grep?(pattern: string, options?: SandboxGrepOptions): Promise<SandboxMatch[]>;
99
+ /** Reads a file's bytes, for an image, audio, or a document. */
100
+ readBytes?(file: string): Promise<Uint8Array>;
61
101
  /** Releases the sandbox. */
62
102
  dispose?(): Promise<void>;
63
103
  }
@@ -88,14 +128,23 @@ export interface ProcessSandboxOptions {
88
128
  * Its file methods refuse any path outside `root`, and commands get only `PATH` and the variables
89
129
  * given, never the host's environment. But a command it runs can read and write anything the user
90
130
  * can, and reach any host: `isolation` says so. Use a container or a VM in production.
131
+ *
132
+ * Its search skips `.git` and `node_modules`, walks at most 20,000 entries, and follows no links;
133
+ * `grep()` skips files over 1 MB and binary files, reads at most 64 MB, and searches the first
134
+ * 4,000 characters of each line.
91
135
  */
92
136
  export declare function processSandbox(options: ProcessSandboxOptions): Sandbox;
93
137
  /** Options for `sandboxTools()`. */
94
138
  export interface SandboxToolsOptions {
95
139
  /** Offer `run_command`. Defaults to true. */
96
140
  shell?: boolean;
97
- /** Offer `read_file`, `write_file`, and `list_files`. Defaults to true. */
141
+ /**
142
+ * Offer the file tools: `read_file`, `write_file`, and, where the sandbox can, `list_files`,
143
+ * `glob_files`, `grep_files`, and `read_media`. Defaults to true.
144
+ */
98
145
  files?: boolean;
146
+ /** The largest file `read_media` sends to the model, in bytes. Defaults to 10 MB. */
147
+ maxMediaBytes?: number;
99
148
  /**
100
149
  * The path a permission policy sees the sandbox's files under. Defaults to `/workspace`, so a
101
150
  * policy granting `/workspace/**` grants the sandbox.
@@ -1,3 +1,5 @@
1
+ import { encodeBase64 } from '../utils/base64.js';
2
+ import { GREP_FILE_BYTES, GREP_TOTAL_BYTES, globMatcher, MEDIA_TYPES, matchingLines, SKIPPED_DIRECTORIES, WALK_LIMIT, } from './glob.js';
1
3
  export class SandboxPathError extends Error {
2
4
  path;
3
5
  code = 'SANDBOX_PATH';
@@ -9,7 +11,7 @@ export class SandboxPathError extends Error {
9
11
  }
10
12
  export function processSandbox(options) {
11
13
  const ready = (async () => {
12
- const [{ spawn }, fs, { dirname, isAbsolute, relative, resolve }] = await Promise.all([
14
+ const [{ spawn }, fs, { dirname, isAbsolute, join, relative, resolve }] = await Promise.all([
13
15
  import('node:child_process'),
14
16
  import('node:fs/promises'),
15
17
  import('node:path'),
@@ -23,10 +25,36 @@ export function processSandbox(options) {
23
25
  throw new SandboxPathError(file);
24
26
  return resolved;
25
27
  };
26
- return { spawn, fs, dirname, root, inside };
28
+ return { spawn, fs, dirname, join, relative, root, inside };
27
29
  })();
28
30
  ready.catch(() => undefined);
29
31
  const maxOutput = options.maxOutputBytes ?? 1_048_576;
32
+ const walk = async (directory) => {
33
+ const { fs, join, relative, root, inside } = await ready;
34
+ const start = inside(directory);
35
+ const slash = (path) => path.replace(/\\/g, '/');
36
+ const files = [];
37
+ const queue = [start];
38
+ let walked = 0;
39
+ while (queue.length > 0 && walked < WALK_LIMIT) {
40
+ const current = queue.shift();
41
+ const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
42
+ for (const entry of entries) {
43
+ walked += 1;
44
+ if (walked > WALK_LIMIT)
45
+ break;
46
+ const full = join(current, entry.name);
47
+ if (entry.isDirectory()) {
48
+ if (!SKIPPED_DIRECTORIES.has(entry.name))
49
+ queue.push(full);
50
+ }
51
+ else if (entry.isFile()) {
52
+ files.push({ full, fromRoot: slash(relative(root, full)), fromStart: slash(relative(start, full)) });
53
+ }
54
+ }
55
+ }
56
+ return files.sort((a, b) => (a.fromRoot < b.fromRoot ? -1 : a.fromRoot > b.fromRoot ? 1 : 0));
57
+ };
30
58
  return {
31
59
  name: 'process (development only)',
32
60
  isolation: { filesystem: false, network: false, processes: false },
@@ -108,6 +136,40 @@ export function processSandbox(options) {
108
136
  throw new SandboxPathError(file);
109
137
  await fs.rm(target, { recursive: true, force: true });
110
138
  },
139
+ async glob(pattern, globOptions = {}) {
140
+ const matches = globMatcher(pattern);
141
+ return (await walk(globOptions.cwd ?? '.'))
142
+ .filter((file) => matches(file.fromStart))
143
+ .slice(0, globOptions.limit ?? 1_000)
144
+ .map((file) => file.fromRoot);
145
+ },
146
+ async grep(pattern, grepOptions = {}) {
147
+ const { fs } = await ready;
148
+ const expression = new RegExp(pattern, grepOptions.ignoreCase ? 'i' : '');
149
+ const only = grepOptions.glob ? globMatcher(grepOptions.glob) : undefined;
150
+ const limit = grepOptions.limit ?? 200;
151
+ const found = [];
152
+ let scanned = 0;
153
+ for (const file of await walk(grepOptions.cwd ?? '.')) {
154
+ if (only && !only(file.fromStart))
155
+ continue;
156
+ const size = (await fs.stat(file.full).catch(() => undefined))?.size;
157
+ if (size === undefined || size > GREP_FILE_BYTES)
158
+ continue;
159
+ scanned += size;
160
+ if (scanned > GREP_TOTAL_BYTES)
161
+ break;
162
+ const lines = matchingLines(await fs.readFile(file.full), expression, limit - found.length);
163
+ found.push(...lines.map((match) => ({ file: file.fromRoot, ...match })));
164
+ if (found.length >= limit)
165
+ break;
166
+ }
167
+ return found;
168
+ },
169
+ async readBytes(file) {
170
+ const { fs, inside } = await ready;
171
+ return fs.readFile(inside(file));
172
+ },
111
173
  };
112
174
  }
113
175
  function killTree(spawn, pid) {
@@ -182,9 +244,100 @@ export function sandboxTools(sandbox, options = {}) {
182
244
  execute: async (args) => list(local(args.path)),
183
245
  });
184
246
  }
247
+ if (sandbox.glob) {
248
+ const glob = sandbox.glob.bind(sandbox);
249
+ tools.push({
250
+ name: 'glob_files',
251
+ description: `Finds files by a glob, such as **/*.ts or src/*.{js,json}, and lists their paths relative to ${mount}, at most ${GLOB_LIMIT}.`,
252
+ parameters: {
253
+ type: 'object',
254
+ properties: {
255
+ pattern: { type: 'string', description: 'The glob the paths must match' },
256
+ path: { type: 'string', description: 'The directory to search. Defaults to the whole workspace' },
257
+ },
258
+ required: ['pattern'],
259
+ },
260
+ capabilities: (args) => [`filesystem:read:${mounted(args.path)}`],
261
+ execute: async (args) => {
262
+ const files = await glob(String(args.pattern ?? '**'), { cwd: local(args.path), limit: GLOB_LIMIT + 1 });
263
+ if (files.length === 0)
264
+ return 'No files match.';
265
+ const shown = files.slice(0, GLOB_LIMIT).join('\n');
266
+ return files.length > GLOB_LIMIT ? `${shown}\n[more files match: narrow the glob]` : shown;
267
+ },
268
+ });
269
+ }
270
+ if (sandbox.grep) {
271
+ const grep = sandbox.grep.bind(sandbox);
272
+ tools.push({
273
+ name: 'grep_files',
274
+ description: `Searches files for lines matching a regular expression and lists each as path:line: text, at most ${GREP_LIMIT}. Large and binary files are skipped.`,
275
+ parameters: {
276
+ type: 'object',
277
+ properties: {
278
+ pattern: { type: 'string', description: 'The regular expression to find' },
279
+ path: { type: 'string', description: 'The directory to search. Defaults to the whole workspace' },
280
+ glob: { type: 'string', description: 'Searches only files matching this glob, such as **/*.ts' },
281
+ ignore_case: { type: 'boolean', description: 'Matches without regard to case' },
282
+ },
283
+ required: ['pattern'],
284
+ },
285
+ capabilities: (args) => [`filesystem:read:${mounted(args.path)}`],
286
+ execute: async (args) => {
287
+ const found = await grep(String(args.pattern ?? ''), {
288
+ cwd: local(args.path),
289
+ ...(typeof args.glob === 'string' && args.glob ? { glob: args.glob } : {}),
290
+ ignoreCase: args.ignore_case === true,
291
+ limit: GREP_LIMIT + 1,
292
+ });
293
+ if (found.length === 0)
294
+ return 'No lines match.';
295
+ const shown = found
296
+ .slice(0, GREP_LIMIT)
297
+ .map((match) => `${match.file}:${match.line}: ${match.text}`)
298
+ .join('\n');
299
+ return found.length > GREP_LIMIT ? `${shown}\n[more lines match: narrow the search]` : shown;
300
+ },
301
+ });
302
+ }
303
+ if (sandbox.readBytes) {
304
+ const readBytes = sandbox.readBytes.bind(sandbox);
305
+ const maxBytes = options.maxMediaBytes ?? 10 * 1_048_576;
306
+ tools.push({
307
+ name: 'read_media',
308
+ description: `Shows you an image (PNG, JPEG, GIF, WebP), audio (WAV, MP3, FLAC, OGG, AAC), or a PDF in the sandbox as content you see or hear, not as text. Paths are relative to ${mount}.`,
309
+ parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
310
+ capabilities: (args) => [`filesystem:read:${mounted(args.path)}`],
311
+ execute: async (args) => {
312
+ const file = local(args.path);
313
+ const mimeType = MEDIA_TYPES[file.split('.').pop()?.toLowerCase() ?? ''];
314
+ if (!mimeType)
315
+ throw new Error(`${file} is not an image, audio, or PDF file; read text with read_file`);
316
+ const bytes = await readBytes(file);
317
+ if (bytes.byteLength > maxBytes) {
318
+ throw new Error(`${file} is ${bytes.byteLength} bytes, over the ${maxBytes}-byte limit for media`);
319
+ }
320
+ return mediaOutput(file, mimeType, bytes);
321
+ },
322
+ });
323
+ }
185
324
  }
186
325
  return tools;
187
326
  }
327
+ const GLOB_LIMIT = 500;
328
+ const GREP_LIMIT = 200;
329
+ function mediaOutput(file, mimeType, bytes) {
330
+ const data = encodeBase64(bytes);
331
+ const part = mimeType.startsWith('image/')
332
+ ? { type: 'image', source: { base64: data, mimeType } }
333
+ : mimeType.startsWith('audio/')
334
+ ? { type: 'audio', source: { base64: data, format: mimeType.slice('audio/'.length) } }
335
+ : { type: 'document', source: { base64: data, mimeType }, filename: file.split('/').pop() ?? file };
336
+ return {
337
+ type: 'tool_output',
338
+ content: [{ type: 'text', text: `${file} (${mimeType}, ${bytes.byteLength} bytes)` }, part],
339
+ };
340
+ }
188
341
  export async function runSandboxConformance(create, options = {}) {
189
342
  const node = options.node ?? 'node';
190
343
  const js = (code) => `${node} -e "${code}"`;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Tool search: an agent with hundreds of tools marks most of them `deferred`, and the model finds the
3
+ * few a task needs with `search_tools` instead of reading every definition on every call.
4
+ *
5
+ * The search ranks tools by BM25 over their names, descriptions, and argument names and descriptions,
6
+ * and answers with the tools it found as `tool_reference` parts. A provider with tool references,
7
+ * such as Anthropic, expands each reference into the tool's definition where it stands, so the tool
8
+ * list at the front of the prompt never changes; every other provider is sent a found tool from then
9
+ * on. Either way the model calls a found tool on its next turn.
10
+ */
11
+ import type { ToolDefinition } from '../types/messages.js';
12
+ /** The name of the search tool an agent with deferred tools offers. */
13
+ export declare const TOOL_SEARCH_NAME = "search_tools";
14
+ /** Options for `toolSearchTool()`. */
15
+ export interface ToolSearchOptions {
16
+ /** How many tools a search returns unless the model asks for another number. Defaults to 5. */
17
+ limit?: number;
18
+ /** The most a model may ask for. Defaults to 20. */
19
+ maxLimit?: number;
20
+ }
21
+ /** One tool a search found, with its score. */
22
+ export interface ToolSearchHit {
23
+ /** The tool. */
24
+ tool: ToolDefinition;
25
+ /** Its BM25 score for the query; higher is closer. */
26
+ score: number;
27
+ }
28
+ /**
29
+ * Ranks tools against a query by BM25 over their names, descriptions, and arguments. A tool's name
30
+ * counts twice, so a query that names a tool finds it first. Returns the tools that match at all,
31
+ * best first, at most `limit`.
32
+ */
33
+ export declare function searchTools(tools: readonly ToolDefinition[], query: string, limit?: number): ToolSearchHit[];
34
+ /**
35
+ * The tool an agent offers when it has deferred tools: `search_tools`, which finds deferred tools by
36
+ * what a task needs and answers with them as tool references. `createAgent()` adds it on its own; an
37
+ * application's own loop can offer it the same way.
38
+ */
39
+ export declare function toolSearchTool(tools: readonly ToolDefinition[], options?: ToolSearchOptions): ToolDefinition;
@@ -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
+ }
@@ -24,6 +24,8 @@ export declare function tool<TArgs extends Record<string, unknown> = Record<stri
24
24
  parameters: Record<string, unknown>;
25
25
  /** What the tool does to the world, for a permission policy; see `ToolDefinition.capabilities`. */
26
26
  capabilities?: readonly string[] | ((args: TArgs) => readonly string[]);
27
+ /** Loads the tool only once the model finds it; see `ToolDefinition.deferred`. */
28
+ deferred?: boolean;
27
29
  } & ToolBody<TArgs>): ToolDefinition;
28
30
  /**
29
31
  * Builds a tool's result as content: text, images, and asset references, in order. A string becomes
@@ -6,6 +6,7 @@ export function tool(definition) {
6
6
  name: definition.name,
7
7
  description: definition.description,
8
8
  parameters: definition.parameters,
9
+ ...(definition.deferred ? { deferred: true } : {}),
9
10
  ...(stream
10
11
  ? {
11
12
  stream: (args, context) => stream(args, context ?? {}),
@@ -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[];