Skip to content

Commit d931871

Browse files
authored
Merge pull request #104 from huggingface/fix/jobs-uv-command
fix uv run image
2 parents 1124956 + c050f4e commit d931871

File tree

3 files changed

+39
-72
lines changed

3 files changed

+39
-72
lines changed

packages/mcp/src/jobs/commands/run.ts

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { RunArgs, UvArgs } from '../types.js';
22
import type { JobsApiClient } from '../api-client.js';
33
import { createJobSpec } from './utils.js';
44
import { fetchJobLogs } from '../sse-handler.js';
5+
import { buildUvCommand, UV_DEFAULT_IMAGE, wrapInlineScript } from './uv-utils.js';
56

67
/**
78
* Execute the 'run' command
@@ -64,26 +65,18 @@ To inspect: \`hf_jobs("inspect", {"job_id": "${job.id}"})\``;
6465
*/
6566
export async function uvCommand(args: UvArgs, client: JobsApiClient, token?: string): Promise<string> {
6667
// UV jobs use a standard UV image unless overridden
67-
const image = 'ghcr.io/astral-sh/uv:latest'; // Standard UV image
68+
const image = UV_DEFAULT_IMAGE;
6869

6970
// Detect script source and build command
7071
const scriptSource = args.script;
7172
let command: string | string[];
7273

73-
// Check if script is a URL
7474
if (scriptSource.startsWith('http://') || scriptSource.startsWith('https://')) {
7575
// URL - download and run
7676
command = buildUvCommand(scriptSource, args);
7777
} else if (scriptSource.includes('\n')) {
7878
// Inline multi-line script - encode it
79-
const encoded = Buffer.from(scriptSource).toString('base64');
80-
const depsPart =
81-
args.with_deps && args.with_deps.length > 0
82-
? args.with_deps.map(dep => `--with ${dep}`).join(' ')
83-
: '';
84-
const pythonPart = args.python ? `-p ${args.python}` : '';
85-
const uvArgs = [depsPart, pythonPart].filter(Boolean).join(' ');
86-
const shellSnippet = `echo "${encoded}" | base64 -d | uv run${uvArgs ? ` ${uvArgs}` : ''} -`;
79+
const shellSnippet = wrapInlineScript(scriptSource, args);
8780
command = ['/bin/sh', '-lc', shellSnippet];
8881
} else {
8982
// Assume it's a URL or path - UV will handle it
@@ -104,32 +97,3 @@ export async function uvCommand(args: UvArgs, client: JobsApiClient, token?: str
10497

10598
return runCommand(runArgs, client, token);
10699
}
107-
108-
/**
109-
* Build UV command with options
110-
*/
111-
function buildUvCommand(script: string, args: UvArgs): string {
112-
const parts: string[] = ['uv', 'run'];
113-
114-
// Add dependencies
115-
if (args.with_deps && args.with_deps.length > 0) {
116-
for (const dep of args.with_deps) {
117-
parts.push('--with', dep);
118-
}
119-
}
120-
121-
// Add Python version
122-
if (args.python) {
123-
parts.push('-p', args.python);
124-
}
125-
126-
// Add script
127-
parts.push(script);
128-
129-
// Add script arguments
130-
if (args.script_args && args.script_args.length > 0) {
131-
parts.push(...args.script_args);
132-
}
133-
134-
return parts.join(' ');
135-
}

packages/mcp/src/jobs/commands/scheduled.ts

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
import type { JobsApiClient } from '../api-client.js';
99
import { formatScheduledJobsTable, formatScheduledJobDetails } from '../formatters.js';
1010
import { createJobSpec } from './utils.js';
11+
import { buildUvCommand, UV_DEFAULT_IMAGE, wrapInlineScript } from './uv-utils.js';
1112

1213
/**
1314
* Execute 'scheduled run' command
@@ -60,7 +61,7 @@ export async function scheduledUvCommand(
6061
token?: string
6162
): Promise<string> {
6263
// For UV, use standard UV image
63-
const image = 'ghcr.io/astral-sh/uv:python3.12-bookworm';
64+
const image = UV_DEFAULT_IMAGE;
6465

6566
// Build UV command (similar to regular uv command)
6667
const scriptSource = args.script;
@@ -69,10 +70,7 @@ export async function scheduledUvCommand(
6970
if (scriptSource.startsWith('http://') || scriptSource.startsWith('https://')) {
7071
command = buildUvCommand(scriptSource, args);
7172
} else if (scriptSource.includes('\n')) {
72-
const encoded = Buffer.from(scriptSource).toString('base64');
73-
const deps =
74-
args.with_deps && args.with_deps.length > 0 ? args.with_deps.map((dep) => `--with ${dep}`).join(' ') + ' ' : '';
75-
command = `echo "${encoded}" | base64 -d | uv run ${deps}${args.python ? `-p ${args.python}` : ''} -`;
73+
command = wrapInlineScript(scriptSource, args);
7674
} else {
7775
command = buildUvCommand(scriptSource, args);
7876
}
@@ -94,34 +92,6 @@ export async function scheduledUvCommand(
9492
return scheduledRunCommand(scheduledRunArgs, client, token);
9593
}
9694

97-
/**
98-
* Build UV command with options
99-
*/
100-
function buildUvCommand(
101-
script: string,
102-
args: { with_deps?: string[]; python?: string; script_args?: string[] }
103-
): string {
104-
const parts: string[] = ['uv', 'run'];
105-
106-
if (args.with_deps && args.with_deps.length > 0) {
107-
for (const dep of args.with_deps) {
108-
parts.push('--with', dep);
109-
}
110-
}
111-
112-
if (args.python) {
113-
parts.push('-p', args.python);
114-
}
115-
116-
parts.push(script);
117-
118-
if (args.script_args && args.script_args.length > 0) {
119-
parts.push(...args.script_args);
120-
}
121-
122-
return parts.join(' ');
123-
}
124-
12595
/**
12696
* Execute 'scheduled ps' command
12797
* Lists scheduled jobs
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { UvArgs } from '../types.js';
2+
3+
export const UV_DEFAULT_IMAGE = 'ghcr.io/astral-sh/uv:python3.12-bookworm';
4+
5+
type UvCommandOptions = Pick<UvArgs, 'with_deps' | 'python' | 'script_args'>;
6+
7+
export function buildUvCommand(script: string, args: UvCommandOptions): string {
8+
const parts: string[] = ['uv', 'run'];
9+
10+
if (args.with_deps && args.with_deps.length > 0) {
11+
for (const dep of args.with_deps) {
12+
parts.push('--with', dep);
13+
}
14+
}
15+
16+
if (args.python) {
17+
parts.push('-p', args.python);
18+
}
19+
20+
parts.push(script);
21+
22+
if (args.script_args && args.script_args.length > 0) {
23+
parts.push(...args.script_args);
24+
}
25+
26+
return parts.join(' ');
27+
}
28+
29+
export function wrapInlineScript(script: string, args: UvCommandOptions): string {
30+
const encoded = Buffer.from(script).toString('base64');
31+
const baseCommand = buildUvCommand('-', args);
32+
return `echo "${encoded}" | base64 -d | ${baseCommand}`;
33+
}

0 commit comments

Comments
 (0)