@theokit/sdk-tools 0.27.6 → 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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/github-link-preview.ts"],"names":[],"mappings":"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","file":"github-link-preview.js","sourcesContent":["/**\n * `github_link_preview` — turn a github.com issue, pull request or repository URL into its title,\n * state and summary.\n *\n * The model's URL is parsed, never fetched: owner, repository and number are validated against\n * GitHub's own name charset and the request always goes to a fixed `https://api.github.com`\n * endpoint built from those parts (ADR D1). Redirects are refused, so a response can never choose\n * the next host or carry the bearer token elsewhere (ADR D2).\n *\n * Return shape (always a JSON string):\n * - `{ ok: true, kind, url, title, state, summary }`\n * - `{ ok: false, error: 'unsupported_url' | 'redirect_blocked' | 'not_found' | 'rate_limited' |\n * 'upstream_error' | 'timeout' | 'fetch_failed' | 'invalid_response', url, ... }`\n */\n\nimport type { CustomTool } from \"@theokit/sdk\";\n\nimport { Tool } from \"@theokit/sdk\";\nimport { z } from \"zod\";\n\nconst API_BASE = \"https://api.github.com\";\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_SUMMARY_CHARS = 500;\nconst MAX_BODY_BYTES = 1_048_576;\nconst GITHUB_HOSTS = new Set([\"github.com\", \"www.github.com\"]);\nconst OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/;\nconst REPO_PATTERN = /^[A-Za-z0-9._-]{1,100}$/;\nconst NUMBER_PATTERN = /^[1-9][0-9]{0,9}$/;\n\n/** What a previewed GitHub URL points at, taken from its path. */\nexport type GithubLinkKind = \"repository\" | \"issue\" | \"pull_request\";\n\n/** Options for {@link createGithubLinkPreviewTool}; every one is optional. */\nexport interface CreateGithubLinkPreviewToolOptions {\n /** Fetch implementation with the global `fetch` signature (injectable for tests). */\n fetchImpl?: typeof fetch;\n /** Optional GitHub token, sent as `Authorization: Bearer` to api.github.com only. */\n token?: string;\n /** Name exposed to the model. Default `github_link_preview`. */\n name?: string;\n /** Description exposed to the model. */\n description?: string;\n /** Timeout covering the request and the body read, in ms. Default 10000. */\n timeoutMs?: number;\n /** Longest summary returned, in code points. Default 500. */\n maxSummaryChars?: number;\n /** Clock for `retry_after_seconds` (injectable for tests). Default `Date.now`. */\n now?: () => number;\n}\n\n/** The JSON the `github_link_preview` tool returns: a preview, or a typed failure. */\nexport type GithubLinkPreviewResult =\n | {\n ok: true;\n kind: GithubLinkKind;\n url: string;\n title: string;\n state: string;\n summary: string;\n }\n | {\n ok: false;\n error:\n | \"unsupported_url\"\n | \"redirect_blocked\"\n | \"not_found\"\n | \"rate_limited\"\n | \"upstream_error\"\n | \"timeout\"\n | \"fetch_failed\"\n | \"invalid_response\";\n url: string;\n status?: number;\n retry_after_seconds?: number;\n timeout_ms?: number;\n error_name?: string;\n };\n\ninterface GithubTarget {\n kind: GithubLinkKind;\n owner: string;\n repo: string;\n number?: string;\n}\n\ninterface Settings {\n fetchImpl: typeof fetch;\n headers: Record<string, string>;\n timeoutMs: number;\n maxSummaryChars: number;\n now: () => number;\n}\n\ntype Failure = Extract<GithubLinkPreviewResult, { ok: false }>;\ntype FailureError = Failure[\"error\"];\n\nclass PreviewFailure extends Error {\n constructor(\n readonly error: FailureError,\n readonly extra: Omit<Failure, \"ok\" | \"error\" | \"url\"> = {},\n ) {\n super(error);\n }\n}\n\nfunction requirePositiveInteger(name: string, value: number): number {\n if (!Number.isInteger(value) || value <= 0) {\n throw new RangeError(\n `createGithubLinkPreviewTool: ${name} must be a positive integer, got ${value}`,\n );\n }\n return value;\n}\n\nfunction hasControlCharacter(text: string): boolean {\n return Array.from(text).some((char) => {\n const code = char.charCodeAt(0);\n return code < 0x20 || code === 0x7f;\n });\n}\n\nfunction requireValidToken(token: string): string {\n if (token.trim() === \"\" || hasControlCharacter(token)) {\n throw new RangeError(\n \"createGithubLinkPreviewTool: token must be non-blank and free of control characters\",\n );\n }\n return token;\n}\n\nfunction buildHeaders(token: string | undefined): Record<string, string> {\n const headers: Record<string, string> = {\n accept: \"application/vnd.github+json\",\n \"user-agent\": \"theokit-sdk-tools\",\n \"x-github-api-version\": \"2022-11-28\",\n };\n if (token !== undefined) headers.authorization = `Bearer ${token}`;\n return headers;\n}\n\nfunction isGithubOrigin(parsed: URL): boolean {\n return (\n parsed.protocol === \"https:\" &&\n GITHUB_HOSTS.has(parsed.hostname) &&\n parsed.username === \"\" &&\n parsed.password === \"\" &&\n parsed.port === \"\"\n );\n}\n\nconst SECTION_KINDS: Readonly<Record<string, GithubLinkKind>> = {\n issues: \"issue\",\n pull: \"pull_request\",\n};\n\nfunction isValidRepository(owner: string, repo: string): boolean {\n return OWNER_PATTERN.test(owner) && REPO_PATTERN.test(repo) && repo !== \".\" && repo !== \"..\";\n}\n\nfunction numberedTarget(owner: string, repo: string, segments: string[]): GithubTarget | undefined {\n const [, , section = \"\", number = \"\"] = segments;\n const kind = Object.hasOwn(SECTION_KINDS, section) ? SECTION_KINDS[section] : undefined;\n if (segments.length !== 4 || kind === undefined || !NUMBER_PATTERN.test(number)) return undefined;\n return { kind, owner, repo, number };\n}\n\nfunction targetFromSegments(segments: string[]): GithubTarget | undefined {\n const [owner = \"\", repo = \"\"] = segments;\n if (!isValidRepository(owner, repo)) return undefined;\n if (segments.length === 2) return { kind: \"repository\", owner, repo };\n return numberedTarget(owner, repo, segments);\n}\n\nfunction parseGithubUrl(url: string): GithubTarget | undefined {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return undefined;\n }\n if (!isGithubOrigin(parsed)) return undefined;\n const segments = parsed.pathname.split(\"/\").slice(1);\n if (segments.at(-1) === \"\") segments.pop();\n return targetFromSegments(segments);\n}\n\nfunction apiUrlFor(target: GithubTarget): string {\n const base = `${API_BASE}/repos/${target.owner}/${target.repo}`;\n if (target.kind === \"issue\") return `${base}/issues/${target.number}`;\n if (target.kind === \"pull_request\") return `${base}/pulls/${target.number}`;\n return base;\n}\n\nfunction canonicalUrlFor(target: GithubTarget): string {\n const base = `https://github.com/${target.owner}/${target.repo}`;\n if (target.kind === \"issue\") return `${base}/issues/${target.number}`;\n if (target.kind === \"pull_request\") return `${base}/pull/${target.number}`;\n return base;\n}\n\n// `retry-after` is read as delta-seconds only; an HTTP-date value falls through to the epoch reset.\nfunction retryAfterSeconds(headers: Headers, nowMs: number): number | undefined {\n const retryAfter = Number(headers.get(\"retry-after\") ?? Number.NaN);\n if (Number.isFinite(retryAfter)) return Math.max(0, Math.ceil(retryAfter));\n const reset = Number(headers.get(\"x-ratelimit-reset\") ?? Number.NaN);\n if (Number.isFinite(reset)) return Math.max(0, Math.ceil(reset - nowMs / 1000));\n return undefined;\n}\n\nfunction isRateLimited(response: Response): boolean {\n if (response.status !== 403 && response.status !== 429) return false;\n return (\n response.headers.get(\"x-ratelimit-remaining\") === \"0\" || response.headers.has(\"retry-after\")\n );\n}\n\nfunction checkStatus(response: Response, nowMs: number): void {\n const { status } = response;\n if (status >= 200 && status < 300) return;\n if (status >= 300 && status < 400) throw new PreviewFailure(\"redirect_blocked\", { status });\n if (status === 404) throw new PreviewFailure(\"not_found\", { status });\n if (isRateLimited(response)) {\n const retry = retryAfterSeconds(response.headers, nowMs);\n throw new PreviewFailure(\"rate_limited\", {\n status,\n ...(retry === undefined ? {} : { retry_after_seconds: retry }),\n });\n }\n throw new PreviewFailure(\"upstream_error\", { status });\n}\n\nasync function readCappedJson(response: Response): Promise<unknown> {\n if (Number(response.headers.get(\"content-length\") ?? 0) > MAX_BODY_BYTES) {\n throw new PreviewFailure(\"invalid_response\");\n }\n const text = await response.text();\n if (Buffer.byteLength(text, \"utf8\") > MAX_BODY_BYTES) {\n throw new PreviewFailure(\"invalid_response\");\n }\n try {\n return JSON.parse(text) as unknown;\n } catch {\n throw new PreviewFailure(\"invalid_response\");\n }\n}\n\nfunction cutCodePoints(text: unknown, max: number): string {\n if (typeof text !== \"string\") return \"\";\n return Array.from(text).slice(0, max).join(\"\");\n}\n\nfunction stateOf(target: GithubTarget, body: Record<string, unknown>): string {\n if (target.kind === \"repository\") return body.archived === true ? \"archived\" : \"active\";\n if (target.kind === \"pull_request\" && body.merged === true) return \"merged\";\n if (typeof body.state !== \"string\") throw new PreviewFailure(\"invalid_response\");\n return body.state;\n}\n\nfunction mapPreview(\n target: GithubTarget,\n body: unknown,\n maxSummaryChars: number,\n): GithubLinkPreviewResult {\n if (typeof body !== \"object\" || body === null || Array.isArray(body)) {\n throw new PreviewFailure(\"invalid_response\");\n }\n const record = body as Record<string, unknown>;\n const isRepository = target.kind === \"repository\";\n const title = isRepository ? record.full_name : record.title;\n if (typeof title !== \"string\") throw new PreviewFailure(\"invalid_response\");\n return {\n ok: true,\n kind: target.kind,\n url: canonicalUrlFor(target),\n title,\n state: stateOf(target, record),\n summary: cutCodePoints(isRepository ? record.description : record.body, maxSummaryChars),\n };\n}\n\nfunction failureFor(err: unknown, timeoutMs: number): PreviewFailure {\n if (err instanceof PreviewFailure) return err;\n const name = (err as { name?: unknown } | null)?.name;\n if (name === \"AbortError\" || name === \"TimeoutError\") {\n return new PreviewFailure(\"timeout\", { timeout_ms: timeoutMs });\n }\n return new PreviewFailure(\"fetch_failed\", {\n error_name: typeof name === \"string\" ? name : \"unknown\",\n });\n}\n\nasync function fetchPreview(\n target: GithubTarget,\n settings: Settings,\n): Promise<GithubLinkPreviewResult> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), settings.timeoutMs);\n try {\n const response = await settings.fetchImpl(apiUrlFor(target), {\n method: \"GET\",\n redirect: \"manual\",\n signal: controller.signal,\n headers: settings.headers,\n });\n checkStatus(response, settings.now());\n const body = await readCappedJson(response);\n return mapPreview(target, body, settings.maxSummaryChars);\n } finally {\n clearTimeout(timer);\n }\n}\n\nasync function preview(url: string, settings: Settings): Promise<GithubLinkPreviewResult> {\n const target = parseGithubUrl(url);\n if (target === undefined) return { ok: false, error: \"unsupported_url\", url };\n try {\n return await fetchPreview(target, settings);\n } catch (err) {\n const failure = failureFor(err, settings.timeoutMs);\n return { ok: false, error: failure.error, url, ...failure.extra };\n }\n}\n\n/**\n * Build the `github_link_preview` tool. Every option is optional: `createGithubLinkPreviewTool()`\n * previews public objects anonymously with a 10-second timeout and a 500-character summary.\n *\n * Only three URL shapes are accepted: `https://github.com/<owner>/<repo>`,\n * `.../issues/<n>` and `.../pull/<n>` (also on `www.github.com`; a trailing slash, query or\n * fragment is ignored). Anything else is `unsupported_url` and makes no request.\n *\n * Throws `RangeError` at construction for a non-positive or fractional `timeoutMs` or\n * `maxSummaryChars`, and for a blank token or one with a control character. Every failure after\n * construction is a typed `{ ok: false, error }` result, never a throw. The token is never\n * echoed: error bodies are not returned, and `fetch_failed` carries only the error's name.\n */\nexport function createGithubLinkPreviewTool(opts?: CreateGithubLinkPreviewToolOptions): CustomTool {\n const settings: Settings = {\n fetchImpl: opts?.fetchImpl ?? fetch,\n headers: buildHeaders(opts?.token === undefined ? undefined : requireValidToken(opts.token)),\n timeoutMs: requirePositiveInteger(\"timeoutMs\", opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS),\n maxSummaryChars: requirePositiveInteger(\n \"maxSummaryChars\",\n opts?.maxSummaryChars ?? DEFAULT_MAX_SUMMARY_CHARS,\n ),\n now: opts?.now ?? Date.now,\n };\n\n return Tool.create({\n name: opts?.name ?? \"github_link_preview\",\n description:\n opts?.description ??\n \"Preview a GitHub issue, pull request or repository from its github.com URL. Returns \" +\n \"{ ok, kind, url, title, state, summary } where state is open/closed/merged for issues and \" +\n \"pull requests and active/archived for repositories, or { ok: false, error }. Only \" +\n \"https://github.com/<owner>/<repo>, .../issues/<n> and .../pull/<n> are accepted.\",\n inputSchema: z.object({\n url: z.string().min(1).describe(\"A github.com repository, issue or pull request URL.\"),\n }),\n handler: async ({ url }) => JSON.stringify(await preview(url, settings)),\n });\n}\n"]}
package/dist/index.cjs CHANGED
@@ -2461,6 +2461,123 @@ function formatResult(result, timeoutMs) {
2461
2461
  exit_code: result.exitCode
2462
2462
  });
2463
2463
  }
2464
+ var AUDIO_EXTENSIONS = /* @__PURE__ */ new Map([
2465
+ ["audio/mpeg", "mp3"],
2466
+ ["audio/wav", "wav"],
2467
+ ["audio/x-wav", "wav"],
2468
+ ["audio/ogg", "ogg"],
2469
+ ["audio/opus", "opus"],
2470
+ ["audio/flac", "flac"],
2471
+ ["audio/aac", "aac"],
2472
+ ["audio/webm", "webm"]
2473
+ ]);
2474
+ var DEFAULT_MAX_TEXT_CHARS = 4096;
2475
+ var DEFAULT_MAX_AUDIO_BYTES = 10 * 1024 * 1024;
2476
+ var STEM_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
2477
+ var json = (result) => JSON.stringify(result);
2478
+ var errorMessage = (err) => err instanceof Error ? err.message : String(err);
2479
+ var errorCode = (err) => err.code ?? "unknown";
2480
+ function requirePositiveInteger(name, value) {
2481
+ if (!Number.isInteger(value) || value <= 0) {
2482
+ throw new RangeError(
2483
+ `createTextToSpeechTool: ${name} must be a positive integer, got ${value}`
2484
+ );
2485
+ }
2486
+ return value;
2487
+ }
2488
+ function defaultStem(text) {
2489
+ return `speech-${crypto.createHash("sha256").update(text).digest("hex").slice(0, 8)}`;
2490
+ }
2491
+ function refusePath(relativeStem, stem, projectRoot) {
2492
+ if (!STEM_PATTERN.test(stem) || isForbiddenAtAnyDepth(relativeStem)) {
2493
+ return json({ ok: false, error: "path_traversal", path: relativeStem });
2494
+ }
2495
+ return checkPathScope(relativeStem, projectRoot);
2496
+ }
2497
+ async function synthesizeSafely(synthesize, text) {
2498
+ try {
2499
+ const value = await synthesize(text);
2500
+ if (!(value?.audio instanceof Uint8Array) || typeof value.mimeType !== "string") {
2501
+ return { ok: false, error: "synthesis_failed", message: "provider returned no audio" };
2502
+ }
2503
+ return { audio: value.audio, mimeType: value.mimeType };
2504
+ } catch (err) {
2505
+ return { ok: false, error: "synthesis_failed", message: errorMessage(err) };
2506
+ }
2507
+ }
2508
+ function checkAudio({ audio, mimeType }, maxAudioBytes) {
2509
+ const extension = AUDIO_EXTENSIONS.get(mimeType);
2510
+ if (extension === void 0)
2511
+ return { ok: false, error: "unsupported_audio_type", mime_type: mimeType };
2512
+ if (audio.length === 0) return { ok: false, error: "empty_audio" };
2513
+ if (audio.length > maxAudioBytes) {
2514
+ return { ok: false, error: "audio_too_large", bytes: audio.length, max_bytes: maxAudioBytes };
2515
+ }
2516
+ return { extension };
2517
+ }
2518
+ async function writeAtomic(target, audio) {
2519
+ const temp = `${path.dirname(target)}/.${path.basename(target)}.${crypto.randomBytes(6).toString("hex")}.tmp`;
2520
+ try {
2521
+ await promises.writeFile(temp, audio, { flag: "wx", mode: 384 });
2522
+ await promises.rename(temp, target);
2523
+ } catch (err) {
2524
+ await promises.rm(temp, { force: true });
2525
+ throw err;
2526
+ }
2527
+ }
2528
+ async function saveClip(projectRoot, outputDir, relativePath2, { audio, mimeType }) {
2529
+ const target = pathSafety.safePathJoin(projectRoot, relativePath2);
2530
+ try {
2531
+ await promises.mkdir(path.dirname(target), { recursive: true });
2532
+ const refused = checkPathScope(outputDir, projectRoot);
2533
+ if (refused !== null) return refused;
2534
+ await writeAtomic(target, audio);
2535
+ return json({ ok: true, path: relativePath2, mime_type: mimeType, bytes: audio.length });
2536
+ } catch (err) {
2537
+ return json({ ok: false, error: "write_failed", message: errorCode(err) });
2538
+ }
2539
+ }
2540
+ function createTextToSpeechTool(opts) {
2541
+ const { synthesize, projectRoot } = opts;
2542
+ const outputDir = path.posix.normalize((opts.outputDir ?? "audio").replace(/\\/g, "/"));
2543
+ const maxTextChars = requirePositiveInteger(
2544
+ "maxTextChars",
2545
+ opts.maxTextChars ?? DEFAULT_MAX_TEXT_CHARS
2546
+ );
2547
+ const maxAudioBytes = requirePositiveInteger(
2548
+ "maxAudioBytes",
2549
+ opts.maxAudioBytes ?? DEFAULT_MAX_AUDIO_BYTES
2550
+ );
2551
+ return sdk.Tool.create({
2552
+ name: opts.name ?? "text_to_speech",
2553
+ 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.",
2554
+ inputSchema: zod.z.object({
2555
+ text: zod.z.string().min(1).describe("The text to speak."),
2556
+ filename: zod.z.string().optional().describe(
2557
+ "File name without extension (letters, digits, '.', '_', '-'). Default: derived from the text."
2558
+ )
2559
+ }),
2560
+ handler: async ({ text, filename }) => {
2561
+ if (text.length > maxTextChars) {
2562
+ return json({
2563
+ ok: false,
2564
+ error: "text_too_large",
2565
+ chars: text.length,
2566
+ max_chars: maxTextChars
2567
+ });
2568
+ }
2569
+ const stem = filename ?? defaultStem(text);
2570
+ const refused = refusePath(path.posix.join(outputDir, stem), stem, projectRoot);
2571
+ if (refused !== null) return refused;
2572
+ const synthesized = await synthesizeSafely(synthesize, text);
2573
+ if ("ok" in synthesized) return json(synthesized);
2574
+ const checked = checkAudio(synthesized, maxAudioBytes);
2575
+ if ("ok" in checked) return json(checked);
2576
+ const relativePath2 = path.posix.join(outputDir, `${stem}.${checked.extension}`);
2577
+ return saveClip(projectRoot, outputDir, relativePath2, synthesized);
2578
+ }
2579
+ });
2580
+ }
2464
2581
 
2465
2582
  // src/todo-plan-nodes.ts
2466
2583
  function todoItemsToPlanNodes(items) {
@@ -2663,7 +2780,7 @@ var MEDIA_TYPES = /* @__PURE__ */ new Map([
2663
2780
  [".webp", "image/webp"]
2664
2781
  ]);
2665
2782
  var DEFAULT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
2666
- var json = (result) => JSON.stringify(result);
2783
+ var json2 = (result) => JSON.stringify(result);
2667
2784
  function createViewImageTool(options) {
2668
2785
  const { projectRoot } = options;
2669
2786
  const maxBytes = options.maxBytes ?? DEFAULT_MAX_IMAGE_BYTES;
@@ -2677,11 +2794,11 @@ function createViewImageTool(options) {
2677
2794
  const refused = checkPathScope(input.path, projectRoot);
2678
2795
  if (refused !== null) return refused;
2679
2796
  if (isForbiddenAtAnyDepth(input.path)) {
2680
- return json({ ok: false, error: "path_traversal", path: input.path });
2797
+ return json2({ ok: false, error: "path_traversal", path: input.path });
2681
2798
  }
2682
2799
  const mediaType = MEDIA_TYPES.get(path.extname(input.path).toLowerCase());
2683
2800
  if (mediaType === void 0) {
2684
- return json({
2801
+ return json2({
2685
2802
  ok: false,
2686
2803
  error: "unsupported_image_type",
2687
2804
  path: input.path,
@@ -2690,8 +2807,8 @@ function createViewImageTool(options) {
2690
2807
  }
2691
2808
  const absolute = pathSafety.safePathJoin(projectRoot, input.path);
2692
2809
  const outcome = readWithinBudget(absolute, maxBytes);
2693
- if (!outcome.ok) return json(failureFor(outcome, input.path, maxBytes));
2694
- return json({
2810
+ if (!outcome.ok) return json2(failureFor(outcome, input.path, maxBytes));
2811
+ return json2({
2695
2812
  ok: true,
2696
2813
  path: input.path,
2697
2814
  media_type: mediaType,
@@ -2888,8 +3005,8 @@ function createBraveWebSearchAdapter(opts = {}) {
2888
3005
  headers: { "X-Subscription-Token": apiKey, Accept: "application/json" }
2889
3006
  });
2890
3007
  if (!res.ok) throw new Error(`brave_search_failed: HTTP ${res.status}`);
2891
- const json2 = await res.json();
2892
- const results = json2?.web?.results ?? [];
3008
+ const json3 = await res.json();
3009
+ const results = json3?.web?.results ?? [];
2893
3010
  return results.map((r) => ({
2894
3011
  title: String(r?.title ?? ""),
2895
3012
  url: String(r?.url ?? ""),
@@ -3051,7 +3168,9 @@ async function isBinaryFile(absolutePath) {
3051
3168
 
3052
3169
  exports.CatastrophicCommandError = CatastrophicCommandError;
3053
3170
  exports.ContextMatchError = ContextMatchError;
3171
+ exports.DEFAULT_MAX_AUDIO_BYTES = DEFAULT_MAX_AUDIO_BYTES;
3054
3172
  exports.DEFAULT_MAX_IMAGE_BYTES = DEFAULT_MAX_IMAGE_BYTES;
3173
+ exports.DEFAULT_MAX_TEXT_CHARS = DEFAULT_MAX_TEXT_CHARS;
3055
3174
  exports.DEFAULT_TOOL_GUIDANCE = DEFAULT_TOOL_GUIDANCE;
3056
3175
  exports.ReadTracker = ReadTracker;
3057
3176
  exports.ReasoningTools = ReasoningTools;
@@ -3078,6 +3197,7 @@ exports.createRunVitestTool = createRunVitestTool;
3078
3197
  exports.createSearchTextTool = createSearchTextTool;
3079
3198
  exports.createSessionArtifactStore = createSessionArtifactStore;
3080
3199
  exports.createShellTool = createShellTool;
3200
+ exports.createTextToSpeechTool = createTextToSpeechTool;
3081
3201
  exports.createTodolistTool = createTodolistTool;
3082
3202
  exports.createUpdatePlanTool = createUpdatePlanTool;
3083
3203
  exports.createViewImageTool = createViewImageTool;