Skip to content

Commit b263dd3

Browse files
authored
Merge pull request #143 from NianJiuZst/feat/sdk-file-module
feat: add FileSDK module for file storage operations
2 parents c9d2ea6 + 89b3d5c commit b263dd3

3 files changed

Lines changed: 110 additions & 0 deletions

File tree

src/sdk/file/index.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { existsSync } from 'node:fs';
2+
import { readFile } from 'node:fs/promises';
3+
import { resolve, basename } from 'node:path';
4+
import { Client } from '../client';
5+
import {
6+
fileUploadEndpoint,
7+
fileListEndpoint,
8+
fileDeleteEndpoint,
9+
fileRetrieveEndpoint,
10+
} from '../../client/endpoints';
11+
import type {
12+
FileUploadResponse,
13+
FileListResponse,
14+
FileDeleteResponse,
15+
FileRetrieveResponse,
16+
} from '../../types/api';
17+
import { SDKError } from '../../errors/base';
18+
import { ExitCode } from '../../errors/codes';
19+
20+
export class FileSDK extends Client {
21+
/**
22+
* Upload a file to MiniMax storage.
23+
*
24+
* @param filePath - Absolute or relative path to the file on disk.
25+
* @param purpose - File purpose, defaults to `"retrieval"`.
26+
*/
27+
async upload(filePath: string, purpose = 'retrieval'): Promise<FileUploadResponse> {
28+
const fullPath = resolve(filePath);
29+
if (!existsSync(fullPath)) {
30+
throw new SDKError(`File not found: ${fullPath}`, ExitCode.USAGE);
31+
}
32+
33+
const fileData = await readFile(fullPath);
34+
const fileName = basename(fullPath);
35+
36+
const formData = new FormData();
37+
formData.append('file', new Blob([fileData]), fileName);
38+
formData.append('purpose', purpose);
39+
40+
const url = fileUploadEndpoint(this.config.baseUrl);
41+
return this.requestJson<FileUploadResponse>({
42+
url,
43+
method: 'POST',
44+
body: formData,
45+
});
46+
}
47+
48+
/** List all files in MiniMax storage. */
49+
async list(): Promise<FileListResponse> {
50+
const url = fileListEndpoint(this.config.baseUrl);
51+
return this.requestJson<FileListResponse>({ url, method: 'GET' });
52+
}
53+
54+
/**
55+
* Delete a file from MiniMax storage by its file ID.
56+
*
57+
* @param fileId - The ID of the file to delete (string or number).
58+
*/
59+
async delete(fileId: string | number): Promise<FileDeleteResponse> {
60+
const url = fileDeleteEndpoint(this.config.baseUrl);
61+
return this.requestJson<FileDeleteResponse>({
62+
url,
63+
method: 'POST',
64+
body: { file_id: Number(fileId) },
65+
});
66+
}
67+
68+
/**
69+
* Retrieve metadata (and optional download URL) for a file.
70+
*
71+
* @param fileId - The ID of the file to retrieve.
72+
*/
73+
async retrieve(fileId: string): Promise<FileRetrieveResponse> {
74+
const url = fileRetrieveEndpoint(this.config.baseUrl, fileId);
75+
return this.requestJson<FileRetrieveResponse>({ url, method: 'GET' });
76+
}
77+
}

src/sdk/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { MusicSDK } from "./music";
66
import { SearchSDK } from "./search";
77
import { VisionSDK } from "./vision";
88
import { QuotaSDK } from "./quota";
9+
import { FileSDK } from "./file";
910
import { Client } from "./client";
1011
import { MiniMaxSDKOptions } from "./types";
1112

@@ -18,6 +19,7 @@ export class MiniMaxSDK extends Client {
1819
readonly search: SearchSDK;
1920
readonly vision: VisionSDK;
2021
readonly quota: QuotaSDK;
22+
readonly file: FileSDK;
2123

2224
constructor(options: MiniMaxSDKOptions) {
2325
super(options);
@@ -29,5 +31,6 @@ export class MiniMaxSDK extends Client {
2931
this.search = new SearchSDK(options);
3032
this.vision = new VisionSDK(options);
3133
this.quota = new QuotaSDK(options);
34+
this.file = new FileSDK(options);
3235
}
3336
}

test/sdk/file.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { describe, it, expect } from 'bun:test';
2+
import { FileSDK } from '../../src/sdk/file';
3+
import { existsSync, unlinkSync, writeFileSync } from 'node:fs';
4+
import { join } from 'node:path';
5+
import { tmpdir } from 'node:os';
6+
7+
describe('FileSDK', () => {
8+
it('throws SDKError when file does not exist', async () => {
9+
const sdk = new FileSDK({ apiKey: 'sk-test', region: 'global' });
10+
await expect(sdk.upload('/tmp/nonexistent-file-xxxxx.bin', 'retrieval'))
11+
.rejects
12+
.toThrow('File not found');
13+
});
14+
15+
it('gets past file existence check for a valid file', async () => {
16+
const tmpFile = join(tmpdir(), 'mmx-sdk-test-upload.txt');
17+
writeFileSync(tmpFile, 'hello world');
18+
19+
try {
20+
const sdk = new FileSDK({ apiKey: 'sk-test', region: 'global' });
21+
await sdk.upload(tmpFile, 'retrieval');
22+
// Should not reach here (no mock server), but if it does, fail informatively
23+
} catch (err) {
24+
// Must NOT be "File not found" — proves file existence check passed
25+
expect((err as Error).message).not.toContain('File not found');
26+
} finally {
27+
if (existsSync(tmpFile)) unlinkSync(tmpFile);
28+
}
29+
});
30+
});

0 commit comments

Comments
 (0)