toolnexus 0.28.0 → 0.29.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 +55 -15
- package/dist/client.d.ts +3 -5
- package/dist/client.js +17 -7
- package/dist/media.d.ts +465 -54
- package/dist/media.js +1819 -230
- package/dist/wire.d.ts +11 -1
- package/dist/wire.js +20 -3
- package/package.json +1 -1
- package/src/client.ts +12 -7
- package/src/media.ts +2034 -271
- package/src/wire.ts +19 -2
package/README.md
CHANGED
|
@@ -177,27 +177,67 @@ const rs = await c.states("a", "b").evaluate(judge.noul("q", "…")) // [{
|
|
|
177
177
|
|
|
178
178
|
## Media generation
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
Images, video, speech, music, transcription, voice cloning and talking-head video, with **one
|
|
181
|
+
client per media object**. The quickest start is a **CLI preset**: `MediaStyle.XaiGrokCli`, `MediaStyle.OpenAICodexCli` and `MediaStyle.GoogleAgyCli` drive the vendor's
|
|
182
|
+
official CLI (`grok`, `codex`, `agy`) in its documented headless mode (`grok -p`, `codex exec`,
|
|
183
|
+
`agy -p`), signed in by you. Grok does image and video; Codex and Antigravity do image. Each preset
|
|
184
|
+
has a mock twin (`MediaStyle.XaiGrokCliMock`, …) for tests. Your provider's terms of service apply to how you use its CLI.
|
|
185
|
+
Every Grok image carries a small visible **"Grok" mark in the bottom-right corner**; toolnexus does
|
|
186
|
+
not remove it. Install, sign-in, Grok video, limits and models:
|
|
187
|
+
<https://muthuishere.github.io/toolnexus/cookbook/cli-media-setup/>. SPEC §7G; guide: <https://muthuishere.github.io/toolnexus/media/>.
|
|
186
188
|
|
|
187
189
|
```ts
|
|
188
|
-
import {
|
|
190
|
+
import { generateMedia, createMedia, loadMediaStyle, createToolkit, MediaStyle } from "toolnexus"
|
|
189
191
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const img = await media.image({ prompt: "a red fox in fresh snow", width: 1024, height: 768 })
|
|
194
|
-
const vid = await media.video({ prompt: "the fox blinks", image: img.files[0].url, seconds: 5 })
|
|
192
|
+
// one line
|
|
193
|
+
const r = await generateMedia(MediaStyle.XaiGrokCli, "image", { prompt: "a red apple", aspectRatio: "16:9" })
|
|
194
|
+
console.log(r.files[0].path)
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
196
|
+
// or keep a media object, and give it to your agent
|
|
197
|
+
const media = createMedia(loadMediaStyle(MediaStyle.XaiGrokCli))
|
|
198
|
+
const clip = await media.video({ prompt: "the apple rolls off the table" })
|
|
199
|
+
const tk = await createToolkit({ media }) // generate_image, generate_video (source "media")
|
|
199
200
|
```
|
|
200
201
|
|
|
202
|
+
For an HTTP API, give `createMedia(client)` one named client — `OpenAIMediaClient`, `OpenRouterMediaClient`, `FalMediaClient`, `ElevenLabsMediaClient` or `A2EMediaClient`. Each reads its key from its env
|
|
203
|
+
var (`OPENAI_API_KEY`, `FAL_API_KEY`, `ELEVENLABS_API_KEY`, …) at call time and never logs it.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { createMedia, ElevenLabsMediaClient } from "toolnexus"
|
|
207
|
+
|
|
208
|
+
const voice = createMedia(new ElevenLabsMediaClient({ gather: { mode: "url" } })) // speech, music, transcribe, cloneVoice
|
|
209
|
+
const say = await voice.speech({ text: "Hello from the fox." })
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- **Typed verbs** — `image`, `video`, `speech`, `music`, `transcribe`, `cloneVoice`, `talk`, each
|
|
213
|
+
`(args & { signal?, deadline?, progress?, providerOptions? }) => Promise<MediaResult>`;
|
|
214
|
+
`generate(verb, args, ctx)` is the raw call. `MediaResult { files, text?, jobId?, status, usage? }`
|
|
215
|
+
— `usage` (`inputTokens`, `outputTokens`, `costUsd`, `raw`) only as the provider reported it
|
|
216
|
+
(OpenAI images, OpenRouter, Grok's `raw.costUsdTicks` unconverted), also on the tool's `metadata.usage`.
|
|
217
|
+
Args are checked per client: a client that does not accept an arg (e.g. `seconds` on
|
|
218
|
+
the Grok CLI preset, which takes `prompt` and size hints only) or lacks a required one fails with
|
|
219
|
+
`unavailable`; a key that is not a canonical arg of the verb fails at once.
|
|
220
|
+
- **Config** (every built-in client, one shape): `model`, `models: { [verb]: model }`, `key`, `url`,
|
|
221
|
+
`command` (CLI), `loop: { intervalMs, timeoutMs, onProgress }` (timeout default 10 min),
|
|
222
|
+
`gather: { mode: "url" | "download" | "inline", dir, maxBytes, keep }` (default `download` into
|
|
223
|
+
`<tmp>/toolnexus-media`, 100 MiB cap), `fetch` (injected transport).
|
|
224
|
+
- **Agents** — `createToolkit({ media })` (or `media.tools()`); produced files land in
|
|
225
|
+
`RunResult.files` (`{ type, mimeType, url?, data?, path?, from, callId }` — `path` is the saved
|
|
226
|
+
local file; it is never sent to the model). Only **image** parts are sent
|
|
227
|
+
back to the model; audio/video stay on `RunResult.files`.
|
|
228
|
+
- **Your own provider** — implement `MediaClient` (`name`, `supports(verb)`, optional `args(verb)`,
|
|
229
|
+
any of the seven verb methods); throw `MediaUnavailableError` when it cannot serve a call (no key, no credits).
|
|
230
|
+
Advanced: `styleMediaClient(loadMediaStyle("my-style.json"))` turns a JSON style into a client.
|
|
231
|
+
- **Errors** — a typed verb throws `MediaError` (`status`: `failed` | `timeout` | `cancelled` |
|
|
232
|
+
`unavailable`); a media tool never throws (`isError: true`).
|
|
233
|
+
|
|
234
|
+
Run it: `npm run build && npm run example:media`. It uses whatever is present, in order: the `grok`
|
|
235
|
+
and `codex` CLIs, `ELEVENLABS_API_KEY`, then `OPENAI_API_KEY`, `FAL_API_KEY`,
|
|
236
|
+
`A2E_API_KEY`; makes an image, ElevenLabs speech (`MEDIA_VOICE` picks the voice) and calls the
|
|
237
|
+
`generate_image` tool; `MEDIA_VIDEO=1` adds a Grok video, `MEDIA_PAID=1` paid video/talk. It prints
|
|
238
|
+
client, verb, path or URL, mime type, size and usage per file, exits non-zero if a step fails, and
|
|
239
|
+
exits 0 with a hint when nothing is available.
|
|
240
|
+
|
|
201
241
|
## MCP 2026-07-28 + OAuth
|
|
202
242
|
|
|
203
243
|
`loadMcp` / `Toolkit.create({ mcpConfig })` now speak MCP **2026-07-28** first, with no MCP SDK on
|
package/dist/client.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified LLM client — the host loop. Give it a plain base URL + a style
|
|
3
|
-
* ("openai" | "anthropic"), and it runs the tool-calling agent loop against a
|
|
4
|
-
* Toolkit. See ../../SPEC.md §8.
|
|
5
|
-
*/
|
|
6
1
|
import type { Toolkit } from "./toolkit.js";
|
|
7
2
|
import type { ToolHints, ToolResult, Request, Answer } from "./types.js";
|
|
8
3
|
import { type PromptCacheOptions } from "./promptcache.js";
|
|
@@ -368,9 +363,12 @@ export type RunFile = {
|
|
|
368
363
|
url?: string;
|
|
369
364
|
data?: string;
|
|
370
365
|
name?: string;
|
|
366
|
+
path?: string;
|
|
371
367
|
from: string;
|
|
372
368
|
callId: string;
|
|
373
369
|
};
|
|
370
|
+
/** RunResult.files: every non-text part of every tool call, in call order (§8). */
|
|
371
|
+
export declare function runFiles(toolCalls: ToolCallRecord[]): RunFile[];
|
|
374
372
|
/** Token usage, summed across every LLM round trip in the run. */
|
|
375
373
|
export interface Usage {
|
|
376
374
|
promptTokens: number;
|
package/dist/client.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified LLM client — the host loop. Give it a plain base URL + a style
|
|
3
|
+
* ("openai" | "anthropic"), and it runs the tool-calling agent loop against a
|
|
4
|
+
* Toolkit. See ../../SPEC.md §8.
|
|
5
|
+
*/
|
|
6
|
+
import { mediaPartPath } from "./media.js";
|
|
1
7
|
import { pendingOf } from "./types.js";
|
|
2
8
|
import { toOpenAI, toAnthropic } from "./adapters.js";
|
|
3
9
|
import { applyPromptCache } from "./promptcache.js";
|
|
4
10
|
import { buildToolPlan, toolSearch, unwrapCallTool } from "./tooltiers.js";
|
|
5
11
|
import { isRetryableStatus, retryAfterMs } from "./retry.js";
|
|
6
12
|
import { isConcurrencySafe, resolveMaxToolConcurrency, scheduleCalls } from "./toolconcurrency.js";
|
|
7
|
-
import { toOpenAIWire, toAnthropicWire, checkPromptParts } from "./wire.js";
|
|
13
|
+
import { toOpenAIWire, toAnthropicWire, checkPromptParts, modelParts } from "./wire.js";
|
|
8
14
|
import { finishReasonFor, argsString, openAIMessagesToAnthropic, openAIToolsToAnthropic, openAIToolChoiceToAnthropic, hasSystemMessage, } from "./translate.js";
|
|
9
15
|
/**
|
|
10
16
|
* The §8 CLIENT status vocabulary, as a value. It is a DIFFERENT, smaller closed set than the
|
|
@@ -139,13 +145,15 @@ function delay(ms, signal) {
|
|
|
139
145
|
/** Model tool-call id of each record, kept off the record itself (§8 shape has no id). */
|
|
140
146
|
const callIds = new WeakMap();
|
|
141
147
|
/** RunResult.files: every non-text part of every tool call, in call order (§8). */
|
|
142
|
-
function runFiles(toolCalls) {
|
|
148
|
+
export function runFiles(toolCalls) {
|
|
143
149
|
const out = [];
|
|
144
150
|
for (const r of toolCalls) {
|
|
145
151
|
for (const p of r.parts ?? []) {
|
|
146
152
|
if (p.type === "text")
|
|
147
153
|
continue;
|
|
148
|
-
|
|
154
|
+
// D20: a media file's local path rides on the run file, never on the part.
|
|
155
|
+
const local = mediaPartPath(p);
|
|
156
|
+
out.push({ ...p, ...(local !== undefined ? { path: local } : {}), from: r.name, callId: callIds.get(r) ?? "" });
|
|
149
157
|
}
|
|
150
158
|
}
|
|
151
159
|
return out;
|
|
@@ -1858,17 +1866,19 @@ function objArgs(v) {
|
|
|
1858
1866
|
*/
|
|
1859
1867
|
function toolMessage(id, name, result) {
|
|
1860
1868
|
const m = { role: "tool", tool_call_id: id, content: result.output };
|
|
1861
|
-
|
|
1869
|
+
const parts = modelParts(result);
|
|
1870
|
+
if (parts.length) {
|
|
1862
1871
|
m.name = name;
|
|
1863
|
-
m.parts =
|
|
1872
|
+
m.parts = parts;
|
|
1864
1873
|
}
|
|
1865
1874
|
return m;
|
|
1866
1875
|
}
|
|
1867
1876
|
/** A canonical Anthropic `tool_result` block; parts ride along and are emitted natively. */
|
|
1868
1877
|
function toolResultBlock(id, result) {
|
|
1869
1878
|
const b = { type: "tool_result", tool_use_id: id, content: result.output, is_error: result.isError };
|
|
1870
|
-
|
|
1871
|
-
|
|
1879
|
+
const parts = modelParts(result);
|
|
1880
|
+
if (parts.length)
|
|
1881
|
+
b.parts = parts;
|
|
1872
1882
|
return b;
|
|
1873
1883
|
}
|
|
1874
1884
|
const INTERRUPTED = { output: "interrupted: the turn ended before this tool returned. Whether its side effects happened is unknown. Do not re-run it automatically; check first.", isError: true };
|