diff --git a/README.md b/README.md index 1e0ae26..b93d7f4 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,11 @@ const client = new VlmRun({ // Process an image async function processImage() { - const response = await client.image.generate( - ["path/to/invoice.jpg"], - "vlm-1", - "document.invoice", + const response = await client.imagePredictions.generate( { + images: ["path/to/invoice.jpg"], + model: "vlm-1", + domain: "document.invoice", jsonSchema: { type: "object", properties: { @@ -66,7 +66,7 @@ async function processImage() { ### Image Utilities ```typescript -import { encodeImage, isImage } from "vlmrun/utils/image"; +import { encodeImage, isImage } from "vlmrun"; // Convert image to base64 const base64Image = encodeImage("path/to/image.jpg"); @@ -87,8 +87,9 @@ src/ │ ├── feedback.ts # Feedback operations │ └── types.ts # Type definitions ├── utils/ # Utility functions -│ └── image.ts # Image processing utilities -└── index.ts # Main entry point +│ ├── image.ts # Image processing utilities +│ └── index.ts # Utility functions +└── index.ts # Main entry point ``` ## 🛠️ Examples diff --git a/jest.config.js b/jest.config.js index 30a07dd..e999a90 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,12 +1,12 @@ -export default { - preset: 'ts-jest', - testEnvironment: 'node', - testMatch: ['**/tests/**/*.test.ts'], - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - transform: { - '^.+\\.tsx?$': ['ts-jest', { - useESM: true, - }] - }, +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/tests/**/*.test.ts'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + transform: { + '^.+\\.tsx?$': ['ts-jest', { + useESM: true, + }] + }, extensionsToTreatAsEsm: [".ts"], }; diff --git a/package-lock.json b/package-lock.json index 73c8f5a..2d5dd64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vlmrun", - "version": "0.2.0", + "version": "0.1.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vlmrun", - "version": "0.2.0", + "version": "0.1.9", "license": "Apache-2.0", "dependencies": { "axios": "^1.7.9", diff --git a/package.json b/package.json index ce2914f..19fd1af 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "vlmrun", - "version": "0.1.8", + "version": "0.1.9", "description": "The official TypeScript library for the VlmRun API", "author": "VlmRun ", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", - "type": "module", + "type": "commonjs", "repository": "github:vlm-run/vlmrun-node-sdk", "license": "Apache-2.0", "keywords": [], diff --git a/src/client/feedback.ts b/src/client/feedback.ts index 38dc56f..528d631 100644 --- a/src/client/feedback.ts +++ b/src/client/feedback.ts @@ -1,5 +1,5 @@ import { Client, APIRequestor } from "./base_requestor"; -import { FeedbackSubmitResponse } from "./types"; +import { FeedbackSubmitResponse, FeedbackSubmitParams } from "./types"; export class Feedback { private client: Client; @@ -13,25 +13,16 @@ export class Feedback { }); } - async submit( - id: string, - options: { - label?: Record; - notes?: string; - flag?: boolean; - } = {} - ): Promise { - const { label, notes, flag } = options; - + async submit(params: FeedbackSubmitParams): Promise { const [response] = await this.requestor.request( "POST", "feedback/submit", undefined, { - request_id: id, - response: label, - notes, - flag, + request_id: params.id, + response: params.label, + notes: params.notes, + flag: params.flag, } ); return response; diff --git a/src/client/files.ts b/src/client/files.ts index c9b4288..d75c0b9 100644 --- a/src/client/files.ts +++ b/src/client/files.ts @@ -1,7 +1,7 @@ import { createHash } from 'crypto'; import { readFile } from 'fs/promises'; import { Client, APIRequestor } from './base_requestor'; -import { FileResponse } from './types'; +import { FileResponse, ListParams, FileUploadParams } from './types'; export class Files { private client: Client; @@ -12,11 +12,11 @@ export class Files { this.requestor = new APIRequestor(client); } - async list(skip: number = 0, limit: number = 10): Promise { + async list(params: ListParams = {}): Promise { const [response] = await this.requestor.request( 'GET', 'files', - { skip, limit } + { skip: params.skip, limit: params.limit } ); return response; } @@ -40,29 +40,25 @@ export class Files { } } - async upload( - filePath: string, - purpose: string, - checkDuplicate: boolean = true - ): Promise { - if (checkDuplicate) { - const existingFile = await this.checkFileExists(filePath); + async upload(params: FileUploadParams): Promise { + if (params.checkDuplicate !== false) { + const existingFile = await this.checkFileExists(params.filePath); if (existingFile) { return existingFile; } } - const fileBuffer = await readFile(filePath); + const fileBuffer = await readFile(params.filePath); const formData = new FormData(); formData.append('file', new Blob([fileBuffer])); - formData.append('purpose', purpose); + formData.append('purpose', params.purpose); const [response] = await this.requestor.request( 'POST', 'files', undefined, undefined, - { file: new Blob([fileBuffer]), purpose } + { file: new Blob([fileBuffer]), purpose: params.purpose } ); return response; } diff --git a/src/client/predictions.ts b/src/client/predictions.ts index 7cce32f..29a9f81 100644 --- a/src/client/predictions.ts +++ b/src/client/predictions.ts @@ -1,5 +1,11 @@ import { Client, APIRequestor } from './base_requestor'; -import { PredictionResponse, DetailLevel } from './types'; +import { + PredictionResponse, + DetailLevel, + ListParams, + ImagePredictionParams, + FilePredictionParams +} from './types'; import { processImage } from '../utils/image'; export class Predictions { @@ -11,44 +17,36 @@ export class Predictions { this.requestor = new APIRequestor(client); } - async list(skip: number = 0, limit: number = 10): Promise { + async list(params: ListParams = {}): Promise { const [response] = await this.requestor.request( 'GET', 'predictions', - { skip, limit } + { skip: params.skip, limit: params.limit } ); return response; } - async get(id: string): Promise { + async get(params: { id: string }): Promise { const [response] = await this.requestor.request( 'GET', - `predictions/${id}` + `predictions/${params.id}` ); return response; } } export class ImagePredictions extends Predictions { - async generate( - images: string[], - model: string, - domain: string, - options: { - jsonSchema?: Record; - detail?: DetailLevel; - batch?: boolean; - metadata?: Record; - callbackUrl?: string; - } = {} - ): Promise { + async generate(params: ImagePredictionParams): Promise { const { + images, + model, + domain, jsonSchema, detail = 'auto', batch = false, metadata = {}, callbackUrl, - } = options; + } = params; const encodedImages = images.map(image => processImage(image)); @@ -79,25 +77,17 @@ export class FilePredictions extends Predictions { this.route = route; } - async generate( - fileIds: string[], - model: string, - domain: string, - options: { - jsonSchema?: Record; - detail?: DetailLevel; - batch?: boolean; - metadata?: Record; - callbackUrl?: string; - } = {} - ): Promise { + async generate(params: FilePredictionParams): Promise { const { + fileIds, + model, + domain, jsonSchema, detail = 'auto', batch = false, metadata = {}, callbackUrl, - } = options; + } = params; const [response] = await this.requestor.request( 'POST', diff --git a/src/client/types.ts b/src/client/types.ts index fd82ab9..b517d78 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -1,15 +1,8 @@ -export type JobStatus = 'pending' | 'running' | 'completed' | 'failed'; +export type JobStatus = string; -export type FilePurpose = - | 'fine-tune' - | 'assistants' - | 'assistants_output' - | 'batch' - | 'batch_output' - | 'vision' - | 'datasets'; +export type FilePurpose = string; -export type DetailLevel = 'auto' | 'lo' | 'hi'; +export type DetailLevel = string; export interface FileResponse { id: string; @@ -48,6 +41,42 @@ export interface FeedbackSubmitResponse { response: any; } +export interface ListParams { + skip?: number; + limit?: number; +} + +export interface FileUploadParams { + filePath: string; + purpose: string; + checkDuplicate?: boolean; +} + +export interface FeedbackSubmitParams { + id: string; + label?: Record; + notes?: string; + flag?: boolean; +} + +export interface PredictionGenerateParams { + model: string; + domain: string; + jsonSchema?: Record; + detail?: DetailLevel; + batch?: boolean; + metadata?: Record; + callbackUrl?: string; +} + +export interface ImagePredictionParams extends PredictionGenerateParams { + images: string[]; +} + +export interface FilePredictionParams extends PredictionGenerateParams { + fileIds: string[]; +} + export class APIError extends Error { constructor( message: string, diff --git a/src/index.ts b/src/index.ts index 3d1cac9..b8343e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,8 @@ export * from "./client/files"; export * from "./client/predictions"; export * from "./client/feedback"; +export * from "./utils"; + export interface VlmRunConfig { apiKey: string; baseURL?: string; diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..cf09068 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1 @@ +export * from './image'; diff --git a/tests/client/files.test.ts b/tests/client/files.test.ts index 86d3881..9eec50e 100644 --- a/tests/client/files.test.ts +++ b/tests/client/files.test.ts @@ -32,13 +32,13 @@ describe('Files', () => { }]; jest.spyOn(files['requestor'], 'request').mockResolvedValue([mockResponse, 200, {}]); - const result = await files.list(); + const result = await files.list({}); expect(result).toEqual(mockResponse); expect(files['requestor'].request).toHaveBeenCalledWith( 'GET', 'files', - { skip: 0, limit: 10 } + { skip: undefined, limit: undefined } ); }); @@ -53,7 +53,7 @@ describe('Files', () => { }]; jest.spyOn(files['requestor'], 'request').mockResolvedValue([mockResponse, 200, {}]); - const result = await files.list(5, 20); + const result = await files.list({ skip: 5, limit: 20 }); expect(result).toEqual(mockResponse); expect(files['requestor'].request).toHaveBeenCalledWith( @@ -146,11 +146,14 @@ describe('Files', () => { }; jest.spyOn(files, 'checkFileExists').mockResolvedValue(existingFile); - const result = await files.upload('test.jpg', 'vision'); + const result = await files.upload({ + filePath: 'test.jpg', + purpose: 'vision', + checkDuplicate: true + }); expect(result).toEqual(existingFile); expect(files.checkFileExists).toHaveBeenCalledWith('test.jpg'); - expect(files['requestor'].request).not.toHaveBeenCalled(); }); it('should upload new file if no duplicate found', async () => { @@ -165,7 +168,11 @@ describe('Files', () => { jest.spyOn(files, 'checkFileExists').mockResolvedValue(null); jest.spyOn(files['requestor'], 'request').mockResolvedValue([mockResponse, 200, {}]); - const result = await files.upload('test.jpg', 'vision'); + const result = await files.upload({ + filePath: 'test.jpg', + purpose: 'vision', + checkDuplicate: true + }); expect(result).toEqual(mockResponse); expect(files.checkFileExists).toHaveBeenCalledWith('test.jpg'); @@ -184,7 +191,11 @@ describe('Files', () => { jest.spyOn(files, 'checkFileExists'); jest.spyOn(files['requestor'], 'request').mockResolvedValue([mockResponse, 200, {}]); - const result = await files.upload('test.jpg', 'vision', false); + const result = await files.upload({ + filePath: 'test.jpg', + purpose: 'vision', + checkDuplicate: false + }); expect(result).toEqual(mockResponse); expect(files.checkFileExists).not.toHaveBeenCalled(); diff --git a/tests/client/predictions.test.ts b/tests/client/predictions.test.ts index abc8d59..7ed6e73 100644 --- a/tests/client/predictions.test.ts +++ b/tests/client/predictions.test.ts @@ -29,11 +29,11 @@ describe('Predictions', () => { const mockResponse = { id: 'pred_123', status: 'completed' }; jest.spyOn(imagePredictions['requestor'], 'request').mockResolvedValue([mockResponse, 200, {}]); - const result = await imagePredictions.generate( - ['image1.jpg'], - 'model1', - 'domain1' - ); + const result = await imagePredictions.generate({ + images: ['image1.jpg'], + model: 'model1', + domain: 'domain1' + }); expect(result).toEqual(mockResponse); expect(imagePredictions['requestor'].request).toHaveBeenCalledWith( @@ -57,20 +57,16 @@ describe('Predictions', () => { const mockResponse = { id: 'pred_123', status: 'completed' }; jest.spyOn(imagePredictions['requestor'], 'request').mockResolvedValue([mockResponse, 200, {}]); - const options = { - jsonSchema: { type: 'object' }, - detail: 'high' as DetailLevel, + const result = await imagePredictions.generate({ + images: ['image1.jpg'], + model: 'model1', + domain: 'domain1', + detail: 'hi', batch: true, metadata: { key: 'value' }, - callbackUrl: 'https://callback.example.com' - }; - - const result = await imagePredictions.generate( - ['image1.jpg', 'image2.jpg'], - 'model1', - 'domain1', - options - ); + jsonSchema: { type: 'object' }, + callbackUrl: 'https://example.com/callback' + }); expect(result).toEqual(mockResponse); expect(imagePredictions['requestor'].request).toHaveBeenCalledWith( @@ -81,11 +77,11 @@ describe('Predictions', () => { image: 'base64-encoded-image', model: 'model1', domain: 'domain1', - detail: 'high', + detail: 'hi', batch: true, metadata: { key: 'value' }, json_schema: { type: 'object' }, - callback_url: 'https://callback.example.com' + callback_url: 'https://example.com/callback' } ); }); @@ -104,11 +100,11 @@ describe('Predictions', () => { const mockResponse = { id: 'pred_123', status: 'completed' }; jest.spyOn(documentPredictions['requestor'], 'request').mockResolvedValue([mockResponse, 200, {}]); - const result = await documentPredictions.generate( - ['doc1.pdf'], - 'model1', - 'domain1' - ); + const result = await documentPredictions.generate({ + fileIds: ['doc1.pdf'], + model: 'model1', + domain: 'domain1' + }); expect(result).toEqual(mockResponse); expect(documentPredictions['requestor'].request).toHaveBeenCalledWith( @@ -142,11 +138,11 @@ describe('Predictions', () => { const mockResponse = { id: 'pred_123', status: 'completed' }; jest.spyOn(audioPredictions['requestor'], 'request').mockResolvedValue([mockResponse, 200, {}]); - const result = await audioPredictions.generate( - ['audio1.mp3'], - 'model1', - 'domain1' - ); + const result = await audioPredictions.generate({ + fileIds: ['audio1.mp3'], + model: 'model1', + domain: 'domain1' + }); expect(result).toEqual(mockResponse); expect(audioPredictions['requestor'].request).toHaveBeenCalledWith( @@ -180,11 +176,11 @@ describe('Predictions', () => { const mockResponse = { id: 'pred_123', status: 'completed' }; jest.spyOn(videoPredictions['requestor'], 'request').mockResolvedValue([mockResponse, 200, {}]); - const result = await videoPredictions.generate( - ['video1.mp4'], - 'model1', - 'domain1' - ); + const result = await videoPredictions.generate({ + fileIds: ['video1.mp4'], + model: 'model1', + domain: 'domain1' + }); expect(result).toEqual(mockResponse); expect(videoPredictions['requestor'].request).toHaveBeenCalledWith(