toolnexus 0.9.4 → 0.10.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 +31 -1
- package/dist/agents/agent.d.ts +60 -0
- package/dist/agents/agent.js +99 -0
- package/dist/agents/compaction.d.ts +77 -0
- package/dist/agents/compaction.js +96 -0
- package/dist/agents/home.d.ts +90 -0
- package/dist/agents/home.js +203 -0
- package/dist/agents/index.d.ts +10 -0
- package/dist/agents/index.js +10 -0
- package/dist/agents/runtime.d.ts +357 -0
- package/dist/agents/runtime.js +733 -0
- package/dist/client.d.ts +28 -4
- package/dist/client.js +26 -12
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/toolkit.d.ts +7 -0
- package/dist/toolkit.js +13 -3
- package/package.json +2 -2
- package/src/agents/agent.ts +129 -0
- package/src/agents/compaction.ts +148 -0
- package/src/agents/home.ts +250 -0
- package/src/agents/index.ts +10 -0
- package/src/agents/runtime.ts +890 -0
- package/src/client.ts +50 -14
- package/src/index.ts +1 -0
- package/src/toolkit.ts +19 -2
package/src/client.ts
CHANGED
|
@@ -47,6 +47,26 @@ export interface ClientOptions {
|
|
|
47
47
|
/** §8 Gap 2. HTTP transport for LLM requests (retries included). Omit ⇒ global fetch. Scope is the
|
|
48
48
|
* LLM path only; MCP transports use the SDK's own client. */
|
|
49
49
|
fetch?: typeof fetch
|
|
50
|
+
/** §8 Resilience. Classify each LLM-call failure into "retry" or "fail". Omit ⇒ the default
|
|
51
|
+
* classifier (retryable status/network within budget ⇒ retry, else fail) — byte-identical to
|
|
52
|
+
* today. A "retry" is always bounded by `retries`; the classifier cannot loop unbounded.
|
|
53
|
+
* There is no "suspend" tier — suspension (§10) stays a user-action pause, not an error path. */
|
|
54
|
+
onError?: (info: ErrorInfo) => ErrorTier
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** §8 Resilience. What to do with a failed LLM call. */
|
|
58
|
+
export type ErrorTier = "retry" | "fail"
|
|
59
|
+
|
|
60
|
+
/** §8 Resilience. Context passed to `onError` for each failed LLM attempt. */
|
|
61
|
+
export interface ErrorInfo {
|
|
62
|
+
/** The thrown network/transport error, when the failure was not an HTTP response. */
|
|
63
|
+
error?: unknown
|
|
64
|
+
/** The HTTP status, when the failure was a non-ok response. */
|
|
65
|
+
status?: number
|
|
66
|
+
/** Zero-based attempt index (0 = first try). */
|
|
67
|
+
attempt: number
|
|
68
|
+
/** Whether `status`/the error is in the default retryable set (429/5xx/network). */
|
|
69
|
+
retryable: boolean
|
|
50
70
|
}
|
|
51
71
|
|
|
52
72
|
/**
|
|
@@ -135,10 +155,14 @@ export interface RunResult {
|
|
|
135
155
|
usage: Usage
|
|
136
156
|
/** The model used. */
|
|
137
157
|
model: string
|
|
138
|
-
/** "done" normally; "pending" iff a tool suspended and no waitFor was configured (§10)
|
|
139
|
-
|
|
158
|
+
/** "done" normally; "pending" iff a tool suspended and no waitFor was configured (§10);
|
|
159
|
+
* "incomplete" iff a limit stopped the run loudly (§7D) — a maxTurns stop with no final
|
|
160
|
+
* text is never a silent "done". */
|
|
161
|
+
status: "done" | "pending" | "incomplete"
|
|
140
162
|
/** The unresolved suspension — present iff status === "pending" (§10). */
|
|
141
163
|
pending?: Request
|
|
164
|
+
/** The limit that stopped the run (e.g. "maxTurns") — present iff status === "incomplete". */
|
|
165
|
+
limit?: string
|
|
142
166
|
}
|
|
143
167
|
|
|
144
168
|
/** Events yielded by client.stream(). */
|
|
@@ -359,10 +383,11 @@ export class Client {
|
|
|
359
383
|
this.opts.onMetric?.(ev)
|
|
360
384
|
}
|
|
361
385
|
|
|
362
|
-
/** Emit the terminal `run` metric event and build the RunResult.
|
|
363
|
-
|
|
386
|
+
/** Emit the terminal `run` metric event and build the RunResult. `exhausted` marks a
|
|
387
|
+
* maxTurns loop exit — with no final text that is a LOUD "incomplete", never "done" (§7D). */
|
|
388
|
+
private endRun(runStart: number, text: string, messages: any[], toolCalls: ToolCallRecord[], turns: number, usage: Usage, exhausted = false): RunResult {
|
|
364
389
|
this.emit({ event: "run", model: this.opts.model, turns, toolCalls: toolCalls.length, totalTokens: usage.totalTokens, ms: Date.now() - runStart })
|
|
365
|
-
return this.result(text, messages, toolCalls, turns, usage)
|
|
390
|
+
return this.result(text, messages, toolCalls, turns, usage, exhausted)
|
|
366
391
|
}
|
|
367
392
|
|
|
368
393
|
/** Emit a `run` error metric event (once, on a thrown run). */
|
|
@@ -439,21 +464,29 @@ export class Client {
|
|
|
439
464
|
return ctrl.signal
|
|
440
465
|
}
|
|
441
466
|
|
|
442
|
-
/** fetch with retry + exponential backoff on 429/5xx/network, honoring Retry-After; aborts via signal.
|
|
467
|
+
/** fetch with retry + exponential backoff on 429/5xx/network, honoring Retry-After; aborts via signal.
|
|
468
|
+
* The retry-vs-fail decision is the host's `onError` (default: retryable-within-budget ⇒ retry). */
|
|
443
469
|
private async llmFetch(url: string, init: RequestInit, signal: AbortSignal): Promise<Response> {
|
|
444
470
|
const retries = this.opts.retries ?? 2
|
|
445
471
|
const base = this.opts.retryBaseMs ?? 500
|
|
472
|
+
// Default classifier = today's behavior, expressed as an onError. A "retry" is always
|
|
473
|
+
// capped by the budget below (attempt === retries stops regardless of the classifier).
|
|
474
|
+
const classify = this.opts.onError ?? ((info: ErrorInfo): ErrorTier => (info.retryable ? "retry" : "fail"))
|
|
446
475
|
let lastErr: unknown
|
|
447
476
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
448
477
|
try {
|
|
449
478
|
const res = await (this.opts.fetch ?? fetch)(url, { ...init, signal })
|
|
450
|
-
if (res.ok
|
|
479
|
+
if (res.ok) return res
|
|
480
|
+
const retryable = RETRYABLE.has(res.status)
|
|
481
|
+
const tier = classify({ status: res.status, attempt, retryable })
|
|
482
|
+
if (tier === "fail" || attempt === retries) return res // caller surfaces the non-ok status
|
|
451
483
|
const ra = Number(res.headers.get("retry-after"))
|
|
452
484
|
await delay(ra ? ra * 1000 : base * 2 ** attempt + Math.random() * 100, signal)
|
|
453
485
|
} catch (e) {
|
|
454
486
|
if (signal.aborted) throw e // abort/timeout: don't retry
|
|
455
487
|
lastErr = e
|
|
456
|
-
|
|
488
|
+
const tier = classify({ error: e, attempt, retryable: true })
|
|
489
|
+
if (tier === "fail" || attempt === retries) throw e
|
|
457
490
|
await delay(base * 2 ** attempt + Math.random() * 100, signal)
|
|
458
491
|
}
|
|
459
492
|
}
|
|
@@ -568,15 +601,18 @@ export class Client {
|
|
|
568
601
|
if (s.halted) return this.pendingRun(runStart, s.halted, messages, toolCalls, turns, usage)
|
|
569
602
|
}
|
|
570
603
|
}
|
|
571
|
-
return this.endRun(runStart, lastText(messages), messages, toolCalls, turns, usage)
|
|
604
|
+
return this.endRun(runStart, lastText(messages), messages, toolCalls, turns, usage, true)
|
|
572
605
|
} catch (e) {
|
|
573
606
|
this.emitRunError(runStart, toolCalls, turns, usage, e)
|
|
574
607
|
throw e
|
|
575
608
|
}
|
|
576
609
|
}
|
|
577
610
|
|
|
578
|
-
private result(text: string, messages: any[], toolCalls: ToolCallRecord[], turns: number, usage: Usage): RunResult {
|
|
579
|
-
|
|
611
|
+
private result(text: string, messages: any[], toolCalls: ToolCallRecord[], turns: number, usage: Usage, exhausted = false): RunResult {
|
|
612
|
+
const incomplete = exhausted && text === ""
|
|
613
|
+
const r: RunResult = { text, messages, toolCalls, toolCallCount: toolCalls.length, turns, usage, model: this.opts.model, status: incomplete ? "incomplete" : "done" }
|
|
614
|
+
if (incomplete) r.limit = "maxTurns"
|
|
615
|
+
return r
|
|
580
616
|
}
|
|
581
617
|
|
|
582
618
|
/** §10: a run halted because a tool suspended and no `waitFor` was configured. */
|
|
@@ -674,7 +710,7 @@ export class Client {
|
|
|
674
710
|
messages.push({ role: "user", content })
|
|
675
711
|
if (halted) return this.pendingRun(runStart, halted, messages, toolCalls, turns, usage)
|
|
676
712
|
}
|
|
677
|
-
return this.endRun(runStart, "", messages, toolCalls, turns, usage)
|
|
713
|
+
return this.endRun(runStart, "", messages, toolCalls, turns, usage, true)
|
|
678
714
|
} catch (e) {
|
|
679
715
|
this.emitRunError(runStart, toolCalls, turns, usage, e)
|
|
680
716
|
throw e
|
|
@@ -770,7 +806,7 @@ export class Client {
|
|
|
770
806
|
yield { type: "tool_result", id: c.id, name: c.name, output: result.output, isError: result.isError }
|
|
771
807
|
}
|
|
772
808
|
}
|
|
773
|
-
yield { type: "done", result: this.endRun(runStart, lastText(messages), messages, toolCalls, turns, usage) }
|
|
809
|
+
yield { type: "done", result: this.endRun(runStart, lastText(messages), messages, toolCalls, turns, usage, true) }
|
|
774
810
|
} catch (e) {
|
|
775
811
|
this.emitRunError(runStart, toolCalls, turns, usage, e)
|
|
776
812
|
throw e
|
|
@@ -863,7 +899,7 @@ export class Client {
|
|
|
863
899
|
}
|
|
864
900
|
messages.push({ role: "user", content: results })
|
|
865
901
|
}
|
|
866
|
-
yield { type: "done", result: this.endRun(runStart, "", messages, toolCalls, turns, usage) }
|
|
902
|
+
yield { type: "done", result: this.endRun(runStart, "", messages, toolCalls, turns, usage, true) }
|
|
867
903
|
} catch (e) {
|
|
868
904
|
this.emitRunError(runStart, toolCalls, turns, usage, e)
|
|
869
905
|
throw e
|
package/src/index.ts
CHANGED
package/src/toolkit.ts
CHANGED
|
@@ -21,6 +21,13 @@ export interface ToolkitOptions {
|
|
|
21
21
|
skillProvider?: () => SkillDef[] | Promise<SkillDef[]>
|
|
22
22
|
/** Per-agent skill allowlist keyed on name; same semantics as the MCP tools filter (§3, S2). */
|
|
23
23
|
skillsFilter?: Record<string, boolean>
|
|
24
|
+
/** Drop tools by their FINAL exposed name across every source (MCP `server_tool`, builtins,
|
|
25
|
+
* native, a2a, extras), applied after aggregation — the simple "just turn these off" list.
|
|
26
|
+
* Empty/undefined ⇒ nothing disabled. Composes with builtins/skillsFilter/server allowlists. */
|
|
27
|
+
disableTools?: string[]
|
|
28
|
+
/** Drop skills by name from the `skill` catalog — sugar over a drop-list in `skillsFilter`
|
|
29
|
+
* (each name folded in as `false`, without overriding an explicit filter entry). */
|
|
30
|
+
disableSkills?: string[]
|
|
24
31
|
/** Sibling-file sample cap: 0 ⇒ default 10, n>0 ⇒ cap, -1 ⇒ omit <skill_files> (§3, S5). */
|
|
25
32
|
skillSampleLimit?: number
|
|
26
33
|
extraTools?: Tool[]
|
|
@@ -52,14 +59,18 @@ export class Toolkit {
|
|
|
52
59
|
private readonly a2aConfig: A2AConfig | undefined,
|
|
53
60
|
/** MCP serve profile read from a top-level `mcpServer` config block. */
|
|
54
61
|
private readonly mcpServerConfig: MCPServeConfig | undefined,
|
|
62
|
+
/** Final-name drop-list (§4A) applied across every source after aggregation. */
|
|
63
|
+
disableTools: string[] = [],
|
|
55
64
|
) {
|
|
56
65
|
// Builtins are the lowest-precedence source: a host extraTools entry with
|
|
57
66
|
// the same name shadows a builtin (SPEC §4). Drop shadowed builtins up
|
|
58
67
|
// front, then apply the normal first-wins dedupe for MCP/skill/agents/extras.
|
|
59
68
|
const extraNames = new Set(extraTools.map((t) => t.name))
|
|
60
69
|
const activeBuiltins = builtins.filter((b) => !extraNames.has(b.name))
|
|
70
|
+
const disabled = new Set(disableTools)
|
|
61
71
|
const all = [...(mcp?.tools ?? []), ...(skill ? [skill.tool] : []), ...activeBuiltins, ...agents, ...extraTools]
|
|
62
72
|
for (const t of all) {
|
|
73
|
+
if (disabled.has(t.name)) continue // §4A drop-list by final exposed name
|
|
63
74
|
if (this.byName.has(t.name)) {
|
|
64
75
|
console.warn(`[toolnexus] duplicate tool name "${t.name}" — keeping first`)
|
|
65
76
|
continue
|
|
@@ -85,10 +96,16 @@ export class Toolkit {
|
|
|
85
96
|
}
|
|
86
97
|
}
|
|
87
98
|
const dataDefs = [...(opts.skills ?? []), ...providerDefs]
|
|
99
|
+
// disableSkills is sugar over a skillsFilter drop-list: fold each name in as false
|
|
100
|
+
// WITHOUT overriding an explicit skillsFilter entry (explicit entries win).
|
|
101
|
+
const filter =
|
|
102
|
+
opts.disableSkills && opts.disableSkills.length > 0
|
|
103
|
+
? { ...Object.fromEntries(opts.disableSkills.map((n) => [n, false])), ...opts.skillsFilter }
|
|
104
|
+
: opts.skillsFilter
|
|
88
105
|
skill = loadSkills({
|
|
89
106
|
dirs: opts.skillsDir,
|
|
90
107
|
skills: dataDefs.length > 0 ? dataDefs : undefined,
|
|
91
|
-
filter
|
|
108
|
+
filter,
|
|
92
109
|
sampleLimit: opts.skillSampleLimit,
|
|
93
110
|
})
|
|
94
111
|
}
|
|
@@ -137,7 +154,7 @@ export class Toolkit {
|
|
|
137
154
|
mcpServerConfig = (opts.mcpConfig as { mcpServer?: MCPServeConfig }).mcpServer
|
|
138
155
|
}
|
|
139
156
|
|
|
140
|
-
return new Toolkit(mcp, skill, builtins, agentToolsFlat, opts.extraTools ?? [], a2aConfig, mcpServerConfig)
|
|
157
|
+
return new Toolkit(mcp, skill, builtins, agentToolsFlat, opts.extraTools ?? [], a2aConfig, mcpServerConfig, opts.disableTools ?? [])
|
|
141
158
|
}
|
|
142
159
|
|
|
143
160
|
tools(): Tool[] {
|