@llmindset/hf-mcp 0.2.33 → 0.2.35

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/dist/docs-search/docs-semantic-search.js +1 -1
  2. package/dist/docs-search/docs-semantic-search.js.map +1 -1
  3. package/dist/hf-api-call.d.ts.map +1 -1
  4. package/dist/hf-api-call.js +4 -0
  5. package/dist/hf-api-call.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/jobs/api-client.d.ts +19 -0
  11. package/dist/jobs/api-client.d.ts.map +1 -0
  12. package/dist/jobs/api-client.js +95 -0
  13. package/dist/jobs/api-client.js.map +1 -0
  14. package/dist/jobs/commands/inspect.d.ts +5 -0
  15. package/dist/jobs/commands/inspect.d.ts.map +1 -0
  16. package/dist/jobs/commands/inspect.js +21 -0
  17. package/dist/jobs/commands/inspect.js.map +1 -0
  18. package/dist/jobs/commands/logs.d.ts +4 -0
  19. package/dist/jobs/commands/logs.d.ts.map +1 -0
  20. package/dist/jobs/commands/logs.js +24 -0
  21. package/dist/jobs/commands/logs.js.map +1 -0
  22. package/dist/jobs/commands/ps.d.ts +4 -0
  23. package/dist/jobs/commands/ps.d.ts.map +1 -0
  24. package/dist/jobs/commands/ps.js +23 -0
  25. package/dist/jobs/commands/ps.js.map +1 -0
  26. package/dist/jobs/commands/run.d.ts +5 -0
  27. package/dist/jobs/commands/run.d.ts.map +1 -0
  28. package/dist/jobs/commands/run.js +90 -0
  29. package/dist/jobs/commands/run.js.map +1 -0
  30. package/dist/jobs/commands/scheduled.d.ts +10 -0
  31. package/dist/jobs/commands/scheduled.d.ts.map +1 -0
  32. package/dist/jobs/commands/scheduled.js +112 -0
  33. package/dist/jobs/commands/scheduled.js.map +1 -0
  34. package/dist/jobs/commands/utils.d.ts +20 -0
  35. package/dist/jobs/commands/utils.d.ts.map +1 -0
  36. package/dist/jobs/commands/utils.js +120 -0
  37. package/dist/jobs/commands/utils.js.map +1 -0
  38. package/dist/jobs/formatters.d.ts +6 -0
  39. package/dist/jobs/formatters.d.ts.map +1 -0
  40. package/dist/jobs/formatters.js +98 -0
  41. package/dist/jobs/formatters.js.map +1 -0
  42. package/dist/jobs/sse-handler.d.ts +12 -0
  43. package/dist/jobs/sse-handler.d.ts.map +1 -0
  44. package/dist/jobs/sse-handler.js +80 -0
  45. package/dist/jobs/sse-handler.js.map +1 -0
  46. package/dist/jobs/tool.d.ts +35 -0
  47. package/dist/jobs/tool.d.ts.map +1 -0
  48. package/dist/jobs/tool.js +333 -0
  49. package/dist/jobs/tool.js.map +1 -0
  50. package/dist/jobs/types.d.ts +295 -0
  51. package/dist/jobs/types.d.ts.map +1 -0
  52. package/dist/jobs/types.js +95 -0
  53. package/dist/jobs/types.js.map +1 -0
  54. package/dist/tool-ids.d.ts +3 -2
  55. package/dist/tool-ids.d.ts.map +1 -1
  56. package/dist/tool-ids.js +10 -2
  57. package/dist/tool-ids.js.map +1 -1
  58. package/dist/types/tool-result.d.ts +1 -0
  59. package/dist/types/tool-result.d.ts.map +1 -1
  60. package/package.json +4 -2
  61. package/src/docs-search/docs-semantic-search.ts +1 -1
  62. package/src/hf-api-call.ts +6 -0
  63. package/src/index.ts +1 -0
  64. package/src/jobs/api-client.ts +187 -0
  65. package/src/jobs/commands/inspect.ts +38 -0
  66. package/src/jobs/commands/logs.ts +36 -0
  67. package/src/jobs/commands/ps.ts +40 -0
  68. package/src/jobs/commands/run.ts +135 -0
  69. package/src/jobs/commands/scheduled.ts +198 -0
  70. package/src/jobs/commands/utils.ts +191 -0
  71. package/src/jobs/formatters.ts +149 -0
  72. package/src/jobs/sse-handler.ts +144 -0
  73. package/src/jobs/tool.ts +435 -0
  74. package/src/jobs/types.ts +237 -0
  75. package/src/tool-ids.ts +11 -1
  76. package/src/types/tool-result.ts +6 -0
  77. package/test/jobs/command-translation.spec.ts +331 -0
  78. package/test/jobs/formatters.spec.ts +267 -0
  79. package/test/jobs/uv-command.spec.ts +81 -0
@@ -0,0 +1,144 @@
1
+ import type { LogEvent } from './types.js';
2
+
3
+ /**
4
+ * Options for fetching logs via SSE
5
+ */
6
+ export interface SseLogOptions {
7
+ /** Maximum time to collect logs in milliseconds (default: 10000 = 10s) */
8
+ maxDuration?: number;
9
+ /** Maximum number of lines to return (default: 20) */
10
+ maxLines?: number;
11
+ /** HF API token for authentication */
12
+ token?: string;
13
+ }
14
+
15
+ /**
16
+ * Result from fetching logs
17
+ */
18
+ export interface SseLogResult {
19
+ /** Log lines collected */
20
+ logs: string[];
21
+ /** Whether the job finished during collection */
22
+ finished: boolean;
23
+ /** Whether collection was truncated due to timeout */
24
+ truncated: boolean;
25
+ }
26
+
27
+ /**
28
+ * Fetch logs from a job via Server-Sent Events (SSE)
29
+ * Collects logs for a maximum duration and returns the last N lines
30
+ *
31
+ * @param url - The SSE endpoint URL for job logs
32
+ * @param options - Options for log collection
33
+ * @returns Log result with collected lines and status
34
+ */
35
+ export async function fetchJobLogs(url: string, options: SseLogOptions = {}): Promise<SseLogResult> {
36
+ const { maxDuration = 10000, maxLines = 20, token } = options;
37
+
38
+ const logLines: string[] = [];
39
+ let finished = false;
40
+ let truncated = false;
41
+
42
+ // Create abort controller for timeout
43
+ const controller = new AbortController();
44
+ const timeoutId = setTimeout(() => {
45
+ controller.abort();
46
+ truncated = true;
47
+ }, maxDuration);
48
+
49
+ try {
50
+ const headers: Record<string, string> = {
51
+ Accept: 'text/event-stream',
52
+ };
53
+ if (token) {
54
+ headers['Authorization'] = `Bearer ${token}`;
55
+ }
56
+
57
+ const response = await fetch(url, {
58
+ headers,
59
+ signal: controller.signal,
60
+ });
61
+
62
+ if (!response.ok) {
63
+ throw new Error(`Failed to fetch logs: ${response.status} ${response.statusText}`);
64
+ }
65
+
66
+ if (!response.body) {
67
+ throw new Error('Response body is null');
68
+ }
69
+
70
+ // Process the SSE stream
71
+ const reader = response.body.getReader();
72
+ const decoder = new TextDecoder();
73
+ let buffer = '';
74
+
75
+ while (true) {
76
+ const { done, value } = await reader.read();
77
+
78
+ if (done) {
79
+ break;
80
+ }
81
+
82
+ // Decode chunk and add to buffer
83
+ buffer += decoder.decode(value, { stream: true });
84
+
85
+ // Process complete lines
86
+ const lines = buffer.split('\n');
87
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
88
+
89
+ for (const line of lines) {
90
+ // SSE format: "data: {json}"
91
+ if (line.startsWith('data: ')) {
92
+ try {
93
+ const jsonStr = line.substring(6); // Remove "data: " prefix
94
+ const event = JSON.parse(jsonStr) as LogEvent;
95
+
96
+ // Filter out system messages
97
+ if (event.data.startsWith('===== Job started')) {
98
+ continue;
99
+ }
100
+
101
+ // Check for job finished message
102
+ if (event.data.startsWith('===== Job finished')) {
103
+ finished = true;
104
+ // Extract status from message if present
105
+ // e.g., "===== Job finished: status=COMPLETED ====="
106
+ logLines.push(event.data);
107
+ break;
108
+ }
109
+
110
+ // Add log line
111
+ logLines.push(event.data);
112
+ } catch {
113
+ // Ignore malformed JSON
114
+ continue;
115
+ }
116
+ }
117
+ }
118
+
119
+ // Break if job finished
120
+ if (finished) {
121
+ break;
122
+ }
123
+ }
124
+
125
+ // Close the reader
126
+ await reader.cancel();
127
+ } catch (error) {
128
+ // If aborted due to timeout, that's expected
129
+ if ((error as Error).name !== 'AbortError') {
130
+ throw error;
131
+ }
132
+ } finally {
133
+ clearTimeout(timeoutId);
134
+ }
135
+
136
+ // Return last N lines
137
+ const lastLines = logLines.slice(-maxLines);
138
+
139
+ return {
140
+ logs: lastLines,
141
+ finished,
142
+ truncated: truncated && !finished,
143
+ };
144
+ }
@@ -0,0 +1,435 @@
1
+ import { z } from 'zod';
2
+ import { JobsApiClient } from './api-client.js';
3
+ import { HfApiError } from '../hf-api-call.js';
4
+ import { runCommand, uvCommand } from './commands/run.js';
5
+ import { psCommand } from './commands/ps.js';
6
+ import { logsCommand } from './commands/logs.js';
7
+ import { inspectCommand, cancelCommand } from './commands/inspect.js';
8
+ import {
9
+ scheduledRunCommand,
10
+ scheduledUvCommand,
11
+ scheduledPsCommand,
12
+ scheduledInspectCommand,
13
+ scheduledDeleteCommand,
14
+ scheduledSuspendCommand,
15
+ scheduledResumeCommand,
16
+ } from './commands/scheduled.js';
17
+ import type { ToolResult } from '../types/tool-result.js';
18
+ import type {
19
+ RunArgs,
20
+ UvArgs,
21
+ PsArgs,
22
+ LogsArgs,
23
+ InspectArgs,
24
+ CancelArgs,
25
+ ScheduledRunArgs,
26
+ ScheduledUvArgs,
27
+ ScheduledPsArgs,
28
+ ScheduledJobArgs,
29
+ } from './types.js';
30
+
31
+ // Re-export types
32
+ export * from './types.js';
33
+ export { JobsApiClient } from './api-client.js';
34
+
35
+ // Import Zod schemas for validation
36
+ import {
37
+ runArgsSchema,
38
+ uvArgsSchema,
39
+ psArgsSchema,
40
+ logsArgsSchema,
41
+ inspectArgsSchema,
42
+ cancelArgsSchema,
43
+ scheduledRunArgsSchema,
44
+ scheduledUvArgsSchema,
45
+ scheduledPsArgsSchema,
46
+ scheduledJobArgsSchema,
47
+ } from './types.js';
48
+
49
+ /**
50
+ * Map of command names to their validation schemas
51
+ */
52
+ const COMMAND_SCHEMAS = {
53
+ run: runArgsSchema,
54
+ uv: uvArgsSchema,
55
+ ps: psArgsSchema,
56
+ logs: logsArgsSchema,
57
+ inspect: inspectArgsSchema,
58
+ cancel: cancelArgsSchema,
59
+ 'scheduled run': scheduledRunArgsSchema,
60
+ 'scheduled uv': scheduledUvArgsSchema,
61
+ 'scheduled ps': scheduledPsArgsSchema,
62
+ 'scheduled inspect': scheduledJobArgsSchema,
63
+ 'scheduled delete': scheduledJobArgsSchema,
64
+ 'scheduled suspend': scheduledJobArgsSchema,
65
+ 'scheduled resume': scheduledJobArgsSchema,
66
+ } as const;
67
+
68
+ /**
69
+ * Validate command arguments against a Zod schema
70
+ * Returns a ToolResult with detailed error message if validation fails
71
+ */
72
+ function validateArgs(
73
+ schema: z.ZodSchema,
74
+ args: unknown,
75
+ commandName: string
76
+ ): { success: true } | { success: false; errorResult: ToolResult } {
77
+ const result = schema.safeParse(args);
78
+
79
+ if (result.success) {
80
+ return { success: true };
81
+ }
82
+
83
+ // Format Zod errors into a helpful message
84
+ const errors = result.error.errors;
85
+ const missingFields: string[] = [];
86
+ const invalidFields: string[] = [];
87
+
88
+ for (const err of errors) {
89
+ const field = err.path.join('.');
90
+ if (err.code === 'invalid_type' && err.received === 'undefined') {
91
+ missingFields.push(` • ${field}: ${err.message}`);
92
+ } else {
93
+ invalidFields.push(` • ${field}: ${err.message}`);
94
+ }
95
+ }
96
+
97
+ let errorMessage = `Error: Invalid parameters for '${commandName}'\n\n`;
98
+
99
+ if (missingFields.length > 0) {
100
+ errorMessage += `Missing required parameters:\n${missingFields.join('\n')}\n\n`;
101
+ }
102
+
103
+ if (invalidFields.length > 0) {
104
+ errorMessage += `Invalid parameters:\n${invalidFields.join('\n')}\n\n`;
105
+ }
106
+
107
+ // Show what was provided
108
+ const providedKeys = args && typeof args === 'object' ? Object.keys(args) : [];
109
+ if (providedKeys.length > 0) {
110
+ errorMessage += `You provided: ${JSON.stringify(args, null, 2)}`;
111
+ } else {
112
+ errorMessage += `You provided: {} (no parameters)`;
113
+ }
114
+
115
+ return {
116
+ success: false,
117
+ errorResult: {
118
+ formatted: errorMessage,
119
+ totalResults: 0,
120
+ resultsShared: 0,
121
+ isError: true,
122
+ },
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Usage instructions when tool is called with no arguments
128
+ */
129
+ const USAGE_INSTRUCTIONS = `# HuggingFace Jobs API
130
+
131
+ Manage compute jobs on Hugging Face infrastructure.
132
+
133
+ ## Available Commands
134
+
135
+ ### Job Management
136
+ - **run** - Run a job with a Docker image
137
+ - **uv** - Run a Python script with UV (inline dependencies)
138
+ - **ps** - List jobs
139
+ - **logs** - Fetch job logs
140
+ - **inspect** - Get detailed job information
141
+ - **cancel** - Cancel a running job
142
+
143
+ ### Scheduled Jobs
144
+ - **scheduled run** - Create a scheduled job
145
+ - **scheduled uv** - Create a scheduled UV job
146
+ - **scheduled ps** - List scheduled jobs
147
+ - **scheduled inspect** - Get scheduled job details
148
+ - **scheduled delete** - Delete a scheduled job
149
+ - **scheduled suspend** - Pause a scheduled job
150
+ - **scheduled resume** - Resume a suspended job
151
+
152
+ ## Examples
153
+
154
+ ### Run a simple job
155
+ \`\`\`
156
+ # Command as array (recommended, especially for complex commands)
157
+ hf_jobs("run", {
158
+ "image": "python:3.12",
159
+ "command": ["python", "-c", "print('Hello from HF Jobs!')"],
160
+ "flavor": "cpu-basic"
161
+ })
162
+
163
+ # Command as string (parsed with POSIX shell semantics)
164
+ hf_jobs("run", {
165
+ "image": "python:3.12",
166
+ "command": "python -c \\"print('Hello world!')\\"",
167
+ "flavor": "cpu-basic"
168
+ })
169
+
170
+ # Use a Hugging Face Space as the image
171
+ hf_jobs("run", {
172
+ "image": "hf.co/spaces/username/spacename",
173
+ "command": ["python", "app.py"],
174
+ "flavor": "cpu-basic"
175
+ })
176
+ \`\`\`
177
+
178
+ ### Run multiline Python scripts
179
+ \`\`\`
180
+ # Use array format with newlines in the -c argument
181
+ hf_jobs("run", {
182
+ "image": "python:3.12",
183
+ "command": ["python", "-c", "import sys\\nprint('Line 1')\\nprint('Line 2')"],
184
+ "flavor": "cpu-basic"
185
+ })
186
+ \`\`\`
187
+
188
+ ### Run bash/shell commands
189
+ \`\`\`
190
+ hf_jobs("run", {
191
+ "image": "ubuntu:22.04",
192
+ "command": ["bash", "-c", "apt-get update && apt-get install -y curl"],
193
+ "flavor": "cpu-basic"
194
+ })
195
+ \`\`\`
196
+
197
+ ### List running jobs
198
+ \`\`\`
199
+ hf_jobs("ps")
200
+ \`\`\`
201
+
202
+ ### Get job logs
203
+ \`\`\`
204
+ hf_jobs("logs", {"job_id": "your-job-id"})
205
+ \`\`\`
206
+
207
+ ### Run with GPU
208
+ \`\`\`
209
+ hf_jobs("run", {
210
+ "image": "pytorch/pytorch:2.6.0-cuda12.4-cudnn9-devel",
211
+ "command": ["python", "train.py"],
212
+ "flavor": "a10g-small"
213
+ })
214
+ \`\`\`
215
+
216
+ ### Schedule a job
217
+ \`\`\`
218
+ hf_jobs("scheduled run", {
219
+ "schedule": "@hourly",
220
+ "image": "python:3.12",
221
+ "command": ["python", "backup.py"]
222
+ })
223
+ \`\`\`
224
+
225
+ ## Hardware Flavors
226
+
227
+ **CPU:** cpu-basic, cpu-upgrade, cpu-performance, cpu-xl
228
+ **GPU:** t4-small, t4-medium, l4x1, a10g-small, a10g-large, a100-large, h100
229
+ **Specialized:** inf2x6 (AWS Inferentia)
230
+
231
+ ## Command Format Guidelines
232
+
233
+ **Array format (recommended):**
234
+ - Use for complex commands, multiline scripts, or commands with special characters
235
+ - No quoting/escaping needed: \`["python", "-c", "print('hello')"]\`
236
+ - Works with any language: Python, bash, npm, etc.
237
+
238
+ **String format:**
239
+ - Parsed with POSIX shell semantics (quotes, escaping)
240
+ - Good for simple commands: \`"python script.py"\`
241
+ - Shell operators (|, &&, >, etc.) are NOT supported - use array with \`bash -c\` instead
242
+ - \`$HF_TOKEN\` stays literal in the command. To forward your authenticated token, add \`secrets: { "HF_TOKEN": "$HF_TOKEN" }\`.
243
+
244
+ **Multiline inline scripts:**
245
+ - Automatically wrapped in \`["/bin/sh", "-lc", "..."]\` for shell execution
246
+ - Paste entire Python/bash snippets—they execute as if typed in a shell
247
+
248
+ ## Tips
249
+
250
+ - Jobs default to detached mode (return immediately with job ID)
251
+ - Use Hub resources directly: \`load_dataset('squad')\`, \`AutoModel.from_pretrained('bert-base')\`
252
+ - To access private Hub assets, include \`secrets: { "HF_TOKEN": "$HF_TOKEN" }\` (or \`${'${HF_TOKEN}'}\`) so the server injects your bearer token.
253
+ - Logs are time-limited (10s max) - check job page for full logs
254
+ - For shell pipes/operators, use: \`["bash", "-c", "cmd1 | cmd2"]\`
255
+ `;
256
+
257
+ /**
258
+ * Jobs tool configuration
259
+ */
260
+ export const HF_JOBS_TOOL_CONFIG = {
261
+ name: 'hf_jobs',
262
+ description:
263
+ 'Manage HuggingFace compute jobs. Run any command in Docker containers (Python, Node.js, bash, etc.), ' +
264
+ 'execute Python scripts with UV package manager, manage scheduled jobs, and monitor job status and logs. ' +
265
+ 'Supports CPU and GPU hardware. Call with no arguments for full usage instructions and examples.',
266
+ schema: z.object({
267
+ command: z
268
+ .string()
269
+ .optional()
270
+ .describe(
271
+ 'Command to execute: "run", "uv", "ps", "logs", "inspect", "cancel", ' +
272
+ '"scheduled run", "scheduled uv", "scheduled ps", "scheduled inspect", ' +
273
+ '"scheduled delete", "scheduled suspend", "scheduled resume"'
274
+ ),
275
+ args: z.record(z.any()).optional().describe('Command-specific arguments as a JSON object'),
276
+ }),
277
+ annotations: {
278
+ title: 'HuggingFace Jobs',
279
+ destructiveHint: false,
280
+ readOnlyHint: false,
281
+ openWorldHint: true,
282
+ },
283
+ } as const;
284
+
285
+ /**
286
+ * Jobs tool implementation
287
+ */
288
+ export class HfJobsTool {
289
+ private client: JobsApiClient;
290
+ private hfToken?: string;
291
+ private isAuthenticated: boolean;
292
+
293
+ constructor(hfToken?: string, isAuthenticated?: boolean, namespace?: string) {
294
+ this.hfToken = hfToken;
295
+ this.isAuthenticated = isAuthenticated ?? !!hfToken;
296
+ this.client = new JobsApiClient(hfToken, namespace);
297
+ }
298
+
299
+ /**
300
+ * Execute a jobs command
301
+ */
302
+ async execute(params: { command?: string; args?: Record<string, unknown> }): Promise<ToolResult> {
303
+ // If not authenticated, show upgrade message
304
+ if (!this.isAuthenticated) {
305
+ return {
306
+ formatted:
307
+ 'Jobs are available for Pro, Team and Enterprise users. Go to https://huggingface.co/pricing to get started.',
308
+ totalResults: 0,
309
+ resultsShared: 0,
310
+ };
311
+ }
312
+
313
+ // If no command provided, return usage instructions
314
+ if (!params.command) {
315
+ return {
316
+ formatted: USAGE_INSTRUCTIONS,
317
+ totalResults: 1,
318
+ resultsShared: 1,
319
+ };
320
+ }
321
+
322
+ const command = params.command.toLowerCase();
323
+ const args = params.args || {};
324
+
325
+ // Validate command arguments if schema exists
326
+ const schema = COMMAND_SCHEMAS[command as keyof typeof COMMAND_SCHEMAS];
327
+ if (schema) {
328
+ const validation = validateArgs(schema, args, command);
329
+ if (!validation.success) {
330
+ return validation.errorResult;
331
+ }
332
+ }
333
+
334
+ try {
335
+ let result: string;
336
+
337
+ switch (command) {
338
+ case 'run':
339
+ result = await runCommand(args as RunArgs, this.client, this.hfToken);
340
+ break;
341
+
342
+ case 'uv':
343
+ result = await uvCommand(args as UvArgs, this.client, this.hfToken);
344
+ break;
345
+
346
+ case 'ps':
347
+ result = await psCommand(args as PsArgs, this.client);
348
+ break;
349
+
350
+ case 'logs':
351
+ result = await logsCommand(args as LogsArgs, this.client, this.hfToken);
352
+ break;
353
+
354
+ case 'inspect':
355
+ result = await inspectCommand(args as InspectArgs, this.client);
356
+ break;
357
+
358
+ case 'cancel':
359
+ result = await cancelCommand(args as CancelArgs, this.client);
360
+ break;
361
+
362
+ case 'scheduled run':
363
+ result = await scheduledRunCommand(args as ScheduledRunArgs, this.client, this.hfToken);
364
+ break;
365
+
366
+ case 'scheduled uv':
367
+ result = await scheduledUvCommand(args as ScheduledUvArgs, this.client, this.hfToken);
368
+ break;
369
+
370
+ case 'scheduled ps':
371
+ result = await scheduledPsCommand(args as ScheduledPsArgs, this.client);
372
+ break;
373
+
374
+ case 'scheduled inspect':
375
+ result = await scheduledInspectCommand(args as ScheduledJobArgs, this.client);
376
+ break;
377
+
378
+ case 'scheduled delete':
379
+ result = await scheduledDeleteCommand(args as ScheduledJobArgs, this.client);
380
+ break;
381
+
382
+ case 'scheduled suspend':
383
+ result = await scheduledSuspendCommand(args as ScheduledJobArgs, this.client);
384
+ break;
385
+
386
+ case 'scheduled resume':
387
+ result = await scheduledResumeCommand(args as ScheduledJobArgs, this.client);
388
+ break;
389
+
390
+ default:
391
+ return {
392
+ formatted: `Unknown command: "${params.command}"
393
+
394
+ Available commands:
395
+ - run, uv, ps, logs, inspect, cancel
396
+ - scheduled run, scheduled uv, scheduled ps, scheduled inspect, scheduled delete, scheduled suspend, scheduled resume
397
+
398
+ Call hf_jobs() with no arguments for full usage instructions.`,
399
+ totalResults: 0,
400
+ resultsShared: 0,
401
+ };
402
+ }
403
+
404
+ return {
405
+ formatted: result,
406
+ totalResults: 1,
407
+ resultsShared: 1,
408
+ };
409
+ } catch (error) {
410
+ let errorMessage = error instanceof Error ? error.message : String(error);
411
+
412
+ // If this is an HfApiError with a response body, include it
413
+ if (error instanceof HfApiError && error.responseBody) {
414
+ try {
415
+ // Try to parse and format the response body
416
+ const parsed: unknown = JSON.parse(error.responseBody);
417
+ const formattedBody = JSON.stringify(parsed, null, 2);
418
+ errorMessage += `\n\nServer response:\n${formattedBody}`;
419
+ } catch {
420
+ // If not valid JSON, include raw response (if not too long)
421
+ if (error.responseBody.length < 500) {
422
+ errorMessage += `\n\nServer response: ${error.responseBody}`;
423
+ }
424
+ }
425
+ }
426
+
427
+ return {
428
+ formatted: `Error executing ${params.command}: ${errorMessage}`,
429
+ totalResults: 0,
430
+ resultsShared: 0,
431
+ isError: true,
432
+ };
433
+ }
434
+ }
435
+ }