@theokit/sdk-tools 0.27.5 → 0.28.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/dist/index.d.cts CHANGED
@@ -1279,6 +1279,113 @@ interface CreateShellToolOptions {
1279
1279
  */
1280
1280
  declare function createShellTool(opts: CreateShellToolOptions): CustomTool;
1281
1281
 
1282
+ /**
1283
+ * `text_to_speech` — turns text into an audio file inside the project root.
1284
+ *
1285
+ * The audio comes from a `synthesize` callback the consumer injects (the `web_search` pattern): the
1286
+ * tool holds no key and opens no socket. The clip is written to `outputDir` below `projectRoot`
1287
+ * under the same confinement `view_image` and `read_file` carry, and the model receives the
1288
+ * relative path, the mime type and the byte count, never the audio itself: a model cannot listen
1289
+ * to it, and a base64 clip at the default ceiling would be about 13 MB of context text.
1290
+ *
1291
+ * Return shape (always a JSON string):
1292
+ * - `{ ok: true, path, mime_type, bytes }`
1293
+ * - `{ ok: false, error }` with `error` one of `path_traversal`, `text_too_large`,
1294
+ * `synthesis_failed`, `unsupported_audio_type`, `empty_audio`, `audio_too_large`, `write_failed`
1295
+ *
1296
+ * Nothing is written on any failure, and a refused path or an oversize text never reaches the
1297
+ * provider.
1298
+ *
1299
+ * Limits, accepted and stated: the confinement is checked before the write and again after the
1300
+ * output directory is created, but a symlink swapped in by another process between that check and
1301
+ * the rename is not caught (the same-user race the sibling tools leave open). A clip is written with
1302
+ * mode 0o600. A chosen `filename`, or a default stem two texts happen to share (32 bits of the
1303
+ * text's sha256), replaces the existing file of that name. `synthesis_failed` carries the provider's
1304
+ * own message on purpose: the provider is the consumer's code, and its words are what the model
1305
+ * needs to explain the failure; `write_failed` carries only the errno code, since a file-system
1306
+ * message names the host's absolute paths.
1307
+ */
1308
+
1309
+ /** Default ceiling on the text sent to the provider, in UTF-16 code units. A default, not a measured limit. */
1310
+ declare const DEFAULT_MAX_TEXT_CHARS = 4096;
1311
+ /** Default ceiling on the clip written to disk: 10 MiB, by analogy with `DEFAULT_MAX_IMAGE_BYTES`. */
1312
+ declare const DEFAULT_MAX_AUDIO_BYTES: number;
1313
+ /** What the provider returns: the encoded clip and its mime type. */
1314
+ interface SynthesizedAudio {
1315
+ audio: Uint8Array;
1316
+ mimeType: string;
1317
+ }
1318
+ /**
1319
+ * The speech provider {@link createTextToSpeechTool} calls.
1320
+ *
1321
+ * Reject the promise to signal a failed synthesis: the tool turns a rejection into
1322
+ * `{ ok: false, error: "synthesis_failed", message }` and never lets it escape into the agent turn.
1323
+ */
1324
+ type TextToSpeechCallback = (text: string) => Promise<SynthesizedAudio>;
1325
+ /** Options for {@link createTextToSpeechTool}. `synthesize` and `projectRoot` are required. */
1326
+ interface CreateTextToSpeechToolOptions {
1327
+ /** Speech provider callback — the consumer injects the implementation. */
1328
+ synthesize: TextToSpeechCallback;
1329
+ /** Root the tool writes under. Every path is resolved inside it. */
1330
+ projectRoot: string;
1331
+ /** Directory for the clips, relative to `projectRoot`. Omitted ⇒ `audio`. Created when absent. */
1332
+ outputDir?: string;
1333
+ /** Name exposed to the model. Omitted ⇒ `text_to_speech`. The name is a contract: the approval
1334
+ * key, what the model sees and what telemetry records. */
1335
+ name?: string;
1336
+ /** Description exposed to the model. Omitted ⇒ the literal below. */
1337
+ description?: string;
1338
+ /** Longest text sent to the provider. Omitted ⇒ {@link DEFAULT_MAX_TEXT_CHARS}. */
1339
+ maxTextChars?: number;
1340
+ /** Largest clip written. Omitted ⇒ {@link DEFAULT_MAX_AUDIO_BYTES}. A clip of exactly this size fits. */
1341
+ maxAudioBytes?: number;
1342
+ }
1343
+ type TextToSpeechFailure = {
1344
+ ok: false;
1345
+ error: "path_traversal";
1346
+ path: string;
1347
+ } | {
1348
+ ok: false;
1349
+ error: "text_too_large";
1350
+ chars: number;
1351
+ max_chars: number;
1352
+ } | {
1353
+ ok: false;
1354
+ error: "synthesis_failed";
1355
+ message: string;
1356
+ } | {
1357
+ ok: false;
1358
+ error: "unsupported_audio_type";
1359
+ mime_type: string;
1360
+ } | {
1361
+ ok: false;
1362
+ error: "empty_audio";
1363
+ } | {
1364
+ ok: false;
1365
+ error: "audio_too_large";
1366
+ bytes: number;
1367
+ max_bytes: number;
1368
+ } | {
1369
+ ok: false;
1370
+ error: "write_failed";
1371
+ message: string;
1372
+ };
1373
+ /** The tool's result, before it is serialised to the JSON string the handler returns. */
1374
+ type TextToSpeechResult = {
1375
+ ok: true;
1376
+ path: string;
1377
+ mime_type: string;
1378
+ bytes: number;
1379
+ } | TextToSpeechFailure;
1380
+ /**
1381
+ * Build the `text_to_speech` tool over a caller-supplied provider.
1382
+ *
1383
+ * Order of checks: the text length, then the path (stem and output directory), then the provider,
1384
+ * then the mime table, the empty and size checks, then the write. A refused path or an oversize
1385
+ * text never reaches the provider, and no failure leaves a file behind.
1386
+ */
1387
+ declare function createTextToSpeechTool(opts: CreateTextToSpeechToolOptions): CustomTool;
1388
+
1282
1389
  /**
1283
1390
  * `todolist` — in-session task tracking for multi-step work.
1284
1391
  *
@@ -1805,4 +1912,4 @@ interface CreateWriteFileToolOptions {
1805
1912
  */
1806
1913
  declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
1807
1914
 
1808
- export { CatastrophicCommandError, type CommandPolicy, ContextMatchError, type ContextMatchReason, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateCurrentTimeToolOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGitStatusToolOptions, type CreateGlobToolOptions, type CreateInteractiveShellToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateViewImageToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, DEFAULT_MAX_IMAGE_BYTES, DEFAULT_TOOL_GUIDANCE, type EnvContextOptions, type PlanModeTool, type PlanModeToolOptions, type PlanModeToolWithStore, type PlanNode, type QuestionTool, type QuestionToolOptions, ReadTracker, ReasoningTools, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationMode, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createCurrentTimeTool, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGitStatusTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createUpdatePlanTool, createViewImageTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
1915
+ export { CatastrophicCommandError, type CommandPolicy, ContextMatchError, type ContextMatchReason, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateCurrentTimeToolOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGitStatusToolOptions, type CreateGlobToolOptions, type CreateInteractiveShellToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateTextToSpeechToolOptions, type CreateViewImageToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, DEFAULT_MAX_AUDIO_BYTES, DEFAULT_MAX_IMAGE_BYTES, DEFAULT_MAX_TEXT_CHARS, DEFAULT_TOOL_GUIDANCE, type EnvContextOptions, type PlanModeTool, type PlanModeToolOptions, type PlanModeToolWithStore, type PlanNode, type QuestionTool, type QuestionToolOptions, ReadTracker, ReasoningTools, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type SynthesizedAudio, type TextToSpeechCallback, type TextToSpeechResult, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationMode, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createCurrentTimeTool, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGitStatusTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTextToSpeechTool, createTodolistTool, createUpdatePlanTool, createViewImageTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
package/dist/index.d.ts CHANGED
@@ -1279,6 +1279,113 @@ interface CreateShellToolOptions {
1279
1279
  */
1280
1280
  declare function createShellTool(opts: CreateShellToolOptions): CustomTool;
1281
1281
 
1282
+ /**
1283
+ * `text_to_speech` — turns text into an audio file inside the project root.
1284
+ *
1285
+ * The audio comes from a `synthesize` callback the consumer injects (the `web_search` pattern): the
1286
+ * tool holds no key and opens no socket. The clip is written to `outputDir` below `projectRoot`
1287
+ * under the same confinement `view_image` and `read_file` carry, and the model receives the
1288
+ * relative path, the mime type and the byte count, never the audio itself: a model cannot listen
1289
+ * to it, and a base64 clip at the default ceiling would be about 13 MB of context text.
1290
+ *
1291
+ * Return shape (always a JSON string):
1292
+ * - `{ ok: true, path, mime_type, bytes }`
1293
+ * - `{ ok: false, error }` with `error` one of `path_traversal`, `text_too_large`,
1294
+ * `synthesis_failed`, `unsupported_audio_type`, `empty_audio`, `audio_too_large`, `write_failed`
1295
+ *
1296
+ * Nothing is written on any failure, and a refused path or an oversize text never reaches the
1297
+ * provider.
1298
+ *
1299
+ * Limits, accepted and stated: the confinement is checked before the write and again after the
1300
+ * output directory is created, but a symlink swapped in by another process between that check and
1301
+ * the rename is not caught (the same-user race the sibling tools leave open). A clip is written with
1302
+ * mode 0o600. A chosen `filename`, or a default stem two texts happen to share (32 bits of the
1303
+ * text's sha256), replaces the existing file of that name. `synthesis_failed` carries the provider's
1304
+ * own message on purpose: the provider is the consumer's code, and its words are what the model
1305
+ * needs to explain the failure; `write_failed` carries only the errno code, since a file-system
1306
+ * message names the host's absolute paths.
1307
+ */
1308
+
1309
+ /** Default ceiling on the text sent to the provider, in UTF-16 code units. A default, not a measured limit. */
1310
+ declare const DEFAULT_MAX_TEXT_CHARS = 4096;
1311
+ /** Default ceiling on the clip written to disk: 10 MiB, by analogy with `DEFAULT_MAX_IMAGE_BYTES`. */
1312
+ declare const DEFAULT_MAX_AUDIO_BYTES: number;
1313
+ /** What the provider returns: the encoded clip and its mime type. */
1314
+ interface SynthesizedAudio {
1315
+ audio: Uint8Array;
1316
+ mimeType: string;
1317
+ }
1318
+ /**
1319
+ * The speech provider {@link createTextToSpeechTool} calls.
1320
+ *
1321
+ * Reject the promise to signal a failed synthesis: the tool turns a rejection into
1322
+ * `{ ok: false, error: "synthesis_failed", message }` and never lets it escape into the agent turn.
1323
+ */
1324
+ type TextToSpeechCallback = (text: string) => Promise<SynthesizedAudio>;
1325
+ /** Options for {@link createTextToSpeechTool}. `synthesize` and `projectRoot` are required. */
1326
+ interface CreateTextToSpeechToolOptions {
1327
+ /** Speech provider callback — the consumer injects the implementation. */
1328
+ synthesize: TextToSpeechCallback;
1329
+ /** Root the tool writes under. Every path is resolved inside it. */
1330
+ projectRoot: string;
1331
+ /** Directory for the clips, relative to `projectRoot`. Omitted ⇒ `audio`. Created when absent. */
1332
+ outputDir?: string;
1333
+ /** Name exposed to the model. Omitted ⇒ `text_to_speech`. The name is a contract: the approval
1334
+ * key, what the model sees and what telemetry records. */
1335
+ name?: string;
1336
+ /** Description exposed to the model. Omitted ⇒ the literal below. */
1337
+ description?: string;
1338
+ /** Longest text sent to the provider. Omitted ⇒ {@link DEFAULT_MAX_TEXT_CHARS}. */
1339
+ maxTextChars?: number;
1340
+ /** Largest clip written. Omitted ⇒ {@link DEFAULT_MAX_AUDIO_BYTES}. A clip of exactly this size fits. */
1341
+ maxAudioBytes?: number;
1342
+ }
1343
+ type TextToSpeechFailure = {
1344
+ ok: false;
1345
+ error: "path_traversal";
1346
+ path: string;
1347
+ } | {
1348
+ ok: false;
1349
+ error: "text_too_large";
1350
+ chars: number;
1351
+ max_chars: number;
1352
+ } | {
1353
+ ok: false;
1354
+ error: "synthesis_failed";
1355
+ message: string;
1356
+ } | {
1357
+ ok: false;
1358
+ error: "unsupported_audio_type";
1359
+ mime_type: string;
1360
+ } | {
1361
+ ok: false;
1362
+ error: "empty_audio";
1363
+ } | {
1364
+ ok: false;
1365
+ error: "audio_too_large";
1366
+ bytes: number;
1367
+ max_bytes: number;
1368
+ } | {
1369
+ ok: false;
1370
+ error: "write_failed";
1371
+ message: string;
1372
+ };
1373
+ /** The tool's result, before it is serialised to the JSON string the handler returns. */
1374
+ type TextToSpeechResult = {
1375
+ ok: true;
1376
+ path: string;
1377
+ mime_type: string;
1378
+ bytes: number;
1379
+ } | TextToSpeechFailure;
1380
+ /**
1381
+ * Build the `text_to_speech` tool over a caller-supplied provider.
1382
+ *
1383
+ * Order of checks: the text length, then the path (stem and output directory), then the provider,
1384
+ * then the mime table, the empty and size checks, then the write. A refused path or an oversize
1385
+ * text never reaches the provider, and no failure leaves a file behind.
1386
+ */
1387
+ declare function createTextToSpeechTool(opts: CreateTextToSpeechToolOptions): CustomTool;
1388
+
1282
1389
  /**
1283
1390
  * `todolist` — in-session task tracking for multi-step work.
1284
1391
  *
@@ -1805,4 +1912,4 @@ interface CreateWriteFileToolOptions {
1805
1912
  */
1806
1913
  declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
1807
1914
 
1808
- export { CatastrophicCommandError, type CommandPolicy, ContextMatchError, type ContextMatchReason, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateCurrentTimeToolOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGitStatusToolOptions, type CreateGlobToolOptions, type CreateInteractiveShellToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateViewImageToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, DEFAULT_MAX_IMAGE_BYTES, DEFAULT_TOOL_GUIDANCE, type EnvContextOptions, type PlanModeTool, type PlanModeToolOptions, type PlanModeToolWithStore, type PlanNode, type QuestionTool, type QuestionToolOptions, ReadTracker, ReasoningTools, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationMode, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createCurrentTimeTool, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGitStatusTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createUpdatePlanTool, createViewImageTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
1915
+ export { CatastrophicCommandError, type CommandPolicy, ContextMatchError, type ContextMatchReason, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateCurrentTimeToolOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGitStatusToolOptions, type CreateGlobToolOptions, type CreateInteractiveShellToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateTextToSpeechToolOptions, type CreateViewImageToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, DEFAULT_MAX_AUDIO_BYTES, DEFAULT_MAX_IMAGE_BYTES, DEFAULT_MAX_TEXT_CHARS, DEFAULT_TOOL_GUIDANCE, type EnvContextOptions, type PlanModeTool, type PlanModeToolOptions, type PlanModeToolWithStore, type PlanNode, type QuestionTool, type QuestionToolOptions, ReadTracker, ReasoningTools, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type SynthesizedAudio, type TextToSpeechCallback, type TextToSpeechResult, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationMode, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createCurrentTimeTool, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGitStatusTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTextToSpeechTool, createTodolistTool, createUpdatePlanTool, createViewImageTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { rm, mkdir, writeFile, readFile, readdir, open, stat } from 'fs/promises';
2
- import { dirname, relative, join, isAbsolute, extname } from 'path';
1
+ import { rm, mkdir, writeFile, readFile, readdir, open, stat, rename } from 'fs/promises';
2
+ import { dirname, relative, join, isAbsolute, posix, extname, basename } from 'path';
3
3
  import { Tool, ConfigurationError } from '@theokit/sdk';
4
4
  import { z } from 'zod';
5
5
  import { safePathJoin, assertNoSymlinkEscape, PathTraversalError, ForbiddenPathError, isForbiddenPath, safeFilenameForId } from '@theokit/sdk/path-safety';
@@ -11,7 +11,7 @@ import { spawn } from 'child_process';
11
11
  import { resolveInteractive, InteractiveUnavailableError, NoSuchSessionError } from '@theokit/sdk/interactive';
12
12
  import { lookup } from 'dns/promises';
13
13
  import { isIP } from 'net';
14
- import { randomUUID } from 'crypto';
14
+ import { randomUUID, createHash, randomBytes } from 'crypto';
15
15
 
16
16
  // src/apply-patch.ts
17
17
 
@@ -2459,6 +2459,123 @@ function formatResult(result, timeoutMs) {
2459
2459
  exit_code: result.exitCode
2460
2460
  });
2461
2461
  }
2462
+ var AUDIO_EXTENSIONS = /* @__PURE__ */ new Map([
2463
+ ["audio/mpeg", "mp3"],
2464
+ ["audio/wav", "wav"],
2465
+ ["audio/x-wav", "wav"],
2466
+ ["audio/ogg", "ogg"],
2467
+ ["audio/opus", "opus"],
2468
+ ["audio/flac", "flac"],
2469
+ ["audio/aac", "aac"],
2470
+ ["audio/webm", "webm"]
2471
+ ]);
2472
+ var DEFAULT_MAX_TEXT_CHARS = 4096;
2473
+ var DEFAULT_MAX_AUDIO_BYTES = 10 * 1024 * 1024;
2474
+ var STEM_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
2475
+ var json = (result) => JSON.stringify(result);
2476
+ var errorMessage = (err) => err instanceof Error ? err.message : String(err);
2477
+ var errorCode = (err) => err.code ?? "unknown";
2478
+ function requirePositiveInteger(name, value) {
2479
+ if (!Number.isInteger(value) || value <= 0) {
2480
+ throw new RangeError(
2481
+ `createTextToSpeechTool: ${name} must be a positive integer, got ${value}`
2482
+ );
2483
+ }
2484
+ return value;
2485
+ }
2486
+ function defaultStem(text) {
2487
+ return `speech-${createHash("sha256").update(text).digest("hex").slice(0, 8)}`;
2488
+ }
2489
+ function refusePath(relativeStem, stem, projectRoot) {
2490
+ if (!STEM_PATTERN.test(stem) || isForbiddenAtAnyDepth(relativeStem)) {
2491
+ return json({ ok: false, error: "path_traversal", path: relativeStem });
2492
+ }
2493
+ return checkPathScope(relativeStem, projectRoot);
2494
+ }
2495
+ async function synthesizeSafely(synthesize, text) {
2496
+ try {
2497
+ const value = await synthesize(text);
2498
+ if (!(value?.audio instanceof Uint8Array) || typeof value.mimeType !== "string") {
2499
+ return { ok: false, error: "synthesis_failed", message: "provider returned no audio" };
2500
+ }
2501
+ return { audio: value.audio, mimeType: value.mimeType };
2502
+ } catch (err) {
2503
+ return { ok: false, error: "synthesis_failed", message: errorMessage(err) };
2504
+ }
2505
+ }
2506
+ function checkAudio({ audio, mimeType }, maxAudioBytes) {
2507
+ const extension = AUDIO_EXTENSIONS.get(mimeType);
2508
+ if (extension === void 0)
2509
+ return { ok: false, error: "unsupported_audio_type", mime_type: mimeType };
2510
+ if (audio.length === 0) return { ok: false, error: "empty_audio" };
2511
+ if (audio.length > maxAudioBytes) {
2512
+ return { ok: false, error: "audio_too_large", bytes: audio.length, max_bytes: maxAudioBytes };
2513
+ }
2514
+ return { extension };
2515
+ }
2516
+ async function writeAtomic(target, audio) {
2517
+ const temp = `${dirname(target)}/.${basename(target)}.${randomBytes(6).toString("hex")}.tmp`;
2518
+ try {
2519
+ await writeFile(temp, audio, { flag: "wx", mode: 384 });
2520
+ await rename(temp, target);
2521
+ } catch (err) {
2522
+ await rm(temp, { force: true });
2523
+ throw err;
2524
+ }
2525
+ }
2526
+ async function saveClip(projectRoot, outputDir, relativePath2, { audio, mimeType }) {
2527
+ const target = safePathJoin(projectRoot, relativePath2);
2528
+ try {
2529
+ await mkdir(dirname(target), { recursive: true });
2530
+ const refused = checkPathScope(outputDir, projectRoot);
2531
+ if (refused !== null) return refused;
2532
+ await writeAtomic(target, audio);
2533
+ return json({ ok: true, path: relativePath2, mime_type: mimeType, bytes: audio.length });
2534
+ } catch (err) {
2535
+ return json({ ok: false, error: "write_failed", message: errorCode(err) });
2536
+ }
2537
+ }
2538
+ function createTextToSpeechTool(opts) {
2539
+ const { synthesize, projectRoot } = opts;
2540
+ const outputDir = posix.normalize((opts.outputDir ?? "audio").replace(/\\/g, "/"));
2541
+ const maxTextChars = requirePositiveInteger(
2542
+ "maxTextChars",
2543
+ opts.maxTextChars ?? DEFAULT_MAX_TEXT_CHARS
2544
+ );
2545
+ const maxAudioBytes = requirePositiveInteger(
2546
+ "maxAudioBytes",
2547
+ opts.maxAudioBytes ?? DEFAULT_MAX_AUDIO_BYTES
2548
+ );
2549
+ return Tool.create({
2550
+ name: opts.name ?? "text_to_speech",
2551
+ description: opts.description ?? "Turn text into spoken audio and save it as a file in the project. Use when the user asks for speech, a voice reply or an audio clip. Returns { ok, path, mime_type, bytes } with the path relative to the project root, or { ok: false, error }. The speech provider is injected by the consumer.",
2552
+ inputSchema: z.object({
2553
+ text: z.string().min(1).describe("The text to speak."),
2554
+ filename: z.string().optional().describe(
2555
+ "File name without extension (letters, digits, '.', '_', '-'). Default: derived from the text."
2556
+ )
2557
+ }),
2558
+ handler: async ({ text, filename }) => {
2559
+ if (text.length > maxTextChars) {
2560
+ return json({
2561
+ ok: false,
2562
+ error: "text_too_large",
2563
+ chars: text.length,
2564
+ max_chars: maxTextChars
2565
+ });
2566
+ }
2567
+ const stem = filename ?? defaultStem(text);
2568
+ const refused = refusePath(posix.join(outputDir, stem), stem, projectRoot);
2569
+ if (refused !== null) return refused;
2570
+ const synthesized = await synthesizeSafely(synthesize, text);
2571
+ if ("ok" in synthesized) return json(synthesized);
2572
+ const checked = checkAudio(synthesized, maxAudioBytes);
2573
+ if ("ok" in checked) return json(checked);
2574
+ const relativePath2 = posix.join(outputDir, `${stem}.${checked.extension}`);
2575
+ return saveClip(projectRoot, outputDir, relativePath2, synthesized);
2576
+ }
2577
+ });
2578
+ }
2462
2579
 
2463
2580
  // src/todo-plan-nodes.ts
2464
2581
  function todoItemsToPlanNodes(items) {
@@ -2661,7 +2778,7 @@ var MEDIA_TYPES = /* @__PURE__ */ new Map([
2661
2778
  [".webp", "image/webp"]
2662
2779
  ]);
2663
2780
  var DEFAULT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
2664
- var json = (result) => JSON.stringify(result);
2781
+ var json2 = (result) => JSON.stringify(result);
2665
2782
  function createViewImageTool(options) {
2666
2783
  const { projectRoot } = options;
2667
2784
  const maxBytes = options.maxBytes ?? DEFAULT_MAX_IMAGE_BYTES;
@@ -2675,11 +2792,11 @@ function createViewImageTool(options) {
2675
2792
  const refused = checkPathScope(input.path, projectRoot);
2676
2793
  if (refused !== null) return refused;
2677
2794
  if (isForbiddenAtAnyDepth(input.path)) {
2678
- return json({ ok: false, error: "path_traversal", path: input.path });
2795
+ return json2({ ok: false, error: "path_traversal", path: input.path });
2679
2796
  }
2680
2797
  const mediaType = MEDIA_TYPES.get(extname(input.path).toLowerCase());
2681
2798
  if (mediaType === void 0) {
2682
- return json({
2799
+ return json2({
2683
2800
  ok: false,
2684
2801
  error: "unsupported_image_type",
2685
2802
  path: input.path,
@@ -2688,8 +2805,8 @@ function createViewImageTool(options) {
2688
2805
  }
2689
2806
  const absolute = safePathJoin(projectRoot, input.path);
2690
2807
  const outcome = readWithinBudget(absolute, maxBytes);
2691
- if (!outcome.ok) return json(failureFor(outcome, input.path, maxBytes));
2692
- return json({
2808
+ if (!outcome.ok) return json2(failureFor(outcome, input.path, maxBytes));
2809
+ return json2({
2693
2810
  ok: true,
2694
2811
  path: input.path,
2695
2812
  media_type: mediaType,
@@ -2886,8 +3003,8 @@ function createBraveWebSearchAdapter(opts = {}) {
2886
3003
  headers: { "X-Subscription-Token": apiKey, Accept: "application/json" }
2887
3004
  });
2888
3005
  if (!res.ok) throw new Error(`brave_search_failed: HTTP ${res.status}`);
2889
- const json2 = await res.json();
2890
- const results = json2?.web?.results ?? [];
3006
+ const json3 = await res.json();
3007
+ const results = json3?.web?.results ?? [];
2891
3008
  return results.map((r) => ({
2892
3009
  title: String(r?.title ?? ""),
2893
3010
  url: String(r?.url ?? ""),
@@ -3047,6 +3164,6 @@ async function isBinaryFile(absolutePath) {
3047
3164
  }
3048
3165
  }
3049
3166
 
3050
- export { CatastrophicCommandError, ContextMatchError, DEFAULT_MAX_IMAGE_BYTES, DEFAULT_TOOL_GUIDANCE, ReadTracker, ReasoningTools, RedirectBlockedError, SsrfBlockedError, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createCurrentTimeTool, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGitStatusTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createUpdatePlanTool, createViewImageTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
3167
+ export { CatastrophicCommandError, ContextMatchError, DEFAULT_MAX_AUDIO_BYTES, DEFAULT_MAX_IMAGE_BYTES, DEFAULT_MAX_TEXT_CHARS, DEFAULT_TOOL_GUIDANCE, ReadTracker, ReasoningTools, RedirectBlockedError, SsrfBlockedError, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createCurrentTimeTool, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGitStatusTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTextToSpeechTool, createTodolistTool, createUpdatePlanTool, createViewImageTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
3051
3168
  //# sourceMappingURL=index.js.map
3052
3169
  //# sourceMappingURL=index.js.map