tune-sdk 0.2.4 → 0.2.5

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.
Files changed (79) hide show
  1. package/.github/workflows/publish.yml +27 -0
  2. package/README.md +2 -1
  3. package/package.json +1 -1
  4. package/tools/README.md +0 -636
  5. package/tools/append.schema.json +0 -17
  6. package/tools/append.tool.mjs +0 -5
  7. package/tools/brave.schema.json +0 -13
  8. package/tools/brave.tool.mjs +0 -32
  9. package/tools/claude.txt +0 -1
  10. package/tools/clipboard.txt +0 -1
  11. package/tools/default.llm.js +0 -2
  12. package/tools/dev.txt +0 -6
  13. package/tools/echo.txt +0 -1
  14. package/tools/editor-filename.chat +0 -10
  15. package/tools/gemini-dev.txt +0 -7
  16. package/tools/gemini25.txt +0 -1
  17. package/tools/gemini_files.schema.json +0 -13
  18. package/tools/gemini_files.tool.mjs +0 -204
  19. package/tools/gemini_ocr.schema.json +0 -21
  20. package/tools/gemini_ocr.tool.mjs +0 -211
  21. package/tools/gemini_tts.schema.json +0 -59
  22. package/tools/gemini_tts.tool.mjs +0 -175
  23. package/tools/gemini_veo.schema.json +0 -12
  24. package/tools/gemini_veo.tool.mjs +0 -233
  25. package/tools/groq_whisper.schema.json +0 -48
  26. package/tools/groq_whisper.tool.mjs +0 -59
  27. package/tools/head.proc.js +0 -24
  28. package/tools/init.proc.js +0 -19
  29. package/tools/jina_r.schema.json +0 -21
  30. package/tools/jina_r.tool.mjs +0 -27
  31. package/tools/js.schema.json +0 -19
  32. package/tools/js.tool.mjs +0 -60
  33. package/tools/json_format.proc.mjs +0 -22
  34. package/tools/linenum.proc.js +0 -21
  35. package/tools/list.schema.json +0 -19
  36. package/tools/list.tool.mjs +0 -20
  37. package/tools/llm-utils.js +0 -150
  38. package/tools/log.proc.js +0 -15
  39. package/tools/mcp.proc.mjs +0 -174
  40. package/tools/message.schema.json +0 -21
  41. package/tools/message.tool.js +0 -14
  42. package/tools/mock.proc.js +0 -35
  43. package/tools/nu.schema.json +0 -13
  44. package/tools/nu.tool.mjs +0 -14
  45. package/tools/openai.js +0 -27
  46. package/tools/openai_imgen.schema.json +0 -35
  47. package/tools/openai_imgen.tool.mjs +0 -83
  48. package/tools/openai_stt.schema.json +0 -49
  49. package/tools/openai_stt.tool.mjs +0 -66
  50. package/tools/openai_tts.schema.json +0 -26
  51. package/tools/openai_tts.tool.mjs +0 -26
  52. package/tools/osa.schema.json +0 -13
  53. package/tools/osa.tool.mjs +0 -12
  54. package/tools/package.json +0 -7
  55. package/tools/patch.schema.json +0 -17
  56. package/tools/patch.tool.mjs +0 -38
  57. package/tools/prop.proc.mjs +0 -34
  58. package/tools/py.schema.json +0 -17
  59. package/tools/py.tool.py +0 -22
  60. package/tools/queryimage.schema.json +0 -17
  61. package/tools/queryimage.tool.chat +0 -4
  62. package/tools/resolve.proc.js +0 -10
  63. package/tools/rf.schema.json +0 -17
  64. package/tools/rf.tool.mjs +0 -21
  65. package/tools/schema.schema.json +0 -13
  66. package/tools/schema.tool.chat +0 -81
  67. package/tools/sh.schema.json +0 -13
  68. package/tools/sh.tool.mjs +0 -12
  69. package/tools/short.txt +0 -1
  70. package/tools/shp.proc.mjs +0 -31
  71. package/tools/slice.proc.js +0 -55
  72. package/tools/tail.proc.js +0 -35
  73. package/tools/text.proc.js +0 -13
  74. package/tools/turn.schema.json +0 -17
  75. package/tools/turn.tool.mjs +0 -8
  76. package/tools/wf.schema.json +0 -17
  77. package/tools/wf.tool.mjs +0 -16
  78. package/tools/yandex_tts.schema.json +0 -41
  79. package/tools/yandex_tts.tool.mjs +0 -31
@@ -1,13 +0,0 @@
1
- {
2
- "description": "Search the internet with brave search",
3
- "parameters": {
4
- "type": "object",
5
- "properties": {
6
- "text": {
7
- "type": "string",
8
- "description": "Query to search"
9
- }
10
- },
11
- "required": ["text"]
12
- }
13
- }
@@ -1,32 +0,0 @@
1
- export default async function brave({text}, ctx) {
2
- const key = await ctx.read("BRAVE_KEY");
3
-
4
- if (!key) {
5
- throw new Error("BRAVE_KEY not found in context. Please set up your Brave Search API key.");
6
- }
7
-
8
- if (!text) {
9
- throw new Error("Query parameter is required.");
10
- }
11
-
12
- const encodedQuery = encodeURIComponent(text);
13
- const url = `https://api.search.brave.com/res/v1/web/search?q=${encodedQuery}`;
14
-
15
- const response = await fetch(url, {
16
- method: 'GET',
17
- headers: {
18
- 'Accept': 'application/json',
19
- 'X-Subscription-Token': key
20
- }
21
- });
22
-
23
-
24
- if (!response.ok) {
25
- throw new Error(`Brave Search API returned ${response.status}: ${response.statusText}\n`);
26
- }
27
-
28
- const data = await response.json();
29
- return data.web.results.map(item =>
30
- `${item.title}\n${item.url}\n${item.description}`
31
- ).join("\n\n").replace(/@/g, "\\@");
32
- }
package/tools/claude.txt DELETED
@@ -1 +0,0 @@
1
- @claude-sonnet-4-20250514
@@ -1 +0,0 @@
1
- @{|shp pbpaste }
@@ -1,2 +0,0 @@
1
- module.exports = require("./openai")({ model: "gpt-4.1-mini" })
2
-
package/tools/dev.txt DELETED
@@ -1,6 +0,0 @@
1
- @claude-sonnet-4-20250514
2
- @rf @wf @sh @patch
3
- current filestructure:
4
- ```
5
- @{| shp git ls-files }
6
- ```
package/tools/echo.txt DELETED
@@ -1 +0,0 @@
1
- you are echo, you print everything back
@@ -1,10 +0,0 @@
1
- s: @{ gpt-4.1-mini | json_format }
2
- You are giving name for chat based on the chat contents
3
- chat file content:
4
- ```
5
- @{ editor/buffer | head 100}
6
- ```
7
- example json output:
8
- { "filename": "how-to-make-hero-costume.chat"}
9
- or
10
- { "filename": "accounting-database-zig.chat"}
@@ -1,7 +0,0 @@
1
- @rf @wf @sh @@gemini25 @patch
2
-
3
- Use sh tool to know more about environment you are in
4
- use rf if you need to read source code of a file
5
- use wf if you need to create a new file or overwrite existing
6
- use patch for small changes in a file
7
- Do not include code into your answers except using wf tool
@@ -1 +0,0 @@
1
- @google/gemini-2.5-pro-exp-03-25
@@ -1,13 +0,0 @@
1
- {
2
- "description": "CLI-style wrapper around the Gemini Files API. Put a command in the \"text\" field (e.g. \"upload './path/to/file.j'pg\", \"list\", \"get files/abc123\", \"delete files/abc123\"). Returned value is a human-readable string or JSON for list/get/upload operations.",
3
- "parameters": {
4
- "type": "object",
5
- "properties": {
6
- "text": {
7
- "type": "string",
8
- "description": "Command to execute. Allowed commands: upload <filepath> [displayName], delete <file_name>, get <file_name>, list, help."
9
- }
10
- },
11
- "required": ["text"]
12
- }
13
- }
@@ -1,204 +0,0 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
-
4
- /**
5
- * Lightweight CLI-style wrapper around Gemini Files API.
6
- * It exposes the most common operations (upload, list, get, delete)
7
- * through a single `text` parameter that looks like a shell command.
8
- *
9
- * Examples
10
- * upload ./assets/photo.jpg -> uploads the file
11
- * upload ./assets/photo.jpg "My photo" -> uploads with display name
12
- * list -> lists uploaded files (first page)
13
- * get files/abc123def -> shows metadata for a file
14
- * delete files/abc123def -> deletes a file
15
- * help -> prints usage
16
- *
17
- * The function returns a human-readable string describing the result
18
- * (or JSON pretty-printed for list/get/upload responses).
19
- */
20
- export default async function geminiFilesCli({ text }, ctx) {
21
- // -------------------------------------------------------------
22
- // 1. Helpers
23
- // -------------------------------------------------------------
24
- const usage = () => `Gemini Files CLI
25
- Usage:
26
- upload <filepath> [displayName] Upload local file and return metadata
27
- delete <file_name> Delete file by name/id (e.g. files/abc123)
28
- get <file_name> Show metadata for file (e.g. files/abc123)
29
- list List uploaded files (first page)
30
- help Show this message
31
- Examples:
32
- upload ./samples/song.mp3
33
- upload ./samples/doc.pdf "My Doc"
34
- delete files/abc123def
35
- get files/abc123def
36
- list
37
- `;
38
-
39
- const EXTENSION_TO_MIME = {
40
- // Documents
41
- '.pdf': 'application/pdf',
42
- '.js': 'application/x-javascript',
43
- '.mjs': 'application/x-javascript',
44
- '.cjs': 'application/x-javascript',
45
- '.py': 'text/x-python',
46
- '.txt': 'text/plain',
47
- '.html': 'text/html',
48
- '.htm': 'text/html',
49
- '.css': 'text/css',
50
- '.md': 'text/markdown',
51
- '.markdown': 'text/markdown',
52
- '.csv': 'text/csv',
53
- '.xml': 'text/xml',
54
- '.rtf': 'text/rtf',
55
-
56
- // Images
57
- '.png': 'image/png',
58
- '.jpg': 'image/jpeg',
59
- '.jpeg': 'image/jpeg',
60
- '.webp': 'image/webp',
61
- '.heic': 'image/heic',
62
- '.heif': 'image/heif',
63
-
64
- // Videos
65
- '.mp4': 'video/mp4',
66
- '.mpeg': 'video/mpeg',
67
- '.mpg': 'video/mpeg',
68
- '.mov': 'video/quicktime',
69
- '.avi': 'video/avi',
70
- '.flv': 'video/x-flv',
71
- '.webm': 'video/webm',
72
- '.wmv': 'video/x-ms-wmv',
73
- '.3gp': 'video/3gpp',
74
- '.3gpp': 'video/3gpp',
75
-
76
- // Audio
77
- '.wav': 'audio/wav',
78
- '.mp3': 'audio/mpeg',
79
- '.aiff': 'audio/aiff',
80
- '.aac': 'audio/aac',
81
- '.ogg': 'audio/ogg',
82
- '.flac': 'audio/flac'
83
- };
84
-
85
- function pretty(json) {
86
- return JSON.stringify(json, null, 2);
87
- }
88
-
89
- // -------------------------------------------------------------
90
- // 2. Setup / validation
91
- // -------------------------------------------------------------
92
- if (!text || typeof text !== 'string' || text.trim() === '') {
93
- return usage();
94
- }
95
-
96
- const key = await ctx.read('GEMINI_KEY');
97
- if (!key) {
98
- throw new Error('GEMINI_KEY not found in environment. Set it in your .env file.');
99
- } /**
100
- * Split cmd line into args (spaces outside quotes don't break up arguments)
101
- * e.g. upload "foo bar.mp4" "My Title" -> [upload, foo bar.mp4, My Title]
102
- */
103
- function parseArgsWithQuotes(input) {
104
- const re = /"([^"]*)"|'([^']*)'|`([^`]*)`|([^\s]+)/g;
105
- const args = [];
106
- let match;
107
- while ((match = re.exec(input))) {
108
- args.push(match[1] || match[2] || match[3] || match[4]);
109
- }
110
- return args;
111
- }
112
-
113
- const args = parseArgsWithQuotes(text.trim());
114
- const [cmd, ...rest] = args;
115
- const command = cmd?.toLowerCase();
116
-
117
- // -------------------------------------------------------------
118
- // 3. Command handlers
119
- // -------------------------------------------------------------
120
- switch (command) {
121
- case 'help':
122
- return usage();
123
-
124
- case 'list': {
125
- const url = `https://generativelanguage.googleapis.com/v1beta/files?key=${key}`;
126
- const resp = await fetch(url);
127
- if (!resp.ok) {
128
- throw new Error(`Gemini API list failed (${resp.status}): ${await resp.text()}`);
129
- }
130
- const json = await resp.json();
131
- return pretty(json);
132
- }
133
-
134
- case 'get': {
135
- if (rest.length === 0) {
136
- return 'Error: "get" requires a file name.\n' + usage();
137
- }
138
- const name = rest[0];
139
- const url = `https://generativelanguage.googleapis.com/v1beta/${name}?key=${key}`;
140
- const resp = await fetch(url);
141
- if (!resp.ok) {
142
- throw new Error(`Gemini API get failed (${resp.status}): ${await resp.text()}`);
143
- }
144
- const json = await resp.json();
145
- return pretty(json);
146
- }
147
-
148
- case 'delete': {
149
- if (rest.length === 0) {
150
- return 'Error: "delete" requires a file name.\n' + usage();
151
- }
152
- const name = rest[0];
153
- const url = `https://generativelanguage.googleapis.com/v1beta/${name}?key=${key}`;
154
- const resp = await fetch(url, { method: 'DELETE' });
155
- if (!resp.ok) {
156
- throw new Error(`Gemini API delete failed (${resp.status}): ${await resp.text()}`);
157
- }
158
- return `Deleted ${name}`;
159
- }
160
-
161
- case 'upload': {
162
- if (rest.length === 0) {
163
- return 'Error: "upload" requires a local file path.\n' + usage();
164
- }
165
- const filepath = rest[0];
166
- const displayName = rest.slice(1).join(' ') || path.basename(filepath);
167
-
168
- let fileBuffer;
169
- let stats;
170
- try {
171
- stats = await fs.stat(filepath);
172
- fileBuffer = await fs.readFile(filepath);
173
- } catch (err) {
174
- throw new Error(`Cannot read file ${filepath}: ${err.message}`);
175
- }
176
-
177
- const ext = path.extname(filepath).toLowerCase();
178
- const mimeType = EXTENSION_TO_MIME[ext] || 'application/octet-stream';
179
-
180
- // 3.1 Perform raw upload.
181
- const uploadUrl = `https://generativelanguage.googleapis.com/upload/v1beta/files?uploadType=media&key=${key}`;
182
- const resp = await fetch(uploadUrl, {
183
- method: 'POST',
184
- headers: {
185
- 'Content-Type': mimeType,
186
- 'X-Goog-Upload-Protocol': 'raw',
187
- 'X-Goog-Upload-Header-Content-Length': stats.size.toString(),
188
- // Attach metadata via JSON content header? Docs suggest passing metadata in body for resumable.
189
- // For raw simple upload the display_name is auto-derived; we will ignore displayName here.
190
- },
191
- body: fileBuffer
192
- });
193
-
194
- if (!resp.ok) {
195
- throw new Error(`Gemini API upload failed (${resp.status}): ${await resp.text()}`);
196
- }
197
- const json = await resp.json();
198
- return pretty(json);
199
- }
200
-
201
- default:
202
- return `Unknown command: ${command}\n` + usage();
203
- }
204
- }
@@ -1,21 +0,0 @@
1
- {
2
- "description": "Process a local file (image, audio, PDF, or text-based documents) using the Gemini API. Provide the file path and an instruction or question. Supported image types: PNG, JPEG, WEBP, HEIC, HEIF. Supported audio types: WAV, MP3, AIFF, AAC, OGG, FLAC. Supported document types: PDF, TXT, HTML, HTM, MD, PY, JS, JSON, CSS, JAVA, C, CS, CPP, GO, PHP, RB, TS, XML, YAML, CSV, TSV, RTF. or video mp4 etc mov",
3
- "parameters": {
4
- "type": "object",
5
- "properties": {
6
- "filename": {
7
- "type": "string",
8
- "description": "Path to the local file (image, audio, PDF, document)"
9
- },
10
- "model": {
11
- "type": "string",
12
- "description": "Gemini model to use for the query, default is gemini-2.0-flash"
13
- },
14
- "text": {
15
- "type": "string",
16
- "description": "Instruction or question about the file (e.g., 'Extract all text', 'Summarize this', 'Transcribe audio')"
17
- }
18
- },
19
- "required": ["filename", "text"]
20
- }
21
- }
@@ -1,211 +0,0 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
-
4
- /**
5
- * Process a local file (image, audio, document, etc.) using Gemini API
6
- *
7
- * @param {Object} params
8
- * @param {string} params.filename - Local path to the file
9
- * @param {string} params.text - Instruction or question about the file content (e.g. "Extract text", "Summarize", "Transcribe audio", "What is this image?")
10
- * @param {Object} ctx - Tune context object
11
- * @returns {Promise<string>} Result text from the API
12
- */
13
- export default async function geminiFileProcessor({ filename, text, model }, ctx) {
14
- const key = await ctx.read('GEMINI_KEY');
15
- if (!key) {
16
- throw new Error('GEMINI_KEY not found in environment. Please set it in your .env file.');
17
- }
18
-
19
- model = model || "gemini-2.5-pro-preview-03-25";
20
-
21
- const EXTENSION_TO_MIME = {
22
- // Documents
23
- '.pdf': 'application/pdf',
24
- '.js': 'application/x-javascript',
25
- '.mjs': 'application/x-javascript',
26
- '.cjs': 'application/x-javascript',
27
- '.py': 'text/x-python',
28
- '.txt': 'text/plain',
29
- '.html': 'text/html',
30
- '.htm': 'text/html',
31
- '.css': 'text/css',
32
- '.md': 'text/md',
33
- '.markdown': 'text/md',
34
- '.csv': 'text/csv',
35
- '.xml': 'text/xml',
36
- '.rtf': 'text/rtf',
37
-
38
- // Images
39
- '.png': 'image/png',
40
- '.jpg': 'image/jpeg',
41
- '.jpeg': 'image/jpeg',
42
- '.webp': 'image/webp',
43
- '.heic': 'image/heic',
44
- '.heif': 'image/heif',
45
-
46
- // Videos
47
- '.mp4': 'video/mp4',
48
- '.mpeg': 'video/mpeg',
49
- '.mpg': 'video/mpg',
50
- '.mov': 'video/mov',
51
- '.avi': 'video/avi',
52
- '.flv': 'video/x-flv',
53
- '.webm': 'video/webm',
54
- '.wmv': 'video/wmv',
55
- '.3gp': 'video/3gpp',
56
- '.3gpp': 'video/3gpp',
57
-
58
- // Audio
59
- '.wav': 'audio/wav',
60
- '.mp3': 'audio/mp3',
61
- '.aiff': 'audio/aiff',
62
- '.aac': 'audio/aac',
63
- '.ogg': 'audio/ogg',
64
- '.flac': 'audio/flac'
65
- };
66
- // Determine if "filename" is a local path or a Gemini Files reference/URI
67
- const isRemote = /^https?:\/\//i.test(filename) || filename.startsWith('files/');
68
-
69
- const FILE_SIZE_THRESHOLD = 10 * 1024 * 1024; // 10 MB
70
- let parts;
71
- let mimeType;
72
-
73
- if (isRemote) {
74
- // ---------------------- Remote (Gemini Files) ----------------------
75
- // Extract the canonical file name ("files/abc123") so we can query metadata
76
- let fileName = filename;
77
- if (!fileName.startsWith('files/')) {
78
- // try to extract between /files/ and the next /
79
- const m = filename.match(/\/files\/([^/?]+)/i);
80
- if (!m) {
81
- throw new Error('Could not extract Gemini file name (files/ID) from URI');
82
- }
83
- fileName = `files/${m[1]}`;
84
- }
85
-
86
- // Poll until state == ACTIVE (max 60s)
87
- const metadataEndpoint = `https://generativelanguage.googleapis.com/v1beta/${fileName}`;
88
- const deadline = Date.now() + 60000;
89
- let fileMeta;
90
- while (Date.now() < deadline) {
91
- const resp = await fetch(`${metadataEndpoint}?key=${key}`);
92
- if (!resp.ok) {
93
- const errTxt = await resp.text();
94
- throw new Error(`Failed to fetch file metadata (${resp.status}): ${errTxt}`);
95
- }
96
- const json = await resp.json();
97
- fileMeta = json.file || json;
98
- if (fileMeta.state === 'ACTIVE') break;
99
- await new Promise(r => setTimeout(r, 1000));
100
- }
101
- if (!fileMeta || fileMeta.state !== 'ACTIVE') {
102
- throw new Error(`File ${fileName} is not ACTIVE (state=${fileMeta?.state}). Try again later.`);
103
- }
104
-
105
- const fileUri = fileMeta.uri || fileMeta.fileUri;
106
- mimeType = fileMeta.mimeType || fileMeta.mime_type;
107
- if (!fileUri || !mimeType) {
108
- throw new Error(`Could not determine uri/mimeType from metadata: ${JSON.stringify(fileMeta)}`);
109
- }
110
-
111
- parts = [
112
- { file_data: { mime_type: mimeType, file_uri: fileUri } },
113
- { text }
114
- ];
115
- } else {
116
- // --------------------------- Local file ---------------------------
117
- const ext = path.extname(filename).toLowerCase();
118
- mimeType = EXTENSION_TO_MIME[ext];
119
- if (!mimeType) {
120
- throw new Error(`Unsupported or unknown file extension: ${ext}. Gemini only supports specific MIME types.`);
121
- }
122
-
123
- // Get file size
124
- let stats;
125
- try {
126
- stats = await fs.stat(filename);
127
- } catch (err) {
128
- throw new Error(`Error stating file ${filename}: ${err.message}`);
129
- }
130
- const fileSize = stats.size;
131
-
132
- if (fileSize > FILE_SIZE_THRESHOLD) {
133
- throw new Error(`File ${filename} is larger than 10 MB. Please upload it first using gemini_files tool and then pass its uri/name here.`);
134
- }
135
-
136
- let fileBuffer;
137
- try {
138
- fileBuffer = await fs.readFile(filename);
139
- } catch (err) {
140
- throw new Error(`Error reading file ${filename}: ${err.message}`);
141
- }
142
- const encodedData = fileBuffer.toString('base64');
143
- parts = [
144
- { inline_data: { mime_type: mimeType, data: encodedData } },
145
- { text }
146
- ];
147
- }
148
-
149
- const body = {
150
- contents: [{ parts }]
151
- // generationConfig can be added here if needed
152
- };
153
-
154
- const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${key}`;
155
-
156
- let response;
157
- try {
158
- response = await fetch(url, {
159
- method: 'POST',
160
- headers: { 'Content-Type': 'application/json' },
161
- body: JSON.stringify(body)
162
- });
163
- } catch (err) {
164
- throw new Error(`Network error calling Gemini API: ${err.message}`);
165
- }
166
-
167
- if (!response.ok) {
168
- const errorText = await response.text();
169
- let detailedError = errorText;
170
- try {
171
- // Try to parse the JSON error response from Gemini for better details
172
- const errorJson = JSON.parse(errorText);
173
- detailedError = JSON.stringify(errorJson.error || errorJson, null, 2);
174
- } catch (parseError) {
175
- // Ignore if parsing fails, just use the raw text
176
- }
177
- throw new Error(`Gemini API Error ${response.status}: ${detailedError}`);
178
- }
179
-
180
- const json = await response.json();
181
-
182
- try {
183
- // Extract the response text, handling potential variations in structure
184
- const candidates = json?.candidates;
185
- if (!candidates || candidates.length === 0) {
186
- return `Gemini API returned no candidates. Full response: ${JSON.stringify(json, null, 2)}`;
187
- }
188
-
189
- // Check for issues like finishReason other than "STOP" or safety blocks
190
- const candidate = candidates[0];
191
- const finishReason = candidate?.finishReason;
192
- const safetyRatings = candidate?.safetyRatings;
193
-
194
- if (finishReason && finishReason !== "STOP" && finishReason !== "MAX_TOKENS") {
195
- return `Gemini API finished prematurely. Reason: ${finishReason}. Safety Ratings: ${JSON.stringify(safetyRatings)}. Full response: ${JSON.stringify(json, null, 2)}`;
196
- }
197
-
198
- const content = candidate?.content;
199
- if (!content || !content.parts || content.parts.length === 0) {
200
- // If no parts but finished normally, might be an empty response or an issue
201
- return `Gemini API returned no text parts (Finish Reason: ${finishReason}). Full response: ${JSON.stringify(json, null, 2)}`;
202
- }
203
-
204
- // Concatenate text from all parts
205
- return content.parts.map(p => p.text).join('\n').trim();
206
-
207
- } catch (e) {
208
- console.error("Error processing Gemini response:", e);
209
- return `Error processing response structure. Full response: ${JSON.stringify(json, null, 2)}`;
210
- }
211
- }
@@ -1,59 +0,0 @@
1
- {
2
- "description": "Generate speech from text using Gemini's Text-to-Speech API. Supports both single-speaker and multi-speaker audio generation with controllable style, tone, accent, and pace using natural language prompts.",
3
- "parameters": {
4
- "type": "object",
5
- "properties": {
6
- "text": {
7
- "type": "string",
8
- "description": "Text to convert to speech. For single-speaker, you can include style instructions like 'Say cheerfully: Have a wonderful day!' For multi-speaker, format as dialogue with speaker names, e.g., 'TTS the following conversation between Joe and Jane: Joe: How's it going? Jane: Not too bad!'"
9
- },
10
- "filename": {
11
- "type": "string",
12
- "description": "Output filename for the audio file. Will automatically add .wav extension if not present."
13
- },
14
- "model": {
15
- "type": "string",
16
- "enum": ["gemini-2.5-flash-preview-tts", "gemini-2.5-pro-preview-tts"],
17
- "description": "Gemini model to use for TTS generation. Default is gemini-2.5-flash-preview-tts"
18
- },
19
- "voice": {
20
- "type": "string",
21
- "enum": [
22
- "Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda",
23
- "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus", "Iapetus",
24
- "Umbriel", "Algieba", "Despina", "Erinome", "Algenib", "Rasalgethi",
25
- "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux", "Pulcherrima",
26
- "Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager", "Sulafat"
27
- ],
28
- "description": "Voice name for single-speaker mode. Each voice has different characteristics: Zephyr (Bright), Puck (Upbeat), Charon (Informative), Kore (Firm), etc. Only used when speakers array is not provided."
29
- },
30
- "speakers": {
31
- "type": "array",
32
- "description": "Array of speaker configurations for multi-speaker mode (max 2 speakers). Each speaker should have 'name' and 'voice' properties. Speaker names must match those used in the text dialogue.",
33
- "items": {
34
- "type": "object",
35
- "properties": {
36
- "name": {
37
- "type": "string",
38
- "description": "Name of the speaker as used in the dialogue text"
39
- },
40
- "voice": {
41
- "type": "string",
42
- "enum": [
43
- "Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda",
44
- "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus", "Iapetus",
45
- "Umbriel", "Algieba", "Despina", "Erinome", "Algenib", "Rasalgethi",
46
- "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux", "Pulcherrima",
47
- "Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager", "Sulafat"
48
- ],
49
- "description": "Voice name for this speaker"
50
- }
51
- },
52
- "required": ["name", "voice"]
53
- },
54
- "maxItems": 2
55
- }
56
- },
57
- "required": ["text", "filename"]
58
- }
59
- }