toolnexus 0.21.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/agents/agent.d.ts +12 -1
- package/dist/agents/agent.js +43 -7
- package/dist/agents/compaction.d.ts +19 -0
- package/dist/agents/compaction.js +26 -4
- package/dist/agents/runtime.d.ts +5 -0
- package/dist/agents/runtime.js +13 -2
- package/dist/builtin.d.ts +16 -0
- package/dist/builtin.js +315 -17
- package/dist/builtin_extras.d.ts +80 -0
- package/dist/builtin_extras.js +248 -0
- package/dist/client.d.ts +77 -2
- package/dist/client.js +304 -61
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/mcp.d.ts +32 -1
- package/dist/mcp.js +184 -6
- package/dist/models.d.ts +20 -0
- package/dist/models.js +46 -0
- package/dist/promptcache.d.ts +21 -0
- package/dist/promptcache.js +59 -0
- package/dist/store.d.ts +32 -0
- package/dist/store.js +131 -0
- package/dist/toolkit.d.ts +2 -2
- package/dist/toolkit.js +2 -2
- package/dist/tooltiers.d.ts +49 -0
- package/dist/tooltiers.js +197 -0
- package/dist/types.d.ts +19 -0
- package/package.json +4 -3
- package/src/agents/agent.ts +39 -8
- package/src/agents/compaction.ts +45 -4
- package/src/agents/runtime.ts +16 -2
- package/src/builtin.ts +333 -18
- package/src/builtin_extras.ts +285 -0
- package/src/client.ts +330 -66
- package/src/index.ts +5 -0
- package/src/mcp.ts +204 -7
- package/src/models.ts +56 -0
- package/src/promptcache.ts +63 -0
- package/src/store.ts +142 -0
- package/src/toolkit.ts +2 -2
- package/src/tooltiers.ts +221 -0
- package/src/types.ts +20 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in builtins for coding-agent hosts (SPEC §4C): background shells (`bash`
|
|
3
|
+
* run_in_background + `bash_output` + `kill_shell`), `websearch` over a host-supplied provider,
|
|
4
|
+
* and `notebook_edit`. None is on by default: a host that sets nothing sees the same ten builtins.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
7
|
+
const err = (output, metadata) => ({ output, isError: true, metadata });
|
|
8
|
+
const ok = (output, metadata) => ({ output, isError: false, metadata });
|
|
9
|
+
/**
|
|
10
|
+
* Owns the commands `bash` started with `run_in_background`. Pass one as
|
|
11
|
+
* `builtins.background`, and call `closeAll()` when the host shuts down — a background command
|
|
12
|
+
* is NOT tied to the turn that started it, so nothing else stops it.
|
|
13
|
+
*/
|
|
14
|
+
export class BackgroundShells {
|
|
15
|
+
next = 0;
|
|
16
|
+
jobs = new Map();
|
|
17
|
+
/** @internal Start `command` under `argv` in its own process group. */
|
|
18
|
+
start(argv, command, workdir, grace) {
|
|
19
|
+
const child = spawn(argv[0], [...argv.slice(1), command], { cwd: workdir, detached: process.platform !== "win32" });
|
|
20
|
+
let resolveDone;
|
|
21
|
+
const done = new Promise((r) => (resolveDone = r));
|
|
22
|
+
const j = {
|
|
23
|
+
id: `bg${++this.next}`, command, started: new Date(), child, pgid: child.pid, grace,
|
|
24
|
+
out: "", read: 0, running: true, exitCode: 0, killed: false, done,
|
|
25
|
+
};
|
|
26
|
+
child.stdout?.on("data", (d) => (j.out += d.toString()));
|
|
27
|
+
child.stderr?.on("data", (d) => (j.out += d.toString()));
|
|
28
|
+
const finish = (code) => {
|
|
29
|
+
if (!j.running)
|
|
30
|
+
return;
|
|
31
|
+
j.running = false;
|
|
32
|
+
j.exitCode = code ?? -1;
|
|
33
|
+
resolveDone();
|
|
34
|
+
};
|
|
35
|
+
child.on("error", (e) => {
|
|
36
|
+
j.out += `bash: ${e.message}\n`;
|
|
37
|
+
finish(-1);
|
|
38
|
+
});
|
|
39
|
+
child.on("close", (code) => finish(code));
|
|
40
|
+
// A background job must never keep the host's event loop alive on its own.
|
|
41
|
+
child.unref();
|
|
42
|
+
child.stdout?.unref?.();
|
|
43
|
+
child.stderr?.unref?.();
|
|
44
|
+
this.jobs.set(j.id, j);
|
|
45
|
+
return j;
|
|
46
|
+
}
|
|
47
|
+
/** @internal */
|
|
48
|
+
get(id) {
|
|
49
|
+
return this.jobs.get(id);
|
|
50
|
+
}
|
|
51
|
+
signal(j, graceful) {
|
|
52
|
+
const pgid = j.pgid;
|
|
53
|
+
if (typeof pgid !== "number" || pgid <= 1 || !j.running)
|
|
54
|
+
return;
|
|
55
|
+
try {
|
|
56
|
+
if (process.platform === "win32")
|
|
57
|
+
spawnSync("taskkill", ["/T", "/F", "/PID", String(pgid)]);
|
|
58
|
+
else
|
|
59
|
+
process.kill(-pgid, graceful ? "SIGTERM" : "SIGKILL");
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* already gone */
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** @internal Stop the job's whole process group: SIGTERM, the grace window, SIGKILL.
|
|
66
|
+
* Resolves whether it was still running. */
|
|
67
|
+
async kill(j) {
|
|
68
|
+
if (!j.running)
|
|
69
|
+
return false;
|
|
70
|
+
j.killed = true;
|
|
71
|
+
// The child and its pipes are unref'd so an idle job never pins the host. While we are
|
|
72
|
+
// WAITING on it, though, they must be ref'd: otherwise, once the grace timer is cleared,
|
|
73
|
+
// nothing holds the event loop open until 'close' fires and Node exits with this promise
|
|
74
|
+
// still pending (seen on Linux, where 'close' lands an iteration later than on macOS).
|
|
75
|
+
j.child.ref();
|
|
76
|
+
j.child.stdout?.ref?.();
|
|
77
|
+
j.child.stderr?.ref?.();
|
|
78
|
+
this.signal(j, true);
|
|
79
|
+
let t;
|
|
80
|
+
const timedOut = await Promise.race([j.done.then(() => false), new Promise((r) => (t = setTimeout(() => r(true), j.grace)))]);
|
|
81
|
+
if (t)
|
|
82
|
+
clearTimeout(t);
|
|
83
|
+
if (timedOut) {
|
|
84
|
+
this.signal(j, false);
|
|
85
|
+
await j.done;
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
static info(j) {
|
|
90
|
+
return { id: j.id, command: j.command, started: j.started, running: j.running, exitCode: j.exitCode, killed: j.killed };
|
|
91
|
+
}
|
|
92
|
+
/** Every background command, oldest first. */
|
|
93
|
+
list() {
|
|
94
|
+
return [...this.jobs.values()].sort((a, b) => a.started.getTime() - b.started.getTime()).map(BackgroundShells.info);
|
|
95
|
+
}
|
|
96
|
+
/** Stop one background command's process group. Unknown id ⇒ throws. */
|
|
97
|
+
async killShell(id) {
|
|
98
|
+
const j = this.jobs.get(id);
|
|
99
|
+
if (!j)
|
|
100
|
+
throw new Error(`no background shell "${id}"`);
|
|
101
|
+
await this.kill(j);
|
|
102
|
+
}
|
|
103
|
+
/** Stop every background command still running, concurrently. */
|
|
104
|
+
async closeAll() {
|
|
105
|
+
await Promise.all([...this.jobs.values()].map((j) => this.kill(j)));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function bgStatus(i) {
|
|
109
|
+
if (i.running)
|
|
110
|
+
return "running";
|
|
111
|
+
if (i.killed)
|
|
112
|
+
return "killed";
|
|
113
|
+
return `exited with code ${i.exitCode}`;
|
|
114
|
+
}
|
|
115
|
+
/** `bash_output(id)`. */
|
|
116
|
+
export function bashOutput(bg, id) {
|
|
117
|
+
const j = bg.get(id);
|
|
118
|
+
if (!j)
|
|
119
|
+
return err(`bash_output: no background shell "${id}"`);
|
|
120
|
+
const fresh = j.out.slice(j.read);
|
|
121
|
+
j.read = j.out.length;
|
|
122
|
+
const info = BackgroundShells.info(j);
|
|
123
|
+
const status = bgStatus(info);
|
|
124
|
+
const meta = { id, status: status.split(" ")[0] };
|
|
125
|
+
if (!info.running && !info.killed)
|
|
126
|
+
meta.exitCode = info.exitCode;
|
|
127
|
+
return ok(`${fresh}\n[status: ${status}]`, meta);
|
|
128
|
+
}
|
|
129
|
+
/** `kill_shell(id)`. */
|
|
130
|
+
export async function killShell(bg, id) {
|
|
131
|
+
const j = bg.get(id);
|
|
132
|
+
if (!j)
|
|
133
|
+
return err(`kill_shell: no background shell "${id}"`);
|
|
134
|
+
if (!(await bg.kill(j))) {
|
|
135
|
+
return ok(`Background shell ${id} had already ${bgStatus(BackgroundShells.info(j))}`, { id, killed: false });
|
|
136
|
+
}
|
|
137
|
+
return ok(`Killed background shell ${id}`, { id, killed: true });
|
|
138
|
+
}
|
|
139
|
+
export async function runWebSearch(search, args, signal) {
|
|
140
|
+
const q = typeof args.query === "string" ? args.query : "";
|
|
141
|
+
if (!q.trim())
|
|
142
|
+
return err("websearch: query is required");
|
|
143
|
+
const limit = typeof args.limit === "number" && args.limit >= 1 ? Math.trunc(args.limit) : 5;
|
|
144
|
+
let results;
|
|
145
|
+
try {
|
|
146
|
+
results = await search(q, limit, { signal });
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
return err(`websearch: ${e instanceof Error ? e.message : String(e)}`);
|
|
150
|
+
}
|
|
151
|
+
results = (results ?? []).slice(0, limit);
|
|
152
|
+
if (results.length === 0)
|
|
153
|
+
return ok("No results.", { count: 0 });
|
|
154
|
+
const text = results
|
|
155
|
+
.map((r, i) => `${i + 1}. ${r.title}\n ${r.url}` + (r.snippet ? `\n ${r.snippet}` : ""))
|
|
156
|
+
.join("\n\n");
|
|
157
|
+
return ok(text, { count: results.length, results: results.map((r) => ({ title: r.title, url: r.url, snippet: r.snippet ?? "" })) });
|
|
158
|
+
}
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// notebook_edit
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
/** Split text into nbformat's list of lines, each keeping its "\n". */
|
|
163
|
+
export function notebookSource(s) {
|
|
164
|
+
if (!s)
|
|
165
|
+
return [];
|
|
166
|
+
return s.split(/(?<=\n)/).filter((p) => p !== "");
|
|
167
|
+
}
|
|
168
|
+
/** Go's json.MarshalIndent(v, "", " ") with sorted keys, plus Go's HTML escaping. */
|
|
169
|
+
export function goJsonIndent(v) {
|
|
170
|
+
const sort = (x) => {
|
|
171
|
+
if (Array.isArray(x))
|
|
172
|
+
return x.map(sort);
|
|
173
|
+
if (x && typeof x === "object") {
|
|
174
|
+
const out = {};
|
|
175
|
+
for (const k of Object.keys(x).sort())
|
|
176
|
+
out[k] = sort(x[k]);
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
return x;
|
|
180
|
+
};
|
|
181
|
+
return JSON.stringify(sort(v), null, " ")
|
|
182
|
+
.replace(/</g, "\\u003c")
|
|
183
|
+
.replace(/>/g, "\\u003e")
|
|
184
|
+
.replace(/&/g, "\\u0026")
|
|
185
|
+
.replace(new RegExp(String.fromCharCode(0x2028), "g"), "\\u2028")
|
|
186
|
+
.replace(new RegExp(String.fromCharCode(0x2029), "g"), "\\u2029");
|
|
187
|
+
}
|
|
188
|
+
/** Apply one notebook_edit to the parsed notebook, in place. Returns the summary, or an error result. */
|
|
189
|
+
export function editNotebook(nb, args, p) {
|
|
190
|
+
let cells = Array.isArray(nb.cells) ? nb.cells : [];
|
|
191
|
+
const mode = typeof args.edit_mode === "string" && args.edit_mode ? args.edit_mode : "replace";
|
|
192
|
+
let idx = -1;
|
|
193
|
+
if (typeof args.cell_id === "string" && args.cell_id) {
|
|
194
|
+
idx = cells.findIndex((c) => c && typeof c === "object" && c.id === args.cell_id);
|
|
195
|
+
if (idx < 0)
|
|
196
|
+
return err(`notebook_edit: no cell with id "${args.cell_id}"`);
|
|
197
|
+
}
|
|
198
|
+
else if (typeof args.cell_index === "number") {
|
|
199
|
+
idx = Math.trunc(args.cell_index);
|
|
200
|
+
if (idx < 0 || idx >= cells.length)
|
|
201
|
+
return err(`notebook_edit: cell_index ${idx} out of range (0-${cells.length - 1})`);
|
|
202
|
+
}
|
|
203
|
+
else if (mode !== "insert") {
|
|
204
|
+
return err("notebook_edit: cell_id or cell_index is required");
|
|
205
|
+
}
|
|
206
|
+
const src = typeof args.new_source === "string" ? args.new_source : "";
|
|
207
|
+
let cellType = typeof args.cell_type === "string" ? args.cell_type : "";
|
|
208
|
+
let summary;
|
|
209
|
+
switch (mode) {
|
|
210
|
+
case "replace": {
|
|
211
|
+
const cm = cells[idx];
|
|
212
|
+
cm.source = notebookSource(src);
|
|
213
|
+
if (cellType)
|
|
214
|
+
cm.cell_type = cellType;
|
|
215
|
+
if (cm.cell_type === "code") {
|
|
216
|
+
cm.outputs = [];
|
|
217
|
+
cm.execution_count = null;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
delete cm.outputs;
|
|
221
|
+
delete cm.execution_count;
|
|
222
|
+
}
|
|
223
|
+
summary = `Replaced cell ${idx} in ${p}`;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
case "insert": {
|
|
227
|
+
if (!cellType)
|
|
228
|
+
cellType = "code";
|
|
229
|
+
const cell = { cell_type: cellType, metadata: {}, source: notebookSource(src) };
|
|
230
|
+
if (cellType === "code") {
|
|
231
|
+
cell.outputs = [];
|
|
232
|
+
cell.execution_count = null;
|
|
233
|
+
}
|
|
234
|
+
const at = idx + 1;
|
|
235
|
+
cells = [...cells.slice(0, at), cell, ...cells.slice(at)];
|
|
236
|
+
summary = `Inserted ${cellType} cell at ${at} in ${p}`;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case "delete":
|
|
240
|
+
cells = [...cells.slice(0, idx), ...cells.slice(idx + 1)];
|
|
241
|
+
summary = `Deleted cell ${idx} from ${p}`;
|
|
242
|
+
break;
|
|
243
|
+
default:
|
|
244
|
+
return err(`notebook_edit: unknown edit_mode "${mode}"`);
|
|
245
|
+
}
|
|
246
|
+
nb.cells = cells;
|
|
247
|
+
return { summary };
|
|
248
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* Toolkit. See ../../SPEC.md §8.
|
|
5
5
|
*/
|
|
6
6
|
import type { Toolkit } from "./toolkit.js";
|
|
7
|
-
import type { ToolResult, Request, Answer } from "./types.js";
|
|
7
|
+
import type { ToolHints, ToolResult, Request, Answer } from "./types.js";
|
|
8
|
+
import { type PromptCacheOptions } from "./promptcache.js";
|
|
9
|
+
import { type ToolTier, type ToolTierOptions } from "./tooltiers.js";
|
|
8
10
|
import type { PromptInput, UnsupportedPartMode } from "./content.js";
|
|
9
11
|
import type { TranslateRequest, TranslateResult } from "./translate.js";
|
|
10
12
|
export type ClientStyle = "openai" | "anthropic";
|
|
@@ -12,7 +14,15 @@ export interface ClientOptions {
|
|
|
12
14
|
baseUrl: string;
|
|
13
15
|
style: ClientStyle;
|
|
14
16
|
model: string;
|
|
17
|
+
/** The key, used as-is. Absent => `apiKeyEnv`, else ONLY the env var that belongs to the
|
|
18
|
+
* `baseUrl` host over https: api.openai.com -> OPENAI_API_KEY, api.anthropic.com ->
|
|
19
|
+
* ANTHROPIC_API_KEY, openrouter.ai (and subdomains) -> OPENROUTER_API_KEY. Any other host (or
|
|
20
|
+
* plain http) needs `apiKey` or `apiKeyEnv` - a key is never sent to a host it does not belong
|
|
21
|
+
* to (SPEC 4C). Never logged. */
|
|
15
22
|
apiKey?: string;
|
|
23
|
+
/** The NAME of the env var to read the key from when `apiKey` is absent - for a host not in
|
|
24
|
+
* the table above. Read at call time; the value is never logged. */
|
|
25
|
+
apiKeyEnv?: string;
|
|
16
26
|
headers?: Record<string, string>;
|
|
17
27
|
systemPrompt?: string;
|
|
18
28
|
maxTurns?: number;
|
|
@@ -53,7 +63,19 @@ export interface ClientOptions {
|
|
|
53
63
|
* Replaying the leaf's stored transcript instead is DEFERRED to its own change; it would not
|
|
54
64
|
* remove this requirement anyway.
|
|
55
65
|
*/
|
|
56
|
-
|
|
66
|
+
/** The second argument carries the turn's abort signal (SPEC 4C), so a pending ask can end
|
|
67
|
+
* when the turn is cancelled: settle once `ctx.signal` aborts. */
|
|
68
|
+
waitFor?: (request: Request, ctx: {
|
|
69
|
+
signal: AbortSignal;
|
|
70
|
+
}) => Promise<Answer>;
|
|
71
|
+
/** Prompt caching (SPEC 8): anthropic `cache_control` breakpoints on the last tool, the system
|
|
72
|
+
* prompt and the latest message; openai `prompt_cache_key`. Applied before `requestParams`
|
|
73
|
+
* and `bodyTransform`, so both can still override it. Absent => body byte-identical. */
|
|
74
|
+
promptCache?: PromptCacheOptions;
|
|
75
|
+
/** Tool tiers (SPEC 8): send only core tool schemas plus a `tool_search` tool that loads the
|
|
76
|
+
* rest on demand (and `call_tool`). The plan is frozen per toolkit until `resetToolTiers`.
|
|
77
|
+
* Absent => every tool is sent, as before. */
|
|
78
|
+
toolTiers?: ToolTierOptions;
|
|
57
79
|
/** §8 Gap 1. Extra top-level keys shallow-merged into EVERY LLM body after the client builds
|
|
58
80
|
* its own — a requestParams key WINS on collision (e.g. max_tokens overrides the anthropic
|
|
59
81
|
* default). `messages`/`tools`/`stream` are forbidden here (stripped + warned) — rewrite
|
|
@@ -200,9 +222,21 @@ export interface ConversationStore {
|
|
|
200
222
|
/** Default conversation provider — keeps transcripts in memory for the client's lifetime. */
|
|
201
223
|
export declare class InMemoryConversationStore implements ConversationStore {
|
|
202
224
|
private readonly map;
|
|
225
|
+
private readonly updated;
|
|
203
226
|
get(id: string): Promise<any[] | undefined>;
|
|
204
227
|
save(id: string, messages: any[]): Promise<void>;
|
|
228
|
+
/** Every stored conversation, newest first (ties by id ascending). */
|
|
229
|
+
list(): Promise<ConversationEntry[]>;
|
|
230
|
+
/** Remove a conversation; absent ids are not an error. */
|
|
231
|
+
delete(id: string): Promise<void>;
|
|
205
232
|
}
|
|
233
|
+
/** One `list()` row of a shipped conversation store. */
|
|
234
|
+
export interface ConversationEntry {
|
|
235
|
+
id: string;
|
|
236
|
+
updatedAt: Date;
|
|
237
|
+
}
|
|
238
|
+
/** @internal Newest first, ties by id ascending (SPEC.md "Conversation memory"). */
|
|
239
|
+
export declare function sortEntries(entries: ConversationEntry[]): ConversationEntry[];
|
|
206
240
|
/**
|
|
207
241
|
* Lifecycle hooks around the agent loop. Each may observe, and (where noted)
|
|
208
242
|
* mutate or short-circuit. All may be async.
|
|
@@ -237,6 +271,7 @@ export interface Hooks {
|
|
|
237
271
|
args: Record<string, unknown>;
|
|
238
272
|
id?: string;
|
|
239
273
|
turn: number;
|
|
274
|
+
hints?: ToolHints;
|
|
240
275
|
}): void | {
|
|
241
276
|
args?: Record<string, unknown>;
|
|
242
277
|
result?: ToolResult;
|
|
@@ -270,6 +305,14 @@ export interface Usage {
|
|
|
270
305
|
promptTokens: number;
|
|
271
306
|
completionTokens: number;
|
|
272
307
|
totalTokens: number;
|
|
308
|
+
/** Prompt tokens served from the provider's prompt cache (anthropic cache_read_input_tokens;
|
|
309
|
+
* openai prompt_tokens_details.cached_tokens). Present once a provider reports any.
|
|
310
|
+
* Under anthropic `promptTokens` EXCLUDES cached tokens: the context sent is
|
|
311
|
+
* promptTokens + cacheReadTokens + cacheWriteTokens. Under openai `promptTokens` already
|
|
312
|
+
* includes cacheReadTokens. */
|
|
313
|
+
cacheReadTokens?: number;
|
|
314
|
+
/** Tokens written to the prompt cache (anthropic cache_creation_input_tokens). */
|
|
315
|
+
cacheWriteTokens?: number;
|
|
273
316
|
}
|
|
274
317
|
export interface RunResult {
|
|
275
318
|
text: string;
|
|
@@ -302,12 +345,15 @@ export type StreamEvent = {
|
|
|
302
345
|
id: string;
|
|
303
346
|
name: string;
|
|
304
347
|
args: Record<string, unknown>;
|
|
348
|
+
via?: "call_tool";
|
|
305
349
|
} | {
|
|
306
350
|
type: "tool_result";
|
|
307
351
|
id: string;
|
|
308
352
|
name: string;
|
|
309
353
|
output: string;
|
|
310
354
|
isError: boolean;
|
|
355
|
+
metadata?: Record<string, unknown>;
|
|
356
|
+
via?: "call_tool";
|
|
311
357
|
} | {
|
|
312
358
|
type: "usage";
|
|
313
359
|
usage: Usage;
|
|
@@ -318,12 +364,22 @@ export type StreamEvent = {
|
|
|
318
364
|
type: "done";
|
|
319
365
|
result: RunResult;
|
|
320
366
|
};
|
|
367
|
+
/**
|
|
368
|
+
* Pick the API key (SPEC 4C, security). An explicit `apiKey` is used as-is; else `apiKeyEnv`;
|
|
369
|
+
* else ONLY the env var that belongs to the `baseUrl` host. There is no cross-provider fallback:
|
|
370
|
+
* any other host needs `apiKey` or `apiKeyEnv`. The value is never logged.
|
|
371
|
+
*/
|
|
372
|
+
export declare function resolveKey(opts: Pick<ClientOptions, "apiKey" | "apiKeyEnv" | "baseUrl">): string;
|
|
373
|
+
/** The env var whose key may be sent to `baseUrl`'s host, or "" when none may. */
|
|
374
|
+
export declare function keyEnvForBaseUrl(baseUrl: string): string;
|
|
321
375
|
export declare class Client {
|
|
322
376
|
private readonly opts;
|
|
323
377
|
/** Conversation provider for `ask()` — from opts.store, else in-memory. */
|
|
324
378
|
private readonly store;
|
|
325
379
|
/** Cumulative Prometheus registry fed by the same events `onMetric` sees. */
|
|
326
380
|
private readonly registry;
|
|
381
|
+
/** The frozen ToolTiers plan per toolkit (SPEC 8 Tool tiers). */
|
|
382
|
+
private tierPlans;
|
|
327
383
|
constructor(opts: ClientOptions);
|
|
328
384
|
/**
|
|
329
385
|
* §8 Gap 4. The client's conversation store — the exact instance passed in `opts.store`, else
|
|
@@ -434,6 +490,25 @@ export declare class Client {
|
|
|
434
490
|
private llmFetch;
|
|
435
491
|
/** Run one tool through beforeTool/afterTool hooks (mutate args, short-circuit, transform result). */
|
|
436
492
|
private runTool;
|
|
493
|
+
private runToolDirect;
|
|
494
|
+
/** ToolTiers `call_tool {name, arguments}` -> the real call; undefined when tiers are off. */
|
|
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;
|
|
499
|
+
/** The stream's tool_call event, naming the real tool for a `call_tool` call. */
|
|
500
|
+
private callEvent;
|
|
501
|
+
private resultEvent;
|
|
502
|
+
private plan;
|
|
503
|
+
/** The tools array for a request: core tools (sorted) then tool_search and call_tool. Without
|
|
504
|
+
* tiers it is every tool, as before. */
|
|
505
|
+
private requestTools;
|
|
506
|
+
/** The frozen tier plan for `toolkit` (built on first use): tool name -> tier. Undefined when
|
|
507
|
+
* `toolTiers` is not set. For a host UI or log. */
|
|
508
|
+
toolTierPlan(toolkit: Toolkit): Record<string, ToolTier> | undefined;
|
|
509
|
+
/** Forget `toolkit`'s plan so the next request rebuilds it - call at a new session start.
|
|
510
|
+
* Mid-session it breaks the cached prefix. */
|
|
511
|
+
resetToolTiers(toolkit: Toolkit): void;
|
|
437
512
|
/** Emit the `tool` metric, classifying a §10 suspension as `pending` (isError:false), not a failure. */
|
|
438
513
|
private emitTool;
|
|
439
514
|
/** One non-streaming LLM call, with an `llm` metric event (ok/error + per-call tokens + ms). */
|