toolnexus 0.22.0 → 0.23.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/README.md +50 -4
- package/dist/client.d.ts +3 -0
- package/dist/client.js +12 -6
- package/dist/toolkit.d.ts +2 -2
- package/dist/toolkit.js +2 -2
- package/package.json +4 -3
- package/src/client.ts +13 -6
- package/src/toolkit.ts +2 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ unified as one tool set, driving any LLM.
|
|
|
12
12
|
> loop, hooks, streaming, retries, memory — and nothing it doesn't.
|
|
13
13
|
|
|
14
14
|
The JS/TypeScript port of [toolnexus](https://github.com/muthuishere/toolnexus) — the same
|
|
15
|
-
library, byte-identical, also in **Python, Go, Java, C#, Elixir and
|
|
15
|
+
library, byte-identical, also in **Python, Go, Java, C#, Elixir, Clojure and Rust**. Built on
|
|
16
16
|
`@modelcontextprotocol/sdk` (the MCP SDK opencode uses).
|
|
17
17
|
|
|
18
18
|
## Install
|
|
@@ -28,11 +28,11 @@ Built-in tools are on by default, so an empty toolkit can already act:
|
|
|
28
28
|
```ts
|
|
29
29
|
import { createToolkit, createClient } from "toolnexus"
|
|
30
30
|
|
|
31
|
-
const tk = await createToolkit()
|
|
31
|
+
const tk = await createToolkit({}) // 10 built-in tools, on by default
|
|
32
32
|
const agent = createClient({
|
|
33
33
|
baseUrl: "https://openrouter.ai/api/v1", // any OpenAI- or Anthropic-style endpoint
|
|
34
34
|
style: "openai", // or "anthropic"
|
|
35
|
-
model: "openai/gpt-4o-mini",
|
|
35
|
+
model: "openai/gpt-4o-mini", // key: OPENROUTER_API_KEY (matches the host)
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
const { text } = await agent.run("What files are in this folder?", { toolkit: tk })
|
|
@@ -49,6 +49,52 @@ const tk = await createToolkit({ mcp: "mcp.json", skills: ["skills"] })
|
|
|
49
49
|
- `skills/` is a folder of `**/SKILL.md` files, loaded on demand through one `skill` tool.
|
|
50
50
|
- Remote MCP `headers` values expand `${ENV_VAR}` at call time and are never logged.
|
|
51
51
|
|
|
52
|
+
## API key by host (BREAKING in 0.22)
|
|
53
|
+
|
|
54
|
+
A key is only ever sent to the host it belongs to. `apiKey` wins; else `apiKeyEnv` (the NAME of
|
|
55
|
+
an env var); else only the env var of the `baseUrl` host, over https — `api.openai.com` →
|
|
56
|
+
`OPENAI_API_KEY`, `api.anthropic.com` → `ANTHROPIC_API_KEY`, `openrouter.ai` →
|
|
57
|
+
`OPENROUTER_API_KEY`. **A local server, a proxy or any other host needs `apiKey` or `apiKeyEnv`**
|
|
58
|
+
(before 0.22 the first provider key in the environment was sent to any `baseUrl`):
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
createClient({ baseUrl: "http://localhost:11434/v1", style: "openai", model: "llama3", apiKey: "YOUR_KEY_HERE" })
|
|
62
|
+
createClient({ baseUrl: "https://my-proxy.example/v1", style: "openai", model: "m", apiKeyEnv: "MY_PROXY_KEY" })
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Prompt cache and tool tiers
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const agent = createClient({
|
|
69
|
+
baseUrl, style, model,
|
|
70
|
+
promptCache: { key: "session-42" }, // anthropic cache breakpoints / openai prompt_cache_key
|
|
71
|
+
toolTiers: { core: ["read", "grep"] }, // the rest load on demand via tool_search / call_tool
|
|
72
|
+
})
|
|
73
|
+
const res = await agent.run(prompt, { toolkit: tk })
|
|
74
|
+
res.usage.cacheReadTokens // cache hits; anthropic context = prompt + cacheRead + cacheWrite
|
|
75
|
+
agent.toolTierPlan(tk) // { read: "core", bash: "listed", … }, frozen per toolkit
|
|
76
|
+
agent.resetToolTiers(tk) // rebuild at session start
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`toolTiers` keeps the tools array identical on every request, so the cached prefix survives a
|
|
80
|
+
large or growing toolkit. `call_tool` is unwrapped before hooks, so permission gates see the real
|
|
81
|
+
tool. Runnable: `npm run example:prompt-cache` ([`examples/prompt_cache.ts`](examples/prompt_cache.ts)).
|
|
82
|
+
|
|
83
|
+
## Host additions (0.22)
|
|
84
|
+
|
|
85
|
+
- `loadMcp(cfg, { onServer })` — each MCP server reported the moment it connects or fails;
|
|
86
|
+
`mcpSource.resourceTools()` adds `list_mcp_resources` / `read_mcp_resource`.
|
|
87
|
+
- `waitFor(request, { signal })` — a pending question ends with its turn.
|
|
88
|
+
- Tool `hints` (`readOnly` / `destructive` / `openWorld`) on every builtin and MCP tool, passed to
|
|
89
|
+
`hooks.beforeTool` — allow, deny with an error result, or ask via `pending`.
|
|
90
|
+
- Opt-in builtins: `builtins: { background: new BackgroundShells(), webSearch, notebookEdit: true }`
|
|
91
|
+
(`bash_output` / `kill_shell`, `websearch`, `notebook_edit`); `grep` takes `context` / `before` /
|
|
92
|
+
`after`; `read` / `grep` report truncation in metadata.
|
|
93
|
+
- `agents.compactor({ onCompact })`, `agent.asToolWith(ctx => ({ hooks, waitFor }))`, runtime
|
|
94
|
+
`waitFor`, `ctx.callId` on tools, `listModels(opts)`.
|
|
95
|
+
|
|
96
|
+
ACP host hooks (permission choice, update stream, cancel, streaming) are Go-only for now.
|
|
97
|
+
|
|
52
98
|
## Simple judgments
|
|
53
99
|
|
|
54
100
|
A thin layer over any `Classifier` (SPEC.md §8B); the wire request is byte-identical to
|
|
@@ -102,4 +148,4 @@ Everything else — the full surface, with runnable examples — lives on the do
|
|
|
102
148
|
| **API reference** | [JavaScript](https://muthuishere.github.io/toolnexus/api/javascript/) |
|
|
103
149
|
| **Cookbook** | [Zero to agent](https://muthuishere.github.io/toolnexus/cookbook/zero-to-agent/) · [MCP servers](https://muthuishere.github.io/toolnexus/cookbook/mcp-servers/) · [Agent skills](https://muthuishere.github.io/toolnexus/cookbook/agent-skills/) · [Judge](https://muthuishere.github.io/toolnexus/cookbook/judge/) |
|
|
104
150
|
|
|
105
|
-
Contract across all
|
|
151
|
+
Contract across all eight ports: [`SPEC.md`](https://github.com/muthuishere/toolnexus/blob/main/SPEC.md).
|
package/dist/client.d.ts
CHANGED
|
@@ -493,6 +493,9 @@ export declare class Client {
|
|
|
493
493
|
private runToolDirect;
|
|
494
494
|
/** ToolTiers `call_tool {name, arguments}` -> the real call; undefined when tiers are off. */
|
|
495
495
|
private unwrapCall;
|
|
496
|
+
/** The RunResult.toolCalls entry: a `call_tool` call is recorded as the real tool with its
|
|
497
|
+
* inner arguments, the name hooks and stream events see (the transcript keeps the wrapper). */
|
|
498
|
+
private toolCallRecord;
|
|
496
499
|
/** The stream's tool_call event, naming the real tool for a `call_tool` call. */
|
|
497
500
|
private callEvent;
|
|
498
501
|
private resultEvent;
|
package/dist/client.js
CHANGED
|
@@ -722,6 +722,12 @@ export class Client {
|
|
|
722
722
|
unwrapCall(name, args) {
|
|
723
723
|
return this.opts.toolTiers ? unwrapCallTool(name, args) : undefined;
|
|
724
724
|
}
|
|
725
|
+
/** The RunResult.toolCalls entry: a `call_tool` call is recorded as the real tool with its
|
|
726
|
+
* inner arguments, the name hooks and stream events see (the transcript keeps the wrapper). */
|
|
727
|
+
toolCallRecord(name, args, result) {
|
|
728
|
+
const inner = this.unwrapCall(name, args);
|
|
729
|
+
return { name: inner ? inner.name : name, args: inner ? inner.args : args, output: result.output, isError: result.isError, metadata: result.metadata };
|
|
730
|
+
}
|
|
725
731
|
/** The stream's tool_call event, naming the real tool for a `call_tool` call. */
|
|
726
732
|
callEvent(id, name, args) {
|
|
727
733
|
const inner = this.unwrapCall(name, args);
|
|
@@ -897,7 +903,7 @@ export class Client {
|
|
|
897
903
|
// and the later suspensions' placeholder results never enter the transcript (they re-suspend
|
|
898
904
|
// on resume). Mirrors the streaming path.
|
|
899
905
|
for (const s of settled) {
|
|
900
|
-
toolCalls.push(
|
|
906
|
+
toolCalls.push(this.toolCallRecord(s.call.function.name, s.args, s.result));
|
|
901
907
|
messages.push(toolMessage(s.call.id, s.call.function.name, s.result));
|
|
902
908
|
if (s.halted)
|
|
903
909
|
return this.pendingRun(runStart, lastModel, s.halted, messages, toolCalls, turns, usage);
|
|
@@ -1021,7 +1027,7 @@ export class Client {
|
|
|
1021
1027
|
const content = [];
|
|
1022
1028
|
let halted;
|
|
1023
1029
|
for (const s of settled) {
|
|
1024
|
-
toolCalls.push(
|
|
1030
|
+
toolCalls.push(this.toolCallRecord(s.use.name, s.args, s.result));
|
|
1025
1031
|
content.push(toolResultBlock(s.use.id, s.result));
|
|
1026
1032
|
if (s.halted) {
|
|
1027
1033
|
halted = s.halted;
|
|
@@ -1145,13 +1151,13 @@ export class Client {
|
|
|
1145
1151
|
const r = await this.resolvePending(toolkit, c.name, args, req, c.id, turn, signal);
|
|
1146
1152
|
result = r.result;
|
|
1147
1153
|
if (r.halted) {
|
|
1148
|
-
toolCalls.push(
|
|
1154
|
+
toolCalls.push(this.toolCallRecord(c.name, args, result));
|
|
1149
1155
|
messages.push(toolMessage(c.id, c.name, result));
|
|
1150
1156
|
yield { type: "done", result: this.pendingRun(runStart, lastModel, r.halted, messages, toolCalls, turns, usage) };
|
|
1151
1157
|
return;
|
|
1152
1158
|
}
|
|
1153
1159
|
}
|
|
1154
|
-
toolCalls.push(
|
|
1160
|
+
toolCalls.push(this.toolCallRecord(c.name, args, result));
|
|
1155
1161
|
messages.push(toolMessage(c.id, c.name, result));
|
|
1156
1162
|
yield this.resultEvent(c.id, c.name, safeJson(c.args), result);
|
|
1157
1163
|
}
|
|
@@ -1266,14 +1272,14 @@ export class Client {
|
|
|
1266
1272
|
const r = await this.resolvePending(toolkit, u.name, args, req, u.id, turn, signal);
|
|
1267
1273
|
result = r.result;
|
|
1268
1274
|
if (r.halted) {
|
|
1269
|
-
toolCalls.push(
|
|
1275
|
+
toolCalls.push(this.toolCallRecord(u.name, args, result));
|
|
1270
1276
|
results.push(toolResultBlock(u.id, result));
|
|
1271
1277
|
messages.push({ role: "user", content: results });
|
|
1272
1278
|
yield { type: "done", result: this.pendingRun(runStart, lastModel, r.halted, messages, toolCalls, turns, usage) };
|
|
1273
1279
|
return;
|
|
1274
1280
|
}
|
|
1275
1281
|
}
|
|
1276
|
-
toolCalls.push(
|
|
1282
|
+
toolCalls.push(this.toolCallRecord(u.name, args, result));
|
|
1277
1283
|
results.push(toolResultBlock(u.id, result));
|
|
1278
1284
|
yield this.resultEvent(u.id, u.name, u.input, result);
|
|
1279
1285
|
}
|
package/dist/toolkit.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ export declare class Toolkit {
|
|
|
52
52
|
private readonly mcpServerConfig;
|
|
53
53
|
private byName;
|
|
54
54
|
private constructor();
|
|
55
|
-
static create(opts
|
|
55
|
+
static create(opts?: ToolkitOptions): Promise<Toolkit>;
|
|
56
56
|
tools(): Tool[];
|
|
57
57
|
get(name: string): Tool | undefined;
|
|
58
58
|
/** Add a tool at runtime (native, http, or any custom Tool). First name wins. */
|
|
@@ -104,4 +104,4 @@ export declare class Toolkit {
|
|
|
104
104
|
}[];
|
|
105
105
|
close(): Promise<void>;
|
|
106
106
|
}
|
|
107
|
-
export declare function createToolkit(opts
|
|
107
|
+
export declare function createToolkit(opts?: ToolkitOptions): Promise<Toolkit>;
|
package/dist/toolkit.js
CHANGED
|
@@ -38,7 +38,7 @@ export class Toolkit {
|
|
|
38
38
|
this.byName.set(t.name, t);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
static async create(opts) {
|
|
41
|
+
static async create(opts = {}) {
|
|
42
42
|
const mcp = opts.mcpConfig !== undefined ? await loadMcp(opts.mcpConfig, { waitFor: opts.waitFor, signal: opts.signal }) : undefined;
|
|
43
43
|
// Skills come from directories, in-memory data, and/or a lazy provider (§3,
|
|
44
44
|
// S1). The provider is resolved here (create() is async) and merged with the
|
|
@@ -184,6 +184,6 @@ export class Toolkit {
|
|
|
184
184
|
await this.mcp?.close();
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
-
export function createToolkit(opts) {
|
|
187
|
+
export function createToolkit(opts = {}) {
|
|
188
188
|
return Toolkit.create(opts);
|
|
189
189
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolnexus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Dynamic MCP servers + agent skills as uniform tools for any LLM (opencode-style).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc -p tsconfig.json",
|
|
20
|
-
"test": "npm run build && node --experimental-strip-types --test test/unit.test.ts test/agents.test.ts test/agent-hooks.test.ts test/agent-inprocess.test.ts test/home.test.ts test/compaction.test.ts test/loop.test.ts test/in-process.test.ts test/translate.test.ts test/multimodal.test.ts test/classifier.test.ts test/acp.test.ts test/issues.test.ts test/host-boundary.test.ts test/judge.test.ts test/batteries.test.ts test/host-engine.test.ts test/host-turn-durability.test.ts test/host-engine-asks.test.ts",
|
|
20
|
+
"test": "npm run build && node --experimental-strip-types --test test/unit.test.ts test/agents.test.ts test/agent-hooks.test.ts test/agent-inprocess.test.ts test/home.test.ts test/compaction.test.ts test/loop.test.ts test/in-process.test.ts test/translate.test.ts test/multimodal.test.ts test/classifier.test.ts test/acp.test.ts test/issues.test.ts test/host-boundary.test.ts test/judge.test.ts test/batteries.test.ts test/host-engine.test.ts test/host-turn-durability.test.ts test/host-engine-asks.test.ts test/examples.test.ts",
|
|
21
21
|
"example": "node --experimental-strip-types examples/basic.ts",
|
|
22
22
|
"example:judge": "node --experimental-strip-types examples/judge.ts",
|
|
23
|
-
"example:multimodal": "node --experimental-strip-types examples/multimodal.ts"
|
|
23
|
+
"example:multimodal": "node --experimental-strip-types examples/multimodal.ts",
|
|
24
|
+
"example:prompt-cache": "node --experimental-strip-types examples/prompt_cache.ts"
|
|
24
25
|
},
|
|
25
26
|
"keywords": [
|
|
26
27
|
"mcp",
|
package/src/client.ts
CHANGED
|
@@ -995,6 +995,13 @@ export class Client {
|
|
|
995
995
|
return this.opts.toolTiers ? unwrapCallTool(name, args) : undefined
|
|
996
996
|
}
|
|
997
997
|
|
|
998
|
+
/** The RunResult.toolCalls entry: a `call_tool` call is recorded as the real tool with its
|
|
999
|
+
* inner arguments, the name hooks and stream events see (the transcript keeps the wrapper). */
|
|
1000
|
+
private toolCallRecord(name: string, args: Record<string, unknown>, result: ToolResult): ToolCallRecord {
|
|
1001
|
+
const inner = this.unwrapCall(name, args)
|
|
1002
|
+
return { name: inner ? inner.name : name, args: inner ? inner.args : args, output: result.output, isError: result.isError, metadata: result.metadata }
|
|
1003
|
+
}
|
|
1004
|
+
|
|
998
1005
|
/** The stream's tool_call event, naming the real tool for a `call_tool` call. */
|
|
999
1006
|
private callEvent(id: string, name: string, args: Record<string, unknown>): StreamEvent {
|
|
1000
1007
|
const inner = this.unwrapCall(name, args)
|
|
@@ -1168,7 +1175,7 @@ export class Client {
|
|
|
1168
1175
|
// and the later suspensions' placeholder results never enter the transcript (they re-suspend
|
|
1169
1176
|
// on resume). Mirrors the streaming path.
|
|
1170
1177
|
for (const s of settled) {
|
|
1171
|
-
toolCalls.push(
|
|
1178
|
+
toolCalls.push(this.toolCallRecord(s.call.function.name, s.args, s.result))
|
|
1172
1179
|
messages.push(toolMessage(s.call.id, s.call.function.name, s.result))
|
|
1173
1180
|
if (s.halted) return this.pendingRun(runStart, lastModel, s.halted, messages, toolCalls, turns, usage)
|
|
1174
1181
|
}
|
|
@@ -1288,7 +1295,7 @@ export class Client {
|
|
|
1288
1295
|
const content: any[] = []
|
|
1289
1296
|
let halted: Request | undefined
|
|
1290
1297
|
for (const s of settled) {
|
|
1291
|
-
toolCalls.push(
|
|
1298
|
+
toolCalls.push(this.toolCallRecord(s.use.name, s.args, s.result))
|
|
1292
1299
|
content.push(toolResultBlock(s.use.id, s.result))
|
|
1293
1300
|
if (s.halted) { halted = s.halted; break }
|
|
1294
1301
|
}
|
|
@@ -1392,13 +1399,13 @@ export class Client {
|
|
|
1392
1399
|
const r = await this.resolvePending(toolkit, c.name, args, req, c.id, turn, signal)
|
|
1393
1400
|
result = r.result
|
|
1394
1401
|
if (r.halted) {
|
|
1395
|
-
toolCalls.push(
|
|
1402
|
+
toolCalls.push(this.toolCallRecord(c.name, args, result))
|
|
1396
1403
|
messages.push(toolMessage(c.id, c.name, result))
|
|
1397
1404
|
yield { type: "done", result: this.pendingRun(runStart, lastModel, r.halted, messages, toolCalls, turns, usage) }
|
|
1398
1405
|
return
|
|
1399
1406
|
}
|
|
1400
1407
|
}
|
|
1401
|
-
toolCalls.push(
|
|
1408
|
+
toolCalls.push(this.toolCallRecord(c.name, args, result))
|
|
1402
1409
|
messages.push(toolMessage(c.id, c.name, result))
|
|
1403
1410
|
yield this.resultEvent(c.id, c.name, safeJson(c.args), result)
|
|
1404
1411
|
}
|
|
@@ -1495,14 +1502,14 @@ export class Client {
|
|
|
1495
1502
|
const r = await this.resolvePending(toolkit, u.name, args, req, u.id, turn, signal)
|
|
1496
1503
|
result = r.result
|
|
1497
1504
|
if (r.halted) {
|
|
1498
|
-
toolCalls.push(
|
|
1505
|
+
toolCalls.push(this.toolCallRecord(u.name, args, result))
|
|
1499
1506
|
results.push(toolResultBlock(u.id, result))
|
|
1500
1507
|
messages.push({ role: "user", content: results })
|
|
1501
1508
|
yield { type: "done", result: this.pendingRun(runStart, lastModel, r.halted, messages, toolCalls, turns, usage) }
|
|
1502
1509
|
return
|
|
1503
1510
|
}
|
|
1504
1511
|
}
|
|
1505
|
-
toolCalls.push(
|
|
1512
|
+
toolCalls.push(this.toolCallRecord(u.name, args, result))
|
|
1506
1513
|
results.push(toolResultBlock(u.id, result))
|
|
1507
1514
|
yield this.resultEvent(u.id, u.name, u.input, result)
|
|
1508
1515
|
}
|
package/src/toolkit.ts
CHANGED
|
@@ -79,7 +79,7 @@ export class Toolkit {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
static async create(opts: ToolkitOptions): Promise<Toolkit> {
|
|
82
|
+
static async create(opts: ToolkitOptions = {}): Promise<Toolkit> {
|
|
83
83
|
const mcp = opts.mcpConfig !== undefined ? await loadMcp(opts.mcpConfig, { waitFor: opts.waitFor, signal: opts.signal }) : undefined
|
|
84
84
|
// Skills come from directories, in-memory data, and/or a lazy provider (§3,
|
|
85
85
|
// S1). The provider is resolved here (create() is async) and merged with the
|
|
@@ -250,6 +250,6 @@ export class Toolkit {
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
-
export function createToolkit(opts: ToolkitOptions): Promise<Toolkit> {
|
|
253
|
+
export function createToolkit(opts: ToolkitOptions = {}): Promise<Toolkit> {
|
|
254
254
|
return Toolkit.create(opts)
|
|
255
255
|
}
|