@nyte-ai/plugin 0.0.25-beta.1 → 0.0.25-nightly.20261025.2

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 (34) hide show
  1. package/THIRD-PARTY-NOTICES.md +13 -5
  2. package/dist/chunks/bash-description-0ah30s12.js +33 -0
  3. package/dist/chunks/bash-description-4cdbmar0.js +124 -0
  4. package/dist/chunks/bash-description-4h0hnsv4.js +68 -0
  5. package/dist/chunks/bash-description-5dpf5hpt.js +724 -0
  6. package/dist/chunks/bash-description-9gw9ets8.js +162 -0
  7. package/dist/chunks/bash-description-b78pre8c.js +147 -0
  8. package/dist/chunks/{bash-description-eqp7ap2w.js → bash-description-vg0xecwm.js} +16 -5
  9. package/dist/src/acp/connection.js +1 -1
  10. package/dist/src/acp/index.js +1 -1
  11. package/dist/src/claude-code-sdk/index.js +1 -0
  12. package/dist/src/codemode.js +148 -41
  13. package/dist/src/core.d.ts +1 -1
  14. package/dist/src/rename.d.ts +7 -22
  15. package/dist/src/rename.js +115 -48
  16. package/dist/src/subagents.d.ts +30 -0
  17. package/dist/src/subagents.js +323 -0
  18. package/dist/src/tool-search.d.ts +49 -0
  19. package/dist/src/tool-search.js +14 -0
  20. package/dist/src/web/body.d.ts +3 -0
  21. package/dist/src/web/body.js +6 -0
  22. package/dist/src/web/fetch.d.ts +16 -0
  23. package/dist/src/web/fetch.js +8 -0
  24. package/dist/src/web/html-markdown.d.ts +9 -0
  25. package/dist/src/web/html-markdown.js +10 -0
  26. package/dist/src/web/index.d.ts +24 -0
  27. package/dist/src/web/index.js +93 -0
  28. package/dist/src/web/providers.d.ts +60 -0
  29. package/dist/src/web/providers.js +11 -0
  30. package/dist/src/web/search.d.ts +21 -0
  31. package/dist/src/web/search.js +6 -0
  32. package/package.json +16 -7
  33. package/dist/src/web-search.d.ts +0 -78
  34. package/dist/src/web-search.js +0 -219
@@ -0,0 +1,162 @@
1
+ import {
2
+ readCapped2
3
+ } from "./bash-description-0ah30s12.js";
4
+
5
+ // src/web/providers.ts
6
+ import { Type } from "typebox";
7
+ import { Value } from "typebox/value";
8
+ var PROVIDERS2 = [
9
+ { id: "exa", name: "Exa" },
10
+ { id: "tavily", name: "Tavily" },
11
+ { id: "firecrawl", name: "Firecrawl" },
12
+ { id: "parallel", name: "Parallel" }
13
+ ];
14
+ var MAX_RESPONSE_BYTES = 256 * 1024;
15
+ var textBlocks = Type.Object({
16
+ content: Type.Array(Type.Object({ type: Type.String(), text: Type.Optional(Type.String()) }))
17
+ });
18
+ var tavilyResponse = Type.Object({
19
+ results: Type.Array(Type.Object({ url: Type.String(), title: Type.String(), content: Type.String() }))
20
+ });
21
+ var firecrawlResponse = Type.Object({
22
+ data: Type.Object({
23
+ web: Type.Array(Type.Object({
24
+ url: Type.String(),
25
+ title: Type.Optional(Type.Union([Type.String(), Type.Null()])),
26
+ description: Type.Optional(Type.Union([Type.String(), Type.Null()]))
27
+ }))
28
+ })
29
+ });
30
+ var parallelResponse = Type.Object({
31
+ structuredContent: Type.Object({
32
+ results: Type.Array(Type.Object({
33
+ url: Type.String(),
34
+ title: Type.Optional(Type.Union([Type.String(), Type.Null()])),
35
+ publish_date: Type.Optional(Type.Union([Type.String(), Type.Null()])),
36
+ excerpts: Type.Array(Type.String())
37
+ }))
38
+ })
39
+ });
40
+ var adapters2 = {
41
+ async tavily({ query, key, fetch, signal }) {
42
+ const response = await fetch("https://api.tavily.com/search", {
43
+ method: "POST",
44
+ headers: {
45
+ "Content-Type": "application/json",
46
+ ...key === undefined ? { "X-Tavily-Access-Mode": "keyless" } : { Authorization: `Bearer ${key}` }
47
+ },
48
+ body: JSON.stringify({ query, search_depth: "basic", max_results: 8 }),
49
+ signal
50
+ });
51
+ const body = parse(await answered(response, "tavily"));
52
+ return Value.Check(tavilyResponse, body) ? body.results.map((hit) => ({ url: hit.url, title: hit.title, content: hit.content })) : [];
53
+ },
54
+ async exa({ query, key, fetch, signal }) {
55
+ const endpoint = new URL("https://mcp.exa.ai/mcp");
56
+ if (key !== undefined)
57
+ endpoint.searchParams.set("exaApiKey", key);
58
+ const result = await callTool(fetch, endpoint, "exa", undefined, "web_search_exa", {
59
+ query,
60
+ numResults: 8
61
+ }, signal);
62
+ const text = Value.Check(textBlocks, result) ? result.content.find((block) => block.text !== undefined)?.text ?? "" : "";
63
+ return text.split(/\n\n---\n\n/).flatMap((block) => {
64
+ const url = field(block, "URL");
65
+ const published = field(block, "Published");
66
+ return url === undefined ? [] : [
67
+ {
68
+ url,
69
+ title: field(block, "Title"),
70
+ content: block.match(/^(?:Highlights|Text):\s*\n?([\s\S]*)$/m)?.[1]?.trim(),
71
+ published: published === undefined ? undefined : Date.parse(published)
72
+ }
73
+ ];
74
+ });
75
+ },
76
+ async firecrawl({ query, key, fetch, signal }) {
77
+ const result = await callTool(fetch, new URL("https://mcp.firecrawl.dev/v2/mcp"), "firecrawl", key, "firecrawl_search", { query, limit: 8 }, signal);
78
+ const text = Value.Check(textBlocks, result) ? result.content.find((block) => block.text !== undefined)?.text ?? "" : "";
79
+ const body = parse(text);
80
+ return Value.Check(firecrawlResponse, body) ? body.data.web.map((hit) => ({
81
+ url: hit.url,
82
+ title: hit.title ?? undefined,
83
+ content: hit.description ?? undefined
84
+ })) : [];
85
+ },
86
+ async parallel({ query, key, fetch, signal }) {
87
+ const result = await callTool(fetch, new URL("https://search.parallel.ai/mcp"), "parallel", key, "web_search", { objective: query, search_queries: [query] }, signal);
88
+ return Value.Check(parallelResponse, result) ? result.structuredContent.results.map((hit) => ({
89
+ url: hit.url,
90
+ title: hit.title ?? undefined,
91
+ content: hit.excerpts.length === 0 ? undefined : hit.excerpts.join(`
92
+
93
+ `),
94
+ published: hit.publish_date === undefined || hit.publish_date === null ? undefined : Date.parse(hit.publish_date)
95
+ })) : [];
96
+ }
97
+ };
98
+ function shippedProviders2(fetch, key) {
99
+ return PROVIDERS2.map((provider) => ({
100
+ id: provider.id,
101
+ name: provider.name,
102
+ search: async (input) => adapters2[provider.id]({
103
+ query: input.query,
104
+ key: await key(provider.id),
105
+ fetch,
106
+ signal: input.signal
107
+ })
108
+ }));
109
+ }
110
+ async function callTool(fetch, url, provider, key, tool, args, signal) {
111
+ const response = await fetch(url, {
112
+ method: "POST",
113
+ headers: {
114
+ Accept: "application/json, text/event-stream",
115
+ "Content-Type": "application/json",
116
+ ...key === undefined ? {} : { Authorization: `Bearer ${key}` }
117
+ },
118
+ body: JSON.stringify({
119
+ jsonrpc: "2.0",
120
+ id: 1,
121
+ method: "tools/call",
122
+ params: { name: tool, arguments: args }
123
+ }),
124
+ signal
125
+ });
126
+ const body = await answered(response, provider);
127
+ const frames = [
128
+ body,
129
+ ...body.split(`
130
+ `).flatMap((line) => line.startsWith("data: ") ? [line.slice(6)] : [])
131
+ ];
132
+ for (const frame of frames) {
133
+ const message = parse(frame);
134
+ if (typeof message === "object" && message !== null && "result" in message)
135
+ return message.result;
136
+ }
137
+ return;
138
+ }
139
+ async function answered(response, provider) {
140
+ const body = new TextDecoder().decode(await readCapped2(response, MAX_RESPONSE_BYTES));
141
+ const name = PROVIDERS2.find((known) => known.id === provider)?.name ?? provider;
142
+ if (response.status === 401)
143
+ throw new Error(`${name} refused the search (HTTP 401). Set a key with /websearch-key ${provider} <key>.`);
144
+ if (response.status === 429)
145
+ throw new Error(`${name} is rate limiting searches (HTTP 429). Try again later, or set a key with /websearch-key ${provider} <key>.`);
146
+ if (!response.ok)
147
+ throw new Error(`HTTP ${response.status}: ${body.trim().slice(0, 200) || response.statusText}`);
148
+ return body;
149
+ }
150
+ function parse(text) {
151
+ try {
152
+ return JSON.parse(text);
153
+ } catch {
154
+ return;
155
+ }
156
+ }
157
+ function field(block, name) {
158
+ const value = block.match(new RegExp(`^${name}:\\s*(.+)$`, "m"))?.[1]?.trim();
159
+ return value === undefined || value === "" || value === "N/A" ? undefined : value;
160
+ }
161
+
162
+ export { PROVIDERS2, adapters2, shippedProviders2 };
@@ -0,0 +1,147 @@
1
+ // src/tool-search.ts
2
+ import { isJsonObject, standard } from "@nyte-ai/schema";
3
+ import { Type } from "typebox";
4
+ var DEFAULT_SEARCH_LIMIT2 = 8;
5
+ var STOP_WORDS = new Set([
6
+ "a",
7
+ "an",
8
+ "and",
9
+ "are",
10
+ "as",
11
+ "at",
12
+ "be",
13
+ "by",
14
+ "for",
15
+ "from",
16
+ "in",
17
+ "is",
18
+ "it",
19
+ "of",
20
+ "on",
21
+ "or",
22
+ "that",
23
+ "the",
24
+ "this",
25
+ "to",
26
+ "with"
27
+ ]);
28
+ var K1 = 1.2;
29
+ var B = 0.75;
30
+ var DESCRIPTION = `Searches over deferred tool metadata with BM25 and exposes matching tools for the next model call.
31
+
32
+ Some of the tools, such as tools of MCP servers, may not have been provided to you upfront, and you should use this tool (\`tool_search\`) to search for the required tools. For MCP tool discovery, always use \`tool_search\`.`;
33
+ var parameters = standard(Type.Object({
34
+ query: Type.String({ description: "Search query for deferred tools." }),
35
+ limit: Type.Optional(Type.Integer({
36
+ minimum: 1,
37
+ description: `Maximum number of tools to return. Defaults to ${DEFAULT_SEARCH_LIMIT2}.`
38
+ }))
39
+ }));
40
+ var output = standard(Type.Object({ loaded: Type.Array(Type.String()) }));
41
+ var tool_search_default = {
42
+ id: "tool-search",
43
+ activate(api) {
44
+ const namespaces = () => new Map(api.contribute.tools.list().flatMap((definition) => definition.namespace === undefined ? [] : [[definition.name, definition.namespace]]));
45
+ const search = {
46
+ name: "tool_search",
47
+ description: DESCRIPTION,
48
+ parameters,
49
+ output,
50
+ exposure: "model-only",
51
+ class: () => ({ kind: "custom", label: "Searching tools" }),
52
+ effects: "none",
53
+ approval: "never",
54
+ replay: "safe",
55
+ loads: (result) => result.loaded,
56
+ async execute(args, invocation) {
57
+ if (args.query.trim() === "")
58
+ throw new Error("query must not be empty");
59
+ const loaded = new Set(await invocation.tools.loaded());
60
+ const found = namespaces();
61
+ const candidates = invocation.tools.list().filter((info) => (info.exposure === "code" || info.exposure === "deferred") && !loaded.has(info.name));
62
+ const names = rankTools2(args.query, candidates.map((info) => createToolSearchDocument2(info, found.get(info.name))), args.limit ?? DEFAULT_SEARCH_LIMIT2);
63
+ const lines = names.map((name) => {
64
+ const [line = ""] = (candidates.find((info) => info.name === name)?.description ?? "").trim().split(/\r?\n/);
65
+ return `- ${name}: ${line}`;
66
+ });
67
+ return {
68
+ content: [
69
+ {
70
+ kind: "text",
71
+ text: names.length === 0 ? "No matching tools found." : `Loaded ${names.length} tool${names.length === 1 ? "" : "s"}. They are available from your next call:
72
+ ${lines.join(`
73
+ `)}`
74
+ }
75
+ ],
76
+ output: { loaded: [...names] }
77
+ };
78
+ }
79
+ };
80
+ api.contribute.tools.add(search);
81
+ api.hook("transform_transcript", (transcript) => {
82
+ const declared = new Set(transcript.tools.map((info) => info.name));
83
+ const pending = api.contribute.tools.list().some((definition) => definition.exposure === "deferred" && !declared.has(definition.name));
84
+ return pending ? transcript : { ...transcript, tools: transcript.tools.filter((info) => info.name !== search.name) };
85
+ });
86
+ }
87
+ };
88
+ function tokenize2(text) {
89
+ return text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 0 && !STOP_WORDS.has(term)).map(stem);
90
+ }
91
+ function stem(term) {
92
+ if (term.length > 4 && term.endsWith("ies"))
93
+ return `${term.slice(0, -3)}y`;
94
+ if (term.length > 4 && /(ches|shes|sses|xes|zes)$/.test(term))
95
+ return term.slice(0, -2);
96
+ if (term.length > 3 && term.endsWith("s") && !term.endsWith("ss"))
97
+ return term.slice(0, -1);
98
+ return term;
99
+ }
100
+ function createToolSearchDocument2(tool, namespace) {
101
+ const parts = [
102
+ tool.name,
103
+ tool.name.replaceAll("_", " "),
104
+ tool.description,
105
+ ...describeSchema(tool.parameters),
106
+ ...namespace === undefined ? [] : [namespace.name, namespace.description ?? "", namespace.instructions ?? ""]
107
+ ];
108
+ return { name: tool.name, text: parts.filter((part) => part.trim() !== "").join(" ") };
109
+ }
110
+ function describeSchema(schema) {
111
+ if (schema === undefined || !isJsonObject(schema))
112
+ return [];
113
+ const properties = isJsonObject(schema.properties) ? Object.entries(schema.properties) : [];
114
+ return [
115
+ ...typeof schema.description === "string" ? [schema.description] : [],
116
+ ...properties.flatMap(([name, property]) => [name, ...describeSchema(property)]),
117
+ ...describeSchema(schema.items),
118
+ ...["anyOf", "oneOf", "allOf"].flatMap((key) => {
119
+ const variants = schema[key];
120
+ return Array.isArray(variants) ? variants.flatMap(describeSchema) : [];
121
+ })
122
+ ];
123
+ }
124
+ function rankTools2(query, documents, limit) {
125
+ const terms = [...new Set(tokenize2(query))];
126
+ if (terms.length === 0 || documents.length === 0)
127
+ return [];
128
+ const indexed = documents.map((document) => {
129
+ const tokens = tokenize2(document.text);
130
+ return { name: document.name, length: tokens.length, counts: Map.groupBy(tokens, (t) => t) };
131
+ });
132
+ const average = indexed.reduce((sum, entry) => sum + entry.length, 0) / indexed.length || 1;
133
+ const idf = new Map(terms.map((term) => {
134
+ const frequency = indexed.filter((entry) => entry.counts.has(term)).length;
135
+ return [term, Math.log(1 + (indexed.length - frequency + 0.5) / (frequency + 0.5))];
136
+ }));
137
+ return indexed.map((entry) => ({
138
+ name: entry.name,
139
+ score: terms.reduce((score, term) => {
140
+ const count = entry.counts.get(term)?.length ?? 0;
141
+ const norm = K1 * (1 - B + B * entry.length / average);
142
+ return score + (idf.get(term) ?? 0) * (count * (K1 + 1)) / (count + norm);
143
+ }, 0)
144
+ })).filter((match) => match.score > 0).toSorted((left, right) => right.score - left.score).slice(0, limit).map((match) => match.name);
145
+ }
146
+
147
+ export { DEFAULT_SEARCH_LIMIT2, tool_search_default, tokenize2, createToolSearchDocument2, rankTools2 };
@@ -15,7 +15,7 @@ import { processEnvironment } from "@nyte-ai/core";
15
15
  // package.json
16
16
  var package_default = {
17
17
  name: "@nyte-ai/plugin",
18
- version: "0.0.25-beta.1",
18
+ version: "0.0.25-nightly.20261025.2",
19
19
  private: false,
20
20
  license: "MIT",
21
21
  repository: {
@@ -46,8 +46,10 @@ var package_default = {
46
46
  "./core/openai-compaction": "./src/openai-compaction.ts",
47
47
  "./core/question": "./src/question/question.ts",
48
48
  "./core/rename": "./src/rename.ts",
49
+ "./core/subagents": "./src/subagents.ts",
49
50
  "./core/telemetry": "./src/telemetry.ts",
50
- "./core/web-search": "./src/web-search.ts",
51
+ "./core/tool-search": "./src/tool-search.ts",
52
+ "./core/web": "./src/web/index.ts",
51
53
  "./client": "./src/client.ts",
52
54
  "./client/mount": "./src/client/mount.ts",
53
55
  "./client/notifications": "./src/client/notifications.ts",
@@ -127,13 +129,21 @@ var package_default = {
127
129
  types: "./dist/src/rename.d.ts",
128
130
  import: "./dist/src/rename.js"
129
131
  },
132
+ "./core/subagents": {
133
+ types: "./dist/src/subagents.d.ts",
134
+ import: "./dist/src/subagents.js"
135
+ },
130
136
  "./core/telemetry": {
131
137
  types: "./dist/src/telemetry.d.ts",
132
138
  import: "./dist/src/telemetry.js"
133
139
  },
134
- "./core/web-search": {
135
- types: "./dist/src/web-search.d.ts",
136
- import: "./dist/src/web-search.js"
140
+ "./core/tool-search": {
141
+ types: "./dist/src/tool-search.d.ts",
142
+ import: "./dist/src/tool-search.js"
143
+ },
144
+ "./core/web": {
145
+ types: "./dist/src/web/index.d.ts",
146
+ import: "./dist/src/web/index.js"
137
147
  },
138
148
  "./client": {
139
149
  types: "./dist/src/client.d.ts",
@@ -206,6 +216,7 @@ var package_default = {
206
216
  "@opentelemetry/sdk-trace": "^3.0.0",
207
217
  "@opentelemetry/semantic-conventions": "^1.43.0",
208
218
  "es-module-lexer": "^3.0.4",
219
+ htmlparser2: "^8.0.2",
209
220
  sucrase: "^3.35.1",
210
221
  typebox: "^1.4.2",
211
222
  zod: "^4.6.7"
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  AcpProcess2
3
- } from "../../chunks/bash-description-eqp7ap2w.js";
3
+ } from "../../chunks/bash-description-vg0xecwm.js";
4
4
  import"../../chunks/bash-description-svrhh5a5.js";
5
5
  import"../../chunks/bash-description-9nn3yp18.js";
6
6
  export {
@@ -8,7 +8,7 @@ import {
8
8
  import"../../chunks/bash-description-svrhh5a5.js";
9
9
  import {
10
10
  AcpProcess2
11
- } from "../../chunks/bash-description-eqp7ap2w.js";
11
+ } from "../../chunks/bash-description-vg0xecwm.js";
12
12
  import"../../chunks/bash-description-4vz9pxyx.js";
13
13
  import {
14
14
  registryEntries2,
@@ -54,6 +54,7 @@ var CLAUDE_CODE_HAS_ITS_OWN = new Set([
54
54
  "edit",
55
55
  "list",
56
56
  "web_search",
57
+ "web_fetch",
57
58
  "question"
58
59
  ]);
59
60
  var ultracodeSetting = {
@@ -1,7 +1,14 @@
1
+ import {
2
+ DEFAULT_SEARCH_LIMIT2,
3
+ createToolSearchDocument2,
4
+ rankTools2
5
+ } from "../chunks/bash-description-b78pre8c.js";
6
+
1
7
  // src/codemode.ts
2
8
  import { CodemodeSandbox } from "@earendil-works/pi-codemode";
3
- import { renderToolSample } from "@earendil-works/pi-codemode/declarations";
9
+ import { renderToolSample, toCodemodeIdentifier } from "@earendil-works/pi-codemode/declarations";
4
10
  import { CODEMODE_SOURCE_GRAMMAR, parseCodemodeSource } from "@earendil-works/pi-codemode/source";
11
+ import { estimateTextTokens } from "@nyte-ai/ai/utils/estimate";
5
12
  import { dev } from "@nyte-ai/core";
6
13
  import { isJson, isJsonObject, SdkError, standard } from "@nyte-ai/schema";
7
14
  import { Type } from "typebox";
@@ -9,8 +16,8 @@ var DESCRIPTION = `Run JavaScript that calls other tools. The input is raw JavaS
9
16
  - \`await tools.<name>({ ...args })\` resolves to the tool's output, or its text, and rejects with an Error on failure.
10
17
  - Optional first line: \`// @options: {"timeout_ms": 60000}\`
11
18
  - \`text(value)\`, \`console.log(...)\` and \`return\` add output; \`store(key, value)\` and \`load(key)\` keep JSON values across codemode calls in this session.
12
- - \`ALL_TOOLS\`, \`searchTools(query, { limit?, namespace? })\` and \`describeTool(name)\` find tools and their declarations.`;
13
- var SUMMARY_CHARS = 160;
19
+ - \`ALL_TOOLS\`, \`await searchTools(query, { limit?, namespace? })\`, \`await describeTool(name)\` and \`await describeNamespace(name)\` find unlisted tools, such as MCP tools, and their declarations.`;
20
+ var LISTING_TOKENS = 3000;
14
21
  var parameters = standard(Type.Object({ code: Type.String({ description: "Raw JavaScript source." }) }));
15
22
  function codemodePlugin(runtime) {
16
23
  return {
@@ -18,32 +25,94 @@ function codemodePlugin(runtime) {
18
25
  activate(api) {
19
26
  api.contribute.tools.add(codemode(api, runtime));
20
27
  api.contribute.prompt.add((draft, siblings) => {
21
- const reachable = siblings.tools.list().flatMap((tool) => tool.exposure === "code" || tool.exposure === "deferred" ? [`- ${tool.name}: ${summarize(tool.description)}`] : []).toSorted();
22
- if (reachable.length === 0)
23
- return;
24
- draft.set("codemode", {
25
- id: "codemode",
26
- order: 96,
27
- text: [
28
- "Tools you reach only from a codemode script, as `await tools.<name>(args)`; `describeTool(name)` inside a script returns a tool's arguments:",
29
- ...reachable
30
- ].join(`
31
- `)
32
- });
28
+ const text = listTools(siblings.tools.list());
29
+ if (text !== null)
30
+ draft.set("codemode", { id: "codemode", order: 96, text });
33
31
  });
34
32
  }
35
33
  };
36
34
  }
37
- function summarize(description) {
38
- const [line = ""] = description.trim().split(`
35
+ function listTools(definitions) {
36
+ const sections = definitions.flatMap((definition) => {
37
+ if (definition.exposure !== "code")
38
+ return [];
39
+ const inputSchema = toJsonSchema(definition.parameters, "input");
40
+ const outputSchema = definition.output === undefined ? { type: "string" } : toJsonSchema(definition.output, "output");
41
+ if (inputSchema === null || outputSchema === null)
42
+ return [];
43
+ const id = toCodemodeIdentifier(definition.name);
44
+ const sample = renderToolSample({
45
+ name: definition.name,
46
+ description: definition.description,
47
+ inputSchema,
48
+ outputSchema
49
+ }).trim();
50
+ const section = `### \`${id}\`${id === definition.name ? "" : ` (\`${definition.name}\`)`}
51
+ ${sample}`;
52
+ return [
53
+ {
54
+ namespace: definition.namespace,
55
+ name: definition.name,
56
+ section,
57
+ cost: estimateTextTokens(section)
58
+ }
59
+ ];
60
+ });
61
+ if (sections.length === 0)
62
+ return null;
63
+ const groups = [
64
+ ...Map.groupBy(sections, (entry) => entry.namespace?.name ?? "").entries()
65
+ ].toSorted((left, right) => left[0].localeCompare(right[0])).map((pair) => ({ namespace: pair[1][0]?.namespace, entries: pair[1] }));
66
+ const shown = selectListed(groups, LISTING_TOKENS);
67
+ return [
68
+ "Tools you reach only from a codemode script, as `await tools.<name>(args)`:",
69
+ ...groups.flatMap((group) => {
70
+ const visible = group.entries.filter((entry) => shown.has(entry.name));
71
+ if (group.namespace === undefined)
72
+ return visible.map((entry) => entry.section);
73
+ const listing = visible.length === group.entries.length ? "" : visible.length === 0 ? " (tools not listed)" : " (some tools not listed)";
74
+ const description = group.namespace.description?.trim() ?? "";
75
+ return [
76
+ `## ${group.namespace.name}${listing}${description === "" ? "" : `
77
+ ${description}`}`,
78
+ ...visible.map((entry) => entry.section)
79
+ ];
80
+ })
81
+ ].join(`
82
+
39
83
  `);
40
- return line.length > SUMMARY_CHARS ? `${line.slice(0, SUMMARY_CHARS)}…` : line;
84
+ }
85
+ function selectListed(groups, budget) {
86
+ const queues = groups.map((group) => group.entries.toSorted((a, b) => a.cost - b.cost));
87
+ const shown = new Set;
88
+ let remaining = budget;
89
+ let active = queues.filter((queue) => queue.length > 0);
90
+ while (active.length > 0)
91
+ active = active.filter((queue) => {
92
+ const next = queue[0];
93
+ if (next === undefined || next.cost > remaining)
94
+ return false;
95
+ remaining -= next.cost;
96
+ shown.add(next.name);
97
+ queue.shift();
98
+ return queue.length > 0;
99
+ });
100
+ return shown;
101
+ }
102
+ function toJsonSchema(schema, direction) {
103
+ try {
104
+ const converted = schema["~standard"].jsonSchema[direction]({ target: "draft-2020-12" });
105
+ return isJson(converted) && isJsonObject(converted) ? converted : null;
106
+ } catch {
107
+ return null;
108
+ }
41
109
  }
42
110
  function codemode(api, runtime) {
43
111
  return {
44
112
  name: "codemode",
45
113
  description: DESCRIPTION,
46
114
  parameters,
115
+ exposure: "model-only",
47
116
  grammar: { kind: "lark", source: CODEMODE_SOURCE_GRAMMAR },
48
117
  class: () => ({ kind: "custom", label: "Running a script" }),
49
118
  effects: "none",
@@ -51,14 +120,24 @@ function codemode(api, runtime) {
51
120
  replay: "never",
52
121
  async execute(args, invocation) {
53
122
  const { code, options } = parseCodemodeSource(args.code);
54
- const callable = invocation.tools.list().filter((info) => info.name !== "codemode" && info.exposure !== "hidden");
123
+ const callable = invocation.tools.list().filter((info) => info.exposure !== "model-only" && info.exposure !== "hidden");
55
124
  const stored = await api.storage.get("store");
56
125
  if (stored !== undefined && !isJsonObject(stored))
57
126
  throw new SdkError("internal", "Codemode's stored values are not a JSON object.");
127
+ const namespaces = new Map(api.contribute.tools.list().flatMap((definition) => definition.namespace === undefined ? [] : [[definition.name, definition.namespace]]));
128
+ const samples = new Map(callable.map((info) => [
129
+ info.name,
130
+ renderToolSample({
131
+ name: info.name,
132
+ description: info.description,
133
+ inputSchema: info.parameters,
134
+ outputSchema: info.output ?? { type: "string" }
135
+ })
136
+ ]));
58
137
  const sandbox = new CodemodeSandbox({
59
138
  ...runtime,
60
- tools: callable.map((info) => bridge(info, invocation)),
61
- globals: discovery(callable),
139
+ tools: callable.map((info) => bridge(info, samples.get(info.name) ?? "", invocation)),
140
+ globals: discovery(callable, samples, namespaces),
62
141
  timeoutMs: options.timeoutMs ?? 300000,
63
142
  memoryLimitBytes: 256 * 1024 * 1024
64
143
  });
@@ -102,11 +181,11 @@ ${calls(result)}`);
102
181
  }
103
182
  };
104
183
  }
105
- function bridge(info, invocation) {
184
+ function bridge(info, sample, invocation) {
106
185
  let sequence = 0;
107
186
  return {
108
187
  name: info.name,
109
- description: info.description,
188
+ description: sample,
110
189
  inputSchema: info.parameters,
111
190
  outputSchema: info.output ?? { type: "string" },
112
191
  async execute(args, { signal }) {
@@ -121,42 +200,70 @@ function unwrapToolOutcome(outcome) {
121
200
  `);
122
201
  throw new Error(outcome.kind === "failed" ? outcome.message : outcome.kind === "rejected" ? `Rejected: ${outcome.reason}` : `The call was ${outcome.kind}.`);
123
202
  }
124
- function discovery(tools) {
203
+ function discovery(tools, samples, namespaces) {
125
204
  return [
126
205
  {
127
206
  name: "searchTools",
128
207
  spread: true,
129
208
  execute(args) {
130
209
  const [query, options] = Array.isArray(args) ? args : [];
131
- const words = new Set(String(query).toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 1));
132
- const limit = typeof options === "object" && options !== null && "limit" in options && typeof options.limit === "number" ? options.limit : 8;
133
- const namespace = typeof options === "object" && options !== null && "namespace" in options && typeof options.namespace === "string" ? options.namespace : null;
134
- return tools.filter((info) => namespace === null || info.namespace === namespace).map((info) => ({
135
- info,
136
- score: `${info.name} ${info.description}`.toLowerCase().split(/[^a-z0-9]+/).filter((word) => words.has(word)).length
137
- })).filter((entry) => entry.score > 0).toSorted((left, right) => right.score - left.score).slice(0, limit).map(({ info }) => ({
138
- name: info.name,
139
- namespace: info.namespace,
140
- description: info.description
210
+ if (typeof query !== "string")
211
+ throw new Error("searchTools() expects a query string");
212
+ const limit = isJsonObject(options) ? options.limit ?? null : null;
213
+ const namespace = isJsonObject(options) ? options.namespace ?? null : null;
214
+ if (limit !== null && (typeof limit !== "number" || !Number.isInteger(limit) || limit <= 0))
215
+ throw new Error("searchTools() limit must be a positive integer");
216
+ if (namespace !== null && typeof namespace !== "string")
217
+ throw new Error("searchTools() namespace must be a string");
218
+ const documents = tools.flatMap((info) => {
219
+ const found = namespaces.get(info.name);
220
+ if (namespace !== null && (found === undefined || !matchesNamespace(found, namespace)))
221
+ return [];
222
+ return [createToolSearchDocument2(info, found)];
223
+ });
224
+ return rankTools2(query, documents, limit ?? DEFAULT_SEARCH_LIMIT2).map((name) => ({
225
+ name: toCodemodeIdentifier(name),
226
+ description: samples.get(name) ?? ""
141
227
  }));
142
228
  }
143
229
  },
144
230
  {
145
231
  name: "describeTool",
146
232
  execute(name) {
147
- const info = tools.find((candidate) => candidate.name === name);
233
+ if (typeof name !== "string")
234
+ throw new Error("describeTool() expects a tool name");
235
+ const info = tools.find((candidate) => candidate.name === name || toCodemodeIdentifier(candidate.name) === name);
148
236
  if (info === undefined)
149
- throw new Error(`No tool is named ${String(name)}.`);
150
- return renderToolSample({
151
- name: info.name,
152
- description: info.description,
153
- inputSchema: info.parameters,
154
- outputSchema: info.output ?? { type: "string" }
237
+ throw new Error(`No tool is named ${name}.`);
238
+ return samples.get(info.name) ?? "";
239
+ }
240
+ },
241
+ {
242
+ name: "describeNamespace",
243
+ execute(name) {
244
+ if (typeof name !== "string")
245
+ throw new Error("describeNamespace() expects a namespace name");
246
+ const members = tools.filter((info) => {
247
+ const found = namespaces.get(info.name);
248
+ return found !== undefined && matchesNamespace(found, name);
155
249
  });
250
+ const namespace = members[0] === undefined ? undefined : namespaces.get(members[0].name);
251
+ if (namespace === undefined)
252
+ throw new Error(`No namespace is named ${name}.`);
253
+ return {
254
+ name: namespace.name,
255
+ ...namespace.description === undefined ? {} : { description: namespace.description },
256
+ ...namespace.instructions === undefined ? {} : { instructions: namespace.instructions },
257
+ tools: members.map((info) => toCodemodeIdentifier(info.name))
258
+ };
156
259
  }
157
260
  }
158
261
  ];
159
262
  }
263
+ function matchesNamespace(namespace, query) {
264
+ const id = toCodemodeIdentifier(query);
265
+ return [namespace.name, ...namespace.aliases ?? []].some((name) => name === query || toCodemodeIdentifier(name) === id);
266
+ }
160
267
  function calls(result) {
161
268
  return result.calls.length === 0 ? "No tool calls were made." : `Tool calls made before the failure (not undone): ${result.calls.map((call) => `${call.name} (${call.status})`).join(", ")}`;
162
269
  }
@@ -1,6 +1,6 @@
1
1
  import type { HookEntry as SideHookEntry, HookHandler as SideHookHandler, HookName as SideHookName, Plugin as CorePlugin, PluginApi as CorePluginApi } from "@nyte-ai/core";
2
2
  export { branchSummary, compaction, definePart, isPart, text, thinking, tool } from "@nyte-ai/core";
3
- export type { ActionContext, Agent, Command, Durable, HookTable, ModelContextPolicy, Part, PartDefinition, PartState, PromptSection, Setting, SettingDefinition, Skill, ToolDefinition, ToolInvocation, } from "@nyte-ai/core";
3
+ export type { ActionContext, Agent, Command, Durable, HookTable, ModelContextPolicy, Part, PartDefinition, PartState, PromptSection, SearchProvider, SearchResult, Setting, SettingDefinition, Skill, ToolDefinition, ToolInvocation, } from "@nyte-ai/core";
4
4
  export type { CompactionPlan, Decision, ModelRequest, Submission, ToolCall, Transcript, } from "@nyte-ai/schema";
5
5
  /**
6
6
  * A core plugin: per session on the host, with tools, prompt sections,