@spinabot/brigade 1.11.2 β 1.13.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 +56 -0
- package/dist/agents/tools/edge-tts.d.ts +44 -0
- package/dist/agents/tools/edge-tts.d.ts.map +1 -0
- package/dist/agents/tools/edge-tts.js +142 -0
- package/dist/agents/tools/edge-tts.js.map +1 -0
- package/dist/agents/tools/generate-music-tool.d.ts +61 -0
- package/dist/agents/tools/generate-music-tool.d.ts.map +1 -0
- package/dist/agents/tools/generate-music-tool.js +286 -0
- package/dist/agents/tools/generate-music-tool.js.map +1 -0
- package/dist/agents/tools/generate-speech-tool.d.ts +69 -0
- package/dist/agents/tools/generate-speech-tool.d.ts.map +1 -0
- package/dist/agents/tools/generate-speech-tool.js +331 -0
- package/dist/agents/tools/generate-speech-tool.js.map +1 -0
- package/dist/agents/tools/generate-video-tool.d.ts +111 -0
- package/dist/agents/tools/generate-video-tool.d.ts.map +1 -0
- package/dist/agents/tools/generate-video-tool.js +1028 -0
- package/dist/agents/tools/generate-video-tool.js.map +1 -0
- package/dist/agents/tools/media-command.d.ts +47 -0
- package/dist/agents/tools/media-command.d.ts.map +1 -0
- package/dist/agents/tools/media-command.js +93 -0
- package/dist/agents/tools/media-command.js.map +1 -0
- package/dist/agents/tools/registry.d.ts.map +1 -1
- package/dist/agents/tools/registry.js +27 -0
- package/dist/agents/tools/registry.js.map +1 -1
- package/dist/agents/tools/transcribe-audio-tool.d.ts +96 -0
- package/dist/agents/tools/transcribe-audio-tool.d.ts.map +1 -0
- package/dist/agents/tools/transcribe-audio-tool.js +577 -0
- package/dist/agents/tools/transcribe-audio-tool.js.map +1 -0
- package/dist/buildstamp.json +1 -1
- package/dist/cli/commands/connect.d.ts +6 -0
- package/dist/cli/commands/connect.d.ts.map +1 -1
- package/dist/cli/commands/connect.js +7 -0
- package/dist/cli/commands/connect.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +2 -1
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/expose.d.ts.map +1 -1
- package/dist/cli/commands/expose.js +22 -3
- package/dist/cli/commands/expose.js.map +1 -1
- package/dist/cli/commands/gateway.d.ts +12 -0
- package/dist/cli/commands/gateway.d.ts.map +1 -1
- package/dist/cli/commands/gateway.js +114 -2
- package/dist/cli/commands/gateway.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +2 -1
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/program/build-program.d.ts.map +1 -1
- package/dist/cli/program/build-program.js +36 -0
- package/dist/cli/program/build-program.js.map +1 -1
- package/dist/config/io.d.ts +13 -0
- package/dist/config/io.d.ts.map +1 -1
- package/dist/config/io.js.map +1 -1
- package/dist/core/gateway-auth.d.ts +86 -0
- package/dist/core/gateway-auth.d.ts.map +1 -0
- package/dist/core/gateway-auth.js +156 -0
- package/dist/core/gateway-auth.js.map +1 -0
- package/dist/core/gateway-probe.d.ts +5 -0
- package/dist/core/gateway-probe.d.ts.map +1 -1
- package/dist/core/gateway-probe.js +2 -1
- package/dist/core/gateway-probe.js.map +1 -1
- package/dist/core/gateway-spawn.d.ts.map +1 -1
- package/dist/core/gateway-spawn.js +5 -2
- package/dist/core/gateway-spawn.js.map +1 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +21 -1
- package/dist/core/server.js.map +1 -1
- package/dist/core/tunnel/auth-proxy.d.ts +3 -2
- package/dist/core/tunnel/auth-proxy.d.ts.map +1 -1
- package/dist/core/tunnel/auth-proxy.js +8 -34
- package/dist/core/tunnel/auth-proxy.js.map +1 -1
- package/dist/core/tunnel/manager.d.ts +4 -2
- package/dist/core/tunnel/manager.d.ts.map +1 -1
- package/dist/core/tunnel/manager.js +3 -2
- package/dist/core/tunnel/manager.js.map +1 -1
- package/dist/tui/client.d.ts +8 -0
- package/dist/tui/client.d.ts.map +1 -1
- package/dist/tui/client.js +5 -1
- package/dist/tui/client.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -119,6 +119,7 @@ backend β all under one `~/.brigade/` directory you fully own.
|
|
|
119
119
|
| π
**Always-on** | Run as a headless WebSocket gateway with a crash supervisor, cron jobs, and OS service install. |
|
|
120
120
|
| π¬ **Channels** | Talk to your crew from WhatsApp and Telegram today; the adapter contract is built for more. |
|
|
121
121
|
| π **1,000+ connectors** | Gmail, Slack, GitHub, Notion, Calendar, Linearβ¦ via the built-in Composio tool. |
|
|
122
|
+
| π **Reads & writes documents** | Send a PDF, Office doc, image, audio, or video and your crew *understands* it β then have it **create or edit** Word, Excel, PowerPoint, and PDF files and hand them back. |
|
|
122
123
|
| π§© **MCP** | Expose your long-term memory to any MCP client (`brigade mcp`), or connect MCP servers in. |
|
|
123
124
|
| ποΈ **Your storage** | Default filesystem mode, or an optional **fully self-hosted Convex** backend with at-rest encryption. |
|
|
124
125
|
| π **Yours** | Everything lives under `~/.brigade/`. Keys are stored locally at mode `0600`. `rm -rf ~/.brigade` wipes it clean. |
|
|
@@ -283,6 +284,17 @@ to **PLATFORM**, open **Settings β API Keys**, and copy the `ak_β¦` key. Hand
|
|
|
283
284
|
Brigade once (*"set my Composio key to `ak_β¦`"*) and it's verified and stored encrypted.
|
|
284
285
|
Full guide: **[docs/composio.md](docs/composio.md)**.
|
|
285
286
|
|
|
287
|
+
### π Documents & media
|
|
288
|
+
Send your crew a file and it actually **reads** it: the `analyze_media` tool
|
|
289
|
+
understands PDFs (text or scanned), Word / Excel / PowerPoint (including the images
|
|
290
|
+
embedded inside them), plain images, audio, and video β auto-selecting the right
|
|
291
|
+
provider and caching the result. The write side, `make_document` and
|
|
292
|
+
`edit_document`, **creates and edits** Word / Excel / PowerPoint / PDF in place:
|
|
293
|
+
fill templates and form fields, set cells and formulas, add charts, and
|
|
294
|
+
merge / split / stamp / watermark PDFs β all with pure-JS libraries, no sandbox.
|
|
295
|
+
Drop a document into a channel and Brigade sees it; ask for a report and it hands
|
|
296
|
+
one back with `send_media`.
|
|
297
|
+
|
|
286
298
|
### π§© MCP
|
|
287
299
|
Run `brigade mcp` to expose your long-term memory to any MCP client (Claude Desktop,
|
|
288
300
|
editors, etc.) as add/search/context tools over stdio, owner-bound.
|
|
@@ -294,6 +306,14 @@ by a per-agent approval allowlist (`brigade exec`). Secrets in config use `${VAR
|
|
|
294
306
|
references that resolve at read time and are never persisted resolved. Optional
|
|
295
307
|
**AES-256-GCM at-rest encryption** in Convex mode (`brigade encrypt`).
|
|
296
308
|
|
|
309
|
+
**Optional gateway tokens.** The gateway is unauthenticated and localhost-only by
|
|
310
|
+
default β ideal for a single machine. Want more? Add one or more access tokens
|
|
311
|
+
(`brigade gateway token new`) and every connection must then present a valid one
|
|
312
|
+
(`Authorization: Bearer`, `x-brigade-token`, or `?token=`). **Multiple tokens** are
|
|
313
|
+
supported, so you can hand a distinct token to each device and revoke one without
|
|
314
|
+
disturbing the rest, and the same tokens secure `brigade expose`. Entirely opt-in:
|
|
315
|
+
with no tokens configured, nothing changes.
|
|
316
|
+
|
|
297
317
|
### π Carrow β cross-model continuity
|
|
298
318
|
Switch models mid-conversation **without losing context**. Carrow carries the full
|
|
299
319
|
transcript onto the new model (it's the same session), **re-anchors your thinking
|
|
@@ -342,6 +362,7 @@ principle is the same: *independent verification, never the agent judging itself
|
|
|
342
362
|
| `brigade status` | Snapshot config, sessions, and gateway state (`--json`) |
|
|
343
363
|
| `brigade doctor` | Health-check Node, config, providers, prompts, logs, gateway (`--json`, `--strict`, `--gateway <url>`) |
|
|
344
364
|
| `brigade logs` | Tail today's gateway log (`--follow`) |
|
|
365
|
+
| `brigade update` Β· `upgrade` | Update Brigade to the latest code and restart the gateway (`--check`, `--no-restart`) |
|
|
345
366
|
|
|
346
367
|
### Gateway
|
|
347
368
|
|
|
@@ -351,6 +372,8 @@ principle is the same: *independent verification, never the agent judging itself
|
|
|
351
372
|
| `brigade gateway status` Β· `stop` Β· `restart` | Inspect / stop / restart the running gateway |
|
|
352
373
|
| `brigade gateway install` Β· `uninstall` | Install/remove as a system service (launchd / systemd / Task Scheduler) |
|
|
353
374
|
| `brigade gateway supervise` | Out-of-process crash watchdog (respawns a wedged gateway) |
|
|
375
|
+
| `brigade expose` Β· `expose stop` | Publish the gateway to the public internet via a secure, token-gated tunnel (alias: `bloody benchmark`) |
|
|
376
|
+
| `brigade gateway token new` Β· `list` Β· `add` Β· `revoke` | Manage **optional** access tokens (multiple supported). No tokens β the gateway stays unauthenticated + localhost-only |
|
|
354
377
|
|
|
355
378
|
### Agents
|
|
356
379
|
|
|
@@ -428,6 +451,10 @@ brigade gateway stop
|
|
|
428
451
|
brigade gateway restart
|
|
429
452
|
brigade gateway install # install as a system service (launchd / systemd / Task Scheduler)
|
|
430
453
|
brigade gateway supervise # out-of-process crash watchdog
|
|
454
|
+
|
|
455
|
+
brigade gateway token new # generate + store an access token (printed once)
|
|
456
|
+
brigade gateway token list # show configured tokens (masked) + whether auth is on
|
|
457
|
+
brigade gateway token revoke 1 # remove a token by its number (or its exact value)
|
|
431
458
|
```
|
|
432
459
|
|
|
433
460
|
| Flag | Default | Notes |
|
|
@@ -438,6 +465,13 @@ brigade gateway supervise # out-of-process crash watchdog
|
|
|
438
465
|
| `--quiet` | off | Suppress the live console stream |
|
|
439
466
|
| `--log-level X` | `info` | `debug` / `info` / `warn` / `error` |
|
|
440
467
|
|
|
468
|
+
**Optional authentication.** By default the gateway is unauthenticated and refuses to
|
|
469
|
+
bind anywhere but loopback. To require a token, run `brigade gateway token new` (repeat
|
|
470
|
+
for multiple tokens) and restart the gateway. On the same machine, clients read the
|
|
471
|
+
token from config automatically; elsewhere pass `--token <t>` or set
|
|
472
|
+
`BRIGADE_GATEWAY_TOKEN`. `BRIGADE_GATEWAY_TOKENS` (comma-separated) adds tokens via the
|
|
473
|
+
environment. Remove every token to return to open, localhost-only mode.
|
|
474
|
+
|
|
441
475
|
### `brigade connect`
|
|
442
476
|
|
|
443
477
|
Attaches a TUI to a running gateway. Same chat experience as `brigade`, but the
|
|
@@ -485,6 +519,27 @@ brigade doctor --json # machine-readable
|
|
|
485
519
|
brigade doctor --strict # exit 1 on warnings (CI mode)
|
|
486
520
|
```
|
|
487
521
|
|
|
522
|
+
### `brigade update`
|
|
523
|
+
|
|
524
|
+
Bring Brigade up to date and reload the gateway. It auto-detects how Brigade is
|
|
525
|
+
installed and does the right thing:
|
|
526
|
+
|
|
527
|
+
- **npm global** β `npm i -g @spinabot/brigade@latest`, then restart.
|
|
528
|
+
- **source checkout** β `git pull` (fast-forward, only when your tree is clean and
|
|
529
|
+
behind upstream β a dirty tree is left untouched and rebuilt as-is), then
|
|
530
|
+
`npm install`, `npm run build`, then restart.
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
brigade update # update + restart the gateway
|
|
534
|
+
brigade upgrade # alias of update
|
|
535
|
+
brigade update --check # report whether newer code is available; change nothing
|
|
536
|
+
brigade update --no-restart
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
If Brigade is installed as a background service (`brigade gateway install`) the
|
|
540
|
+
restart is automatic; if you run the gateway in the foreground, restart it yourself
|
|
541
|
+
to load the new code. Same behavior on macOS, Linux, and Windows.
|
|
542
|
+
|
|
488
543
|
### `brigade config`
|
|
489
544
|
|
|
490
545
|
Read and write the local config without opening the TUI.
|
|
@@ -605,6 +660,7 @@ Every agent gets a curated toolset. Mutating/privileged tools are owner-gated
|
|
|
605
660
|
- **Web:** `web_search`, `fetch_url`, `browser` (when a provider is configured)
|
|
606
661
|
- **Connectors:** `composio` (1,000+ apps), `oauth_authorize`
|
|
607
662
|
- **Generation:** `generate_image`
|
|
663
|
+
- **Documents & media:** `analyze_media` (read/understand PDF Β· Office Β· image Β· audio Β· video), `make_document` Β· `edit_document` (create & edit Word/Excel/PowerPoint/PDF)
|
|
608
664
|
- **Channels:** `send_message`, `send_media` (when a channel is linked)
|
|
609
665
|
|
|
610
666
|
---
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft Edge "Read Aloud" text-to-speech over WebSocket β FREE, no API key.
|
|
3
|
+
*
|
|
4
|
+
* This is the same free endpoint the `node-edge-tts` package uses (the Bing /
|
|
5
|
+
* "Read Aloud" TTS WebSocket). Auth is an embedded TrustedClientToken plus a
|
|
6
|
+
* computed `Sec-MS-GEC` token (a SHA-256 of the current Windows file-time ticks,
|
|
7
|
+
* floored to 5 minutes, concatenated with the token). The socket sends a
|
|
8
|
+
* `speech.config` frame then an `ssml` frame; audio arrives as binary WS frames
|
|
9
|
+
* (after a `Path:audio` header) and the turn ends on a `Path:turn.end` text
|
|
10
|
+
* frame. Returns MP3 bytes.
|
|
11
|
+
*
|
|
12
|
+
* Re-implemented self-contained (no new dependency) over the `ws` package that
|
|
13
|
+
* Brigade already ships for the gateway/TUI.
|
|
14
|
+
*/
|
|
15
|
+
/** Minimal WebSocket surface edge-tts uses β lets tests inject a fake socket. */
|
|
16
|
+
export interface EdgeWebSocketLike {
|
|
17
|
+
on(event: "open" | "message" | "error" | "close", cb: (...args: unknown[]) => void): void;
|
|
18
|
+
send(data: string): void;
|
|
19
|
+
close(): void;
|
|
20
|
+
}
|
|
21
|
+
export interface EdgeTtsOptions {
|
|
22
|
+
text: string;
|
|
23
|
+
voice: string;
|
|
24
|
+
outputFormat?: string;
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
/** Test seam: inject a WebSocket factory instead of opening a real socket. */
|
|
28
|
+
wsFactory?: (url: string, headers: Record<string, string>) => EdgeWebSocketLike;
|
|
29
|
+
}
|
|
30
|
+
/** Synthesize speech via the free Edge endpoint. Resolves with MP3 bytes. */
|
|
31
|
+
export declare function synthesizeEdge(opts: EdgeTtsOptions): Promise<Buffer>;
|
|
32
|
+
/**
|
|
33
|
+
* The `Sec-MS-GEC` auth token: uppercase SHA-256 hex of `${ticks}${TrustedClientToken}`,
|
|
34
|
+
* where `ticks` = Windows file time (100-ns intervals since 1601-01-01) floored to
|
|
35
|
+
* the nearest 5 minutes. BigInt math β the tick count exceeds Number.MAX_SAFE_INTEGER.
|
|
36
|
+
*/
|
|
37
|
+
export declare function secMsGec(nowMs?: number): string;
|
|
38
|
+
/** The opening `speech.config` frame carrying the requested output format. */
|
|
39
|
+
export declare function configFrame(outputFormat: string): string;
|
|
40
|
+
/** The `ssml` frame carrying the voice + escaped text. */
|
|
41
|
+
export declare function ssmlFrame(text: string, voice: string): string;
|
|
42
|
+
/** Minimal XML escaping for SSML text content. */
|
|
43
|
+
export declare function escapeXml(s: string): string;
|
|
44
|
+
//# sourceMappingURL=edge-tts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-tts.d.ts","sourceRoot":"","sources":["../../../src/agents/tools/edge-tts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAYH,iFAAiF;AACjF,MAAM,WAAW,iBAAiB;IACjC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAC1F,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,iBAAiB,CAAC;CAChF;AAED,6EAA6E;AAC7E,wBAAsB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAsE1E;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,GAAE,MAAmB,GAAG,MAAM,CAK3D;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAYxD;AAED,0DAA0D;AAC1D,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAM7D;AAED,kDAAkD;AAClD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAO3C"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft Edge "Read Aloud" text-to-speech over WebSocket β FREE, no API key.
|
|
3
|
+
*
|
|
4
|
+
* This is the same free endpoint the `node-edge-tts` package uses (the Bing /
|
|
5
|
+
* "Read Aloud" TTS WebSocket). Auth is an embedded TrustedClientToken plus a
|
|
6
|
+
* computed `Sec-MS-GEC` token (a SHA-256 of the current Windows file-time ticks,
|
|
7
|
+
* floored to 5 minutes, concatenated with the token). The socket sends a
|
|
8
|
+
* `speech.config` frame then an `ssml` frame; audio arrives as binary WS frames
|
|
9
|
+
* (after a `Path:audio` header) and the turn ends on a `Path:turn.end` text
|
|
10
|
+
* frame. Returns MP3 bytes.
|
|
11
|
+
*
|
|
12
|
+
* Re-implemented self-contained (no new dependency) over the `ws` package that
|
|
13
|
+
* Brigade already ships for the gateway/TUI.
|
|
14
|
+
*/
|
|
15
|
+
import crypto from "node:crypto";
|
|
16
|
+
import { WebSocket } from "ws";
|
|
17
|
+
const TRUSTED_TOKEN = "6A5AA1D4EAFF4E9FB37E23D68491D6F4";
|
|
18
|
+
const GEC_VERSION = "1-131.0.2903.86";
|
|
19
|
+
const WSS_BASE = "wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1";
|
|
20
|
+
const CHROME_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0";
|
|
21
|
+
/** Synthesize speech via the free Edge endpoint. Resolves with MP3 bytes. */
|
|
22
|
+
export async function synthesizeEdge(opts) {
|
|
23
|
+
const outputFormat = opts.outputFormat ?? "audio-24khz-48kbitrate-mono-mp3";
|
|
24
|
+
const url = `${WSS_BASE}?TrustedClientToken=${TRUSTED_TOKEN}&Sec-MS-GEC=${secMsGec()}&Sec-MS-GEC-Version=${GEC_VERSION}`;
|
|
25
|
+
const headers = {
|
|
26
|
+
"User-Agent": CHROME_UA,
|
|
27
|
+
Origin: "chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold",
|
|
28
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
29
|
+
};
|
|
30
|
+
const ws = opts.wsFactory
|
|
31
|
+
? opts.wsFactory(url, headers)
|
|
32
|
+
: new WebSocket(url, { headers });
|
|
33
|
+
return await new Promise((resolve, reject) => {
|
|
34
|
+
const chunks = [];
|
|
35
|
+
let settled = false;
|
|
36
|
+
const timer = setTimeout(() => fail(new Error("Edge TTS timed out")), opts.timeoutMs ?? 30_000);
|
|
37
|
+
const onAbort = () => fail(new Error("aborted"));
|
|
38
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
39
|
+
const cleanup = () => {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
42
|
+
try {
|
|
43
|
+
ws.close();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
/* ignore */
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function fail(err) {
|
|
50
|
+
if (settled)
|
|
51
|
+
return;
|
|
52
|
+
settled = true;
|
|
53
|
+
cleanup();
|
|
54
|
+
reject(err);
|
|
55
|
+
}
|
|
56
|
+
function done() {
|
|
57
|
+
if (settled)
|
|
58
|
+
return;
|
|
59
|
+
settled = true;
|
|
60
|
+
cleanup();
|
|
61
|
+
if (chunks.length === 0)
|
|
62
|
+
reject(new Error("Edge TTS produced no audio"));
|
|
63
|
+
else
|
|
64
|
+
resolve(Buffer.concat(chunks));
|
|
65
|
+
}
|
|
66
|
+
ws.on("open", () => {
|
|
67
|
+
ws.send(configFrame(outputFormat));
|
|
68
|
+
ws.send(ssmlFrame(opts.text, opts.voice));
|
|
69
|
+
});
|
|
70
|
+
ws.on("message", (...args) => {
|
|
71
|
+
const data = args[0];
|
|
72
|
+
const isBinary = args[1] === true;
|
|
73
|
+
const buf = Buffer.isBuffer(data)
|
|
74
|
+
? data
|
|
75
|
+
: data instanceof ArrayBuffer
|
|
76
|
+
? Buffer.from(data)
|
|
77
|
+
: Buffer.from(String(data), "utf8");
|
|
78
|
+
if (isBinary) {
|
|
79
|
+
// Binary frame: first 2 bytes = big-endian header length; audio follows.
|
|
80
|
+
if (buf.length < 2)
|
|
81
|
+
return;
|
|
82
|
+
const headerLen = buf.readUInt16BE(0);
|
|
83
|
+
const header = buf.subarray(2, 2 + headerLen).toString("utf8");
|
|
84
|
+
if (header.includes("Path:audio"))
|
|
85
|
+
chunks.push(buf.subarray(2 + headerLen));
|
|
86
|
+
}
|
|
87
|
+
else if (buf.toString("utf8").includes("Path:turn.end")) {
|
|
88
|
+
done();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
ws.on("error", (...args) => {
|
|
92
|
+
const e = args[0];
|
|
93
|
+
fail(e instanceof Error ? e : new Error(String(e)));
|
|
94
|
+
});
|
|
95
|
+
ws.on("close", () => {
|
|
96
|
+
if (!settled)
|
|
97
|
+
done();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The `Sec-MS-GEC` auth token: uppercase SHA-256 hex of `${ticks}${TrustedClientToken}`,
|
|
103
|
+
* where `ticks` = Windows file time (100-ns intervals since 1601-01-01) floored to
|
|
104
|
+
* the nearest 5 minutes. BigInt math β the tick count exceeds Number.MAX_SAFE_INTEGER.
|
|
105
|
+
*/
|
|
106
|
+
export function secMsGec(nowMs = Date.now()) {
|
|
107
|
+
const secondsSince1601 = BigInt(Math.floor(nowMs / 1000) + 11_644_473_600);
|
|
108
|
+
const roundedSeconds = (secondsSince1601 / 300n) * 300n;
|
|
109
|
+
const ticks = roundedSeconds * 10000000n;
|
|
110
|
+
return crypto.createHash("sha256").update(`${ticks}${TRUSTED_TOKEN}`).digest("hex").toUpperCase();
|
|
111
|
+
}
|
|
112
|
+
/** The opening `speech.config` frame carrying the requested output format. */
|
|
113
|
+
export function configFrame(outputFormat) {
|
|
114
|
+
const cfg = {
|
|
115
|
+
context: {
|
|
116
|
+
synthesis: {
|
|
117
|
+
audio: {
|
|
118
|
+
metadataoptions: { sentenceBoundaryEnabled: false, wordBoundaryEnabled: false },
|
|
119
|
+
outputFormat,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
return `X-Timestamp:${new Date().toString()}\r\nContent-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n${JSON.stringify(cfg)}`;
|
|
125
|
+
}
|
|
126
|
+
/** The `ssml` frame carrying the voice + escaped text. */
|
|
127
|
+
export function ssmlFrame(text, voice) {
|
|
128
|
+
const id = crypto.randomUUID().replace(/-/g, "");
|
|
129
|
+
const ssml = `<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='en-US'>` +
|
|
130
|
+
`<voice name='${voice}'><prosody pitch='+0Hz' rate='+0%' volume='+0%'>${escapeXml(text)}</prosody></voice></speak>`;
|
|
131
|
+
return `X-RequestId:${id}\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:${new Date().toString()}Z\r\nPath:ssml\r\n\r\n${ssml}`;
|
|
132
|
+
}
|
|
133
|
+
/** Minimal XML escaping for SSML text content. */
|
|
134
|
+
export function escapeXml(s) {
|
|
135
|
+
return s
|
|
136
|
+
.replace(/&/g, "&")
|
|
137
|
+
.replace(/</g, "<")
|
|
138
|
+
.replace(/>/g, ">")
|
|
139
|
+
.replace(/'/g, "'")
|
|
140
|
+
.replace(/"/g, """);
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=edge-tts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-tts.js","sourceRoot":"","sources":["../../../src/agents/tools/edge-tts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `generate_music` tool β text-to-music generation, modeled on the proven
|
|
3
|
+
* `generate_speech` self-contained pattern.
|
|
4
|
+
*
|
|
5
|
+
* Why this tool exists
|
|
6
|
+
* --------------------
|
|
7
|
+
* Same reasoning as `generate_speech`/`generate_image`: without a first-class
|
|
8
|
+
* tool, "make a song" / "compose background music" sends the model to raw
|
|
9
|
+
* `curl` against a music API β the key flows through a shell, the (binary or
|
|
10
|
+
* base64) audio response gets mangled by a text-only parser, and a billed
|
|
11
|
+
* generation is dropped. This tool owns the call in-process: stored auth,
|
|
12
|
+
* validated params, a parser that understands each provider's audio shape, and
|
|
13
|
+
* a saved file the model hands to `send_media`.
|
|
14
|
+
*
|
|
15
|
+
* Providers (auto-selected by which key is configured, preference order):
|
|
16
|
+
* β’ google β Lyria via Gemini generateContent (AUDIO modality) β base64
|
|
17
|
+
* audio (mp3). Single POST, no poll.
|
|
18
|
+
* β’ minimax β Music generation β URL or inline (hex/base64) audio (mp3).
|
|
19
|
+
* β’ elevenlabs β Music endpoint β raw mp3 bytes.
|
|
20
|
+
* Keys resolve through `resolveMediaProviderKey` (the same credential-store +
|
|
21
|
+
* env path the media-understanding subsystem uses), so music generation works
|
|
22
|
+
* for whichever provider the operator already configured β no bespoke auth.
|
|
23
|
+
*
|
|
24
|
+
* Flow: generate β bytes saved under `<cache>/audio/` β result text carries a
|
|
25
|
+
* `MEDIA:<saved-path>` line β the model delivers with `send_media({path})`.
|
|
26
|
+
*/
|
|
27
|
+
import { Type } from "typebox";
|
|
28
|
+
import type { BrigadeTool } from "./types.js";
|
|
29
|
+
type MusicProviderId = "google" | "minimax" | "elevenlabs";
|
|
30
|
+
declare const GenerateMusicParams: Type.TObject<{
|
|
31
|
+
action: Type.TOptional<Type.TUnion<[Type.TLiteral<"generate">, Type.TLiteral<"list">]>>;
|
|
32
|
+
prompt: Type.TOptional<Type.TString>;
|
|
33
|
+
lyrics: Type.TOptional<Type.TString>;
|
|
34
|
+
instrumental: Type.TOptional<Type.TBoolean>;
|
|
35
|
+
provider: Type.TOptional<Type.TUnion<[Type.TLiteral<"google">, Type.TLiteral<"minimax">, Type.TLiteral<"elevenlabs">]>>;
|
|
36
|
+
model: Type.TOptional<Type.TString>;
|
|
37
|
+
durationSeconds: Type.TOptional<Type.TInteger>;
|
|
38
|
+
filename: Type.TOptional<Type.TString>;
|
|
39
|
+
}>;
|
|
40
|
+
interface GenerateMusicDetails {
|
|
41
|
+
action: "generate" | "list";
|
|
42
|
+
provider?: string;
|
|
43
|
+
model?: string;
|
|
44
|
+
path?: string;
|
|
45
|
+
providers?: string[];
|
|
46
|
+
ok: boolean;
|
|
47
|
+
message?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface MakeGenerateMusicToolOptions {
|
|
50
|
+
/** Caller's agent id β drives which credential store backs the key. */
|
|
51
|
+
agentId?: string;
|
|
52
|
+
/** Test seam: replaces global fetch. */
|
|
53
|
+
fetchFn?: typeof fetch;
|
|
54
|
+
/** Test seam: output directory override. Default `<cache>/audio`. */
|
|
55
|
+
outDirOverride?: string;
|
|
56
|
+
/** Test seam: per-provider API-key resolver override. */
|
|
57
|
+
resolveKey?: (provider: MusicProviderId) => string;
|
|
58
|
+
}
|
|
59
|
+
export declare function makeGenerateMusicTool(opts?: MakeGenerateMusicToolOptions): BrigadeTool<typeof GenerateMusicParams, GenerateMusicDetails>;
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=generate-music-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-music-tool.d.ts","sourceRoot":"","sources":["../../../src/agents/tools/generate-music-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAKH,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAM/B,OAAO,KAAK,EAAmB,WAAW,EAAE,MAAM,YAAY,CAAC;AAO/D,KAAK,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC;AAW3D,QAAA,MAAM,mBAAmB;;;;;;;;;EAwBvB,CAAC;AAEH,UAAU,oBAAoB;IAC7B,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,4BAA4B;IAC5C,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,MAAM,CAAC;CACnD;AAED,wBAAgB,qBAAqB,CACpC,IAAI,GAAE,4BAAiC,GACrC,WAAW,CAAC,OAAO,mBAAmB,EAAE,oBAAoB,CAAC,CAwG/D"}
|