From d80b305fc8757c135aefbb63ac08ef72488e2445 Mon Sep 17 00:00:00 2001 From: Win Cheng Date: Tue, 19 Aug 2025 11:49:01 -0700 Subject: [PATCH 1/4] rm tts --- README.md | 9 +---- src/audio/audio.ts | 21 ---------- src/vercel-ai-toolkit.ts | 24 ------------ tests/audio.test.ts | 82 ---------------------------------------- 4 files changed, 1 insertion(+), 135 deletions(-) diff --git a/README.md b/README.md index a83129f..4a432c7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ To learn more about all available JigsawStack AI services, view the [Documentati | ----------------- | -------------------------------------------------- | | **👉 General** | Translation, Summarization, Sentiment Analysis | | **🌐 Web** | AI Web Scraping, AI Web Search | -| **🎵 Audio** | Text to Speech, Speech to Text | +| **🎵 Audio** | Speech to Text | | **👀 Vision** | vOCR, Object Detection | | **🧠 LLMs** | Prompt Engine | | **🖼️ Generative** | AI Image (Flux, SD, SDXL-Fast & more), HTML to Any | @@ -63,13 +63,6 @@ const resp = await jigsaw.web.ai_scrape({ }); ``` -Text to Speech Example: - -```ts -const resp = await jigsaw.audio.text_to_speech({ - text: "Hello, how are you doing?", -}); -``` Upload a file to use across any API example: diff --git a/src/audio/audio.ts b/src/audio/audio.ts index 967d633..5b85297 100644 --- a/src/audio/audio.ts +++ b/src/audio/audio.ts @@ -11,27 +11,6 @@ class Audio { } return await this.client.fetchJSS("/ai/transcribe", "POST", params); } - - text_to_speech = async (params: TextToSpeechParams) => { - const resp = await this.client.fetchJSS("/ai/tts", "POST", params); - return respToFileChoice(resp); - }; - - speaker_voice_accents = async () => { - return await this.client.fetchJSS("/ai/tts", "GET"); - }; - - create_clone = async (params: TTSCloneParams) => { - return await this.client.fetchJSS("/ai/tts/clone", "POST", params); - }; - - list_clones = async (params?: ListTTSVoiceClonesParams) => { - return await this.client.fetchJSS("/ai/tts/clone", "GET", undefined, params); - }; - - delete_clone = async (voice_id: string) => { - return await this.client.fetchJSS(`/ai/tts/clone/${voice_id}`, "DELETE"); - }; } export default Audio; diff --git a/src/vercel-ai-toolkit.ts b/src/vercel-ai-toolkit.ts index f46f552..dab2c8b 100644 --- a/src/vercel-ai-toolkit.ts +++ b/src/vercel-ai-toolkit.ts @@ -291,30 +291,6 @@ export class JigsawStackToolSet { }, }), - text_to_speech: tool({ - description: "Convert text to speech", - parameters: z.object({ - text: z.string().describe("Text to convert to speech"), - accent: z.string().optional().describe("Voice accent (see JigsawStack docs for supported accents)"), - voice_clone_id: z.string().optional().describe("ID of cloned voice to use"), - }), - execute: async ({ text, accent, voice_clone_id }) => { - await this.jigsawStack.audio.text_to_speech({ - text, - accent: accent as SupportedAccents, - voice_clone_id, - }); - - return { - success: true, - message: "Successfully converted text to speech", - text_length: text.length, - accent_used: accent || "default", - note: "Audio has been generated. Use jigsawStack.audio.text_to_speech() directly to get the binary data.", - }; - }, - }), - // ========== VALIDATION TOOLS ========== nsfw_detection: tool({ description: "Detect NSFW (Not Safe For Work) content in images", diff --git a/tests/audio.test.ts b/tests/audio.test.ts index c67061b..94a4fec 100644 --- a/tests/audio.test.ts +++ b/tests/audio.test.ts @@ -67,85 +67,3 @@ describe("STT APIs", () => { expectType(result.text, "string"); }); }); - -// describe("TTS APIs", () => { -// let client: ReturnType; - -// beforeEach(() => { -// client = createJigsawStackClient(); -// }); - -// test("text to speech basic", async () => { -// const result = await client.audio.text_to_speech({ -// text: text, -// accent: "en-US-female-3", -// }); - -// // TTS returns a file choice object -// expectProperty(result, "blob"); -// expectType(result.blob, "function"); -// expectProperty(result, "buffer"); -// expectType(result.buffer, "function"); -// expectProperty(result, "file"); -// expectType(result.file, "function"); -// }); - -// test("text to speech with different accent", async () => { -// const result = await client.audio.text_to_speech({ -// text: text, -// accent: "en-GB-male-2", -// }); - -// // TTS returns a file choice object -// expectProperty(result, "blob"); -// expectType(result.blob, "function"); -// }); - -// test("text to speech with return type base64", async () => { -// const result = await client.audio.text_to_speech({ -// text: text, -// accent: "en-US-female-3", -// return_type: "base64", -// }); - -// // TTS returns a file choice object -// expectProperty(result, "url"); -// expectType(result.url, "string"); -// }); - -// test("text to speech with return type binary", async () => { -// const result = await client.audio.text_to_speech({ -// text: text, -// accent: "en-US-female-3", -// return_type: "binary", -// }); - -// // TTS returns a file choice object -// expectProperty(result, "blob"); -// expectType(result.blob, "function"); -// }); - -// test("get speaker voice accents", async () => { -// const result = await client.audio.speaker_voice_accents(); - -// expectSuccess(result); -// expectType(result, "object"); -// }); - -// test("list voice clones", async () => { -// const result = await client.audio.list_clones({ -// limit: 10, -// page: 0, -// }); - -// expectSuccess(result); -// expectType(result, "object"); -// }); - -// test("list voice clones with default params", async () => { -// const result = await client.audio.list_clones(); - -// expectSuccess(result); -// expectType(result, "object"); -// }); -// }); From dadfa41f6be39835ba46908f31d2c9b819e82a81 Mon Sep 17 00:00:00 2001 From: Win Cheng Date: Tue, 19 Aug 2025 11:52:04 -0700 Subject: [PATCH 2/4] rm prompt engine --- README.md | 1 - src/core.ts | 3 -- src/prompt_engine/index.ts | 50 ------------------------ src/prompt_engine/interfaces.ts | 69 --------------------------------- 4 files changed, 123 deletions(-) delete mode 100644 src/prompt_engine/index.ts delete mode 100644 src/prompt_engine/interfaces.ts diff --git a/README.md b/README.md index 4a432c7..11b7855 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ To learn more about all available JigsawStack AI services, view the [Documentati | **🌐 Web** | AI Web Scraping, AI Web Search | | **🎵 Audio** | Speech to Text | | **👀 Vision** | vOCR, Object Detection | -| **🧠 LLMs** | Prompt Engine | | **🖼️ Generative** | AI Image (Flux, SD, SDXL-Fast & more), HTML to Any | | **✅ Validation** | Email, NSFW images, profanity & more | diff --git a/src/core.ts b/src/core.ts index 9f9ed96..3f3965e 100644 --- a/src/core.ts +++ b/src/core.ts @@ -2,7 +2,6 @@ import "isomorphic-fetch"; import { BaseConfig } from "../types"; import AudioApis from "./audio/audio"; import General from "./general"; -import PromptEngine from "./prompt_engine"; import { RequestClient } from "./request"; import { File } from "./store/file"; import Validate from "./validate"; @@ -21,7 +20,6 @@ export const JigsawStack = (config?: BaseConfig) => { const web = new Web(client); const vision = new Vision(client); const audio = new AudioApis(client); - const promptengine = new PromptEngine(client); const file = new File(client); const validate = new Validate(client); const store = { @@ -50,7 +48,6 @@ export const JigsawStack = (config?: BaseConfig) => { search: web.search, search_suggestions: web.search_suggestions, }, - prompt_engine: promptengine, store, validate, }; diff --git a/src/prompt_engine/index.ts b/src/prompt_engine/index.ts deleted file mode 100644 index 811e635..0000000 --- a/src/prompt_engine/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Stream } from "../lib/streaming"; -import { RequestClient } from "../request"; -import { - PromptCreateParams, - PromptExecuteParams, - PromptGetResponse, - PromptListParams, - PromptListResponse, - PromptRunParams, - RunPromptDirectResponse, - RunPromptResponse, - RunPromptResponseStream, -} from "./interfaces"; - -class PromptEngine { - constructor(private readonly client: RequestClient) {} - create = async (params: PromptCreateParams): Promise<{ prompt_engine_id: string }> => { - return await this.client.fetchJSS("/prompt_engine", "POST", params); - }; - - run_prompt_direct(params: PromptRunParams & { stream: true }): Promise>; - run_prompt_direct(params: PromptRunParams & { stream?: false }): Promise; - async run_prompt_direct(params: PromptRunParams): Promise> { - const resp = await this.client.fetchJSS(`/prompt_engine/run`, "POST", params); - if (!params.stream) { - return resp as RunPromptDirectResponse; - } - return Stream.fromReadableStream(resp.body) as RunPromptResponseStream; - } - get = async (id: string): Promise => { - return await this.client.fetchJSS(`/prompt_engine/${id}`, "GET", {}); - }; - list = async (params: PromptListParams = { limit: 20, page: 0 }): Promise => { - return await this.client.fetchJSS("/prompt_engine", "GET", {}, params); - }; - delete = async (id: string): Promise<{ prompt_engine_id: string }> => { - return await this.client.fetchJSS(`/prompt_engine/${id}`, "DELETE", {}); - }; - run(params: PromptExecuteParams & { stream: true }): Promise>; - run(params: PromptExecuteParams & { stream?: false }): Promise; - async run(params: PromptExecuteParams): Promise | RunPromptResponse> { - const resp = await this.client.fetchJSS(`/prompt_engine/${params.id}`, "POST", params); - if (!params.stream) { - return resp as RunPromptResponse; - } - return Stream.fromReadableStream(resp.body) as RunPromptResponseStream; - } -} - -export default PromptEngine; diff --git a/src/prompt_engine/interfaces.ts b/src/prompt_engine/interfaces.ts deleted file mode 100644 index bb450f9..0000000 --- a/src/prompt_engine/interfaces.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Stream } from "../lib/streaming"; - -export interface PromptCreateParams { - prompt: string; - return_prompt?: string | Array> | Record; - inputs?: Array<{ - key: string; - optional?: boolean; - initial_value?: string; - }>; - use_internet?: boolean; - optimize_prompt?: boolean; - prompt_guard?: Array< - | "defamation" - | "specialized_advice" - | "privacy" - | "intellectual_property" - | "indiscriminate_weapons" - | "hate" - | "sexual_content" - | "elections" - | "code_interpreter_abuse" - >; -} - -export interface PromptRunParams extends Omit { - input_values?: Record; - stream?: boolean; -} - -export interface PromptExecuteParams { - id: string; - input_values?: Record; - stream?: boolean; -} - -export interface PromptListParams { - page?: number; - limit?: number; -} - -export interface PromptResult { - id: string; - prompt: string; - inputs: Array<{ - key: string; - optional: boolean; - }>; - return_prompt: string; - created_at: string; -} - -export interface PromptGetResponse extends PromptResult { - success: boolean; -} - -export interface PromptListResponse extends PromptResult { - prompt_engines: PromptResult[]; -} - -export interface RunPromptResponse { - result: any; - success: boolean; - message?: string; -} - -export interface RunPromptResponseStream extends Stream {} - -export interface RunPromptDirectResponse extends RunPromptResponse {} From df3b4c7fd1198b6857b6e5528124a20c2cc140fd Mon Sep 17 00:00:00 2001 From: Win Cheng Date: Wed, 20 Aug 2025 10:40:15 -0700 Subject: [PATCH 3/4] classification added with tests --- src/classification/index.ts | 19 + src/classification/interfaces.ts | 31 ++ test.ts | 16 + tests/classification.test.ts | 591 +++++++++++++++++++++++++++++++ tests/test-helpers.ts | 4 + tests/web.test.ts | 76 ---- 6 files changed, 661 insertions(+), 76 deletions(-) create mode 100644 src/classification/index.ts create mode 100644 src/classification/interfaces.ts create mode 100644 test.ts create mode 100644 tests/classification.test.ts diff --git a/src/classification/index.ts b/src/classification/index.ts new file mode 100644 index 0000000..14059bf --- /dev/null +++ b/src/classification/index.ts @@ -0,0 +1,19 @@ +import { RequestClient } from "../request"; +import { ClassificationTextParams, ClassificationImageParams, ClassificationResponse } from "./interfaces"; + +class Classification { + private readonly client: RequestClient; + constructor(client: RequestClient) { + this.client = client; + } + + text = async (params: ClassificationTextParams): Promise => { + return await this.client.fetchJSS("/classification", "POST", params); + }; + + image = async (params: ClassificationImageParams): Promise => { + return await this.client.fetchJSS("/classification", "POST", params); + }; +} + +export default Classification; diff --git a/src/classification/interfaces.ts b/src/classification/interfaces.ts new file mode 100644 index 0000000..a2f345e --- /dev/null +++ b/src/classification/interfaces.ts @@ -0,0 +1,31 @@ +import { BaseResponse } from "../../types"; + +export interface ClassificationResponse extends BaseResponse { + predictions: (string | string[])[]; +} + +export interface ClassificationTextParams { + dataset: Array<{ + type: "text"; + value: string; + }>; + labels: Array<{ + key?: string; + type: "text"; + value: string; + }>; + multiple_labels?: boolean; +} + +export interface ClassificationImageParams { + dataset: Array<{ + type: "image"; + value: string; + }>; + labels: Array<{ + key?: string; + type: "image" | "text"; + value: string; + }>; + multiple_labels?: boolean; +} diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..14e860d --- /dev/null +++ b/test.ts @@ -0,0 +1,16 @@ +import { createJigsawStackClient } from "./tests/test-helpers"; + +const client = createJigsawStackClient(); + +const result = await client.classification.text({ + dataset: [ + { type: "text" as const, value: "This is a great product! I love it." }, + { type: "text" as const, value: "This is a great product! I hate it." }, + ], + labels: [ + { key: "positive", type: "text" as const, value: "positive sentiment" }, + { key: "negative", type: "text" as const, value: "negative sentiment" }, + ], +}); + +console.log(result); diff --git a/tests/classification.test.ts b/tests/classification.test.ts new file mode 100644 index 0000000..d8f3206 --- /dev/null +++ b/tests/classification.test.ts @@ -0,0 +1,591 @@ +import { beforeEach, describe, test } from "node:test"; +import { createJigsawStackClient, expectArray, expectProperty, expectSuccess, expectType } from "./test-helpers.js"; + +const TEST_TEXT_DATA = [ + { + type: "text" as const, + value: "This is a great product! I love it.", + }, + { + type: "text" as const, + value: "This is ass product", + }, + { + type: "text" as const, + value: "Dogs are cool", + }, + { + type: "text" as const, + value: "Cats are not cool", + }, +]; + +const TEST_IMAGE_DATA = [ + { + type: "image" as const, + value: "https://as2.ftcdn.net/v2/jpg/02/24/11/57/1000_F_224115780_2ssvcCoTfQrx68Qsl5NxtVIDFWKtAgq2.jpg", + }, + { + type: "image" as const, + value: "https://t3.ftcdn.net/jpg/02/95/44/22/240_F_295442295_OXsXOmLmqBUfZreTnGo9PREuAPSLQhff.jpg", + }, + { + type: "image" as const, + value: "https://as1.ftcdn.net/v2/jpg/05/54/94/46/1000_F_554944613_okdr3fBwcE9kTOgbLp4BrtVi8zcKFWdP.jpg", + }, +]; + +// Fix the label definitions to match real API +const TEXT_LABELS = [ + { + type: "text" as const, + value: "positive", + }, + { + type: "text" as const, + value: "negative", + }, + { + type: "text" as const, + value: "dogs", + }, + { + type: "text" as const, + value: "cats", + }, +]; + +const IMAGE_LABELS = [ + { + type: "image" as const, // Note: "text" not "image"! + value: "https://upload.wikimedia.org/wikipedia/commons/8/8a/Banana-Single.jpg", + }, + { + type: "image" as const, + value: "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Instagram_icon.png/2048px-Instagram_icon.png", + }, +]; + +// Comprehensive Text Classification API Tests +describe("Text Classification API", () => { + let client: ReturnType; + + beforeEach(() => { + client = createJigsawStackClient(); + }); + + // Test missing required parameters + test("should fail when no parameters are provided", async () => { + try { + await client.classification.text({} as any); + throw new Error("Expected API call to fail with no parameters"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should fail when dataset is missing", async () => { + try { + await client.classification.text({ + labels: TEXT_LABELS, + } as any); + throw new Error("Expected API call to fail with missing dataset"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should fail when labels are missing", async () => { + try { + await client.classification.text({ + dataset: TEST_TEXT_DATA, + labels: [], + } as any); + throw new Error("Expected API call to fail with missing labels"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should fail when dataset is empty", async () => { + try { + await client.classification.text({ + dataset: [], + labels: TEXT_LABELS, + }); + throw new Error("Expected API call to fail with empty dataset"); + } catch (error) { + expectType(error, "object"); + } + }); + + // Basic functionality tests + test("should work with basic text classification", async () => { + const result = await client.classification.text({ + dataset: [ + { type: "text" as const, value: "This is a great product! I love it." }, + { type: "text" as const, value: "This is a great product! I hate it." }, + ], + labels: [ + { key: "positive", type: "text" as const, value: "positive sentiment" }, + { key: "negative", type: "text" as const, value: "negative sentiment" }, + ], + }); + + expectSuccess(result); + expectProperty(result, "predictions"); + expectArray(result.predictions); + + console.log(result.predictions); + + // Predictions should be simple strings + for (const prediction of result.predictions) { + expectType(prediction, "string"); + } + }); + + test("should work with multiple text samples", async () => { + const result = await client.classification.text({ + dataset: [ + { type: "text" as const, value: "This is a great product! I love it." }, + { type: "text" as const, value: "This product is terrible and broken." }, + { type: "text" as const, value: "This is a product description." }, + ], + labels: [ + { key: "positive", type: "text" as const, value: "positive sentiment" }, + { key: "negative", type: "text" as const, value: "negative sentiment" }, + { key: "neutral", type: "text" as const, value: "neutral sentiment" }, + ], + }); + + expectSuccess(result); + expectArray(result.predictions); + + // Should have predictions for each input text + if (result.predictions.length !== 3) { + console.log(`Note: Expected 3 predictions, got ${result.predictions.length}`); + } + }); + + test("should work with multiple_labels enabled", async () => { + const result = await client.classification.text({ + dataset: [{ type: "text" as const, value: "This is a great product with excellent customer service!" }], + labels: [ + { key: "quality", type: "text" as const, value: "relates to product quality" }, + { key: "service", type: "text" as const, value: "relates to customer service" }, + { key: "pricing", type: "text" as const, value: "relates to pricing" }, + ], + multiple_labels: true, + }); + + expectSuccess(result); + expectArray(result.predictions); + + // With multiple_labels, predictions should be arrays of strings + for (const prediction of result.predictions) { + if (Array.isArray(prediction)) { + expectArray(prediction); + for (const item of prediction) { + expectType(item, "string"); + } + } + } + }); + + test("should work with multiple_labels disabled", async () => { + const result = await client.classification.text({ + dataset: [{ type: "text" as const, value: "This is a great product! I love it." }], + labels: [ + { key: "positive", type: "text" as const, value: "positive sentiment" }, + { key: "negative", type: "text" as const, value: "negative sentiment" }, + ], + multiple_labels: false, + }); + + expectSuccess(result); + expectArray(result.predictions); + }); + + // Test with various text content types + test("should work with long text", async () => { + const longText = "This is a comprehensive review of a product that I purchased recently. ".repeat(10); + + const result = await client.classification.text({ + dataset: [{ type: "text" as const, value: longText }], + labels: [ + { key: "review", type: "text" as const, value: "product review" }, + { key: "description", type: "text" as const, value: "product description" }, + ], + }); + + expectSuccess(result); + expectArray(result.predictions); + }); + + test("should work with special characters and unicode", async () => { + const result = await client.classification.text({ + dataset: [{ type: "text" as const, value: "¡Excelente producto! 🚀 Me encanta. Ñoño café français." }], + labels: [ + { key: "positive", type: "text" as const, value: "positive sentiment" }, + { key: "negative", type: "text" as const, value: "negative sentiment" }, + ], + }); + + expectSuccess(result); + expectArray(result.predictions); + }); + + test("should work with many labels", async () => { + const labels: Array<{ key?: string; type: "text"; value: string }> = []; + for (let i = 1; i <= 10; i++) { + labels.push({ + key: `category_${i}`, + type: "text" as const, + value: `Category ${i} description`, + }); + } + + const result = await client.classification.text({ + dataset: [{ type: "text" as const, value: "This is a test text for classification." }], + labels: labels, + }); + + expectSuccess(result); + expectArray(result.predictions); + }); + + // Edge cases and error handling + test("should handle empty text gracefully", async () => { + try { + const result = await client.classification.text({ + dataset: [{ type: "text" as const, value: "" }], + labels: [ + { key: "empty", type: "text" as const, value: "empty text" }, + { key: "not_empty", type: "text" as const, value: "non-empty text" }, + ], + }); + + expectSuccess(result); + expectArray(result.predictions); + } catch (error) { + // Some APIs might not allow empty text + expectType(error, "object"); + } + }); + + test("should handle whitespace-only text", async () => { + try { + const result = await client.classification.text({ + dataset: [{ type: "text" as const, value: " \n\t " }], + labels: [ + { key: "whitespace", type: "text" as const, value: "whitespace content" }, + { key: "content", type: "text" as const, value: "actual content" }, + ], + }); + + expectSuccess(result); + expectArray(result.predictions); + } catch (error) { + // Some APIs might not allow whitespace-only text + expectType(error, "object"); + } + }); + + test("should fail with invalid dataset type", async () => { + try { + await client.classification.text({ + dataset: [{ type: "image" as any, value: "This is a great product! I love it." }], + labels: [{ key: "positive", type: "text" as const, value: "positive sentiment" }], + } as any); + throw new Error("Expected API call to fail with invalid dataset type"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should fail with invalid label type", async () => { + try { + await client.classification.text({ + dataset: [{ type: "text" as const, value: "This is a great product! I love it." }], + labels: [{ key: "positive", type: "image" as any, value: "positive sentiment" }], + } as any); + throw new Error("Expected API call to fail with invalid label type"); + } catch (error) { + expectType(error, "object"); + } + }); +}); + +// Comprehensive Image Classification API Tests +describe("Image Classification API", () => { + let client: ReturnType; + + beforeEach(() => { + client = createJigsawStackClient(); + }); + + // Test missing required parameters + test("should fail when no parameters are provided", async () => { + try { + await client.classification.image({} as any); + throw new Error("Expected API call to fail with no parameters"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should fail when dataset is missing", async () => { + try { + await client.classification.image({ + labels: IMAGE_LABELS, + } as any); + throw new Error("Expected API call to fail with missing dataset"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should fail when labels are missing", async () => { + try { + await client.classification.image({ + dataset: TEST_IMAGE_DATA, + } as any); + throw new Error("Expected API call to fail with missing labels"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should fail when dataset is empty", async () => { + try { + await client.classification.image({ + dataset: [], + labels: IMAGE_LABELS, + }); + throw new Error("Expected API call to fail with empty dataset"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should fail when labels are empty", async () => { + try { + await client.classification.image({ + dataset: TEST_IMAGE_DATA, + labels: [], + }); + throw new Error("Expected API call to fail with empty labels"); + } catch (error) { + expectType(error, "object"); + } + }); + + // Basic functionality tests + test("should work with basic image classification", async () => { + const result = await client.classification.image({ + dataset: TEST_IMAGE_DATA, + labels: IMAGE_LABELS, + }); + + expectSuccess(result); + expectProperty(result, "predictions"); + expectArray(result.predictions); + + // Check predictions structure + for (const prediction of result.predictions) { + expectType(prediction, typeof prediction === "string" ? "string" : "object"); + if (Array.isArray(prediction)) { + expectArray(prediction); + for (const item of prediction) { + expectType(item, "string"); + } + } + } + }); + + test("should work with multiple images", async () => { + const result = await client.classification.image({ + dataset: [ + { type: "image" as const, value: "https://as2.ftcdn.net/v2/jpg/02/24/11/57/1000_F_224115780_2ssvcCoTfQrx68Qsl5NxtVIDFWKtAgq2.jpg" }, + { type: "image" as const, value: "https://t3.ftcdn.net/jpg/02/95/44/22/240_F_295442295_OXsXOmLmqBUfZreTnGo9PREuAPSLQhff.jpg" }, + ], + labels: [ + { key: "banana", type: "text" as const, value: "banana" }, + { key: "sunglasses", type: "text" as const, value: "sunglasses" }, + ], + }); + + expectSuccess(result); + expectArray(result.predictions); + + // Should have predictions for each input image + if (result.predictions.length !== 2) { + console.log(`Note: Expected 2 predictions, got ${result.predictions.length}`); + } + }); + + test("should work with multiple_labels enabled for images", async () => { + const result = await client.classification.image({ + dataset: [{ type: "image" as const, value: "https://as2.ftcdn.net/v2/jpg/02/24/11/57/1000_F_224115780_2ssvcCoTfQrx68Qsl5NxtVIDFWKtAgq2.jpg" }], + labels: [ + { key: "banana", type: "text" as const, value: "banana" }, + { key: "sunglasses", type: "text" as const, value: "sunglasses" }, + ], + multiple_labels: true, + }); + + expectSuccess(result); + expectArray(result.predictions); + + // With multiple_labels, predictions should be arrays of strings + for (const prediction of result.predictions) { + if (Array.isArray(prediction)) { + expectArray(prediction); + for (const item of prediction) { + expectType(item, "string"); + } + } + } + }); + + test("should work with multiple_labels disabled for images", async () => { + const result = await client.classification.image({ + dataset: [{ type: "image" as const, value: "https://as2.ftcdn.net/v2/jpg/02/24/11/57/1000_F_224115780_2ssvcCoTfQrx68Qsl5NxtVIDFWKtAgq2.jpg" }], + labels: [ + { key: "banana", type: "text" as const, value: "banana" }, + { key: "sunglasses", type: "text" as const, value: "sunglasses" }, + ], + multiple_labels: false, + }); + + expectSuccess(result); + expectArray(result.predictions); + }); + + // Test with multiple label images + test("should work with many image labels", async () => { + const labels: Array<{ key?: string; type: "text"; value: string }> = []; + const labelValues = ["banana", "sunglasses", "person smiling", "outdoor scene", "colorful object"]; + + for (let i = 0; i < labelValues.length; i++) { + labels.push({ + key: `category_${i + 1}`, + type: "text" as const, + value: labelValues[i], + }); + } + + const result = await client.classification.image({ + dataset: [{ type: "image" as const, value: "https://as2.ftcdn.net/v2/jpg/02/24/11/57/1000_F_224115780_2ssvcCoTfQrx68Qsl5NxtVIDFWKtAgq2.jpg" }], + labels: labels, + }); + + expectSuccess(result); + expectArray(result.predictions); + }); + + // Edge cases and error handling + test("should handle invalid image URL gracefully", async () => { + try { + await client.classification.image({ + dataset: [{ type: "image" as const, value: "not-a-valid-url" }], + labels: [{ key: "banana", type: "text" as const, value: "banana" }], + }); + throw new Error("Expected API call to fail with invalid image URL"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should handle empty image URL", async () => { + try { + await client.classification.image({ + dataset: [{ type: "image" as const, value: "" }], + labels: [{ key: "banana", type: "text" as const, value: "banana" }], + }); + throw new Error("Expected API call to fail with empty image URL"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should fail with invalid dataset type", async () => { + try { + await client.classification.image({ + dataset: [{ type: "text" as any, value: "https://as2.ftcdn.net/v2/jpg/02/24/11/57/1000_F_224115780_2ssvcCoTfQrx68Qsl5NxtVIDFWKtAgq2.jpg" }], + labels: [{ key: "banana", type: "text" as const, value: "banana" }], + } as any); + throw new Error("Expected API call to fail with invalid dataset type"); + } catch (error) { + expectType(error, "object"); + } + }); + + test("should fail with invalid label type", async () => { + try { + await client.classification.image({ + dataset: [ + { type: "image" as const, value: "https://as2.ftcdn.net/v2/jpg/02/24/11/57/1000_F_224115780_2ssvcCoTfQrx68Qsl5NxtVIDFWKtAgq2.jpg" }, + ], + labels: [{ key: "banana", type: "text" as any, value: "banana" }], + } as any); + throw new Error("Expected API call to fail with invalid label type"); + } catch (error) { + expectType(error, "object"); + } + }); + + // Complex scenario test + test("should work with comprehensive image classification configuration", async () => { + const result = await client.classification.image({ + dataset: [ + { type: "image" as const, value: "https://as2.ftcdn.net/v2/jpg/02/24/11/57/1000_F_224115780_2ssvcCoTfQrx68Qsl5NxtVIDFWKtAgq2.jpg" }, + { type: "image" as const, value: "https://t3.ftcdn.net/jpg/02/95/44/22/240_F_295442295_OXsXOmLmqBUfZreTnGo9PREuAPSLQhff.jpg" }, + ], + labels: [ + { key: "banana", type: "text" as const, value: "banana" }, + { key: "sunglasses", type: "text" as const, value: "sunglasses" }, + { key: "fun", type: "text" as const, value: "having fun rocking away" }, + ], + multiple_labels: true, + }); + + expectSuccess(result); + expectArray(result.predictions); + }); +}); + +// Mixed Classification Tests +describe("Classification API Edge Cases", () => { + let client: ReturnType; + + beforeEach(() => { + client = createJigsawStackClient(); + }); + + test("should handle large batch of text classification", async () => { + const dataset: Array<{ type: "text"; value: string }> = []; + for (let i = 1; i <= 5; i++) { + dataset.push({ + type: "text" as const, + value: `This is test text number ${i} for classification.`, + }); + } + + const result = await client.classification.text({ + dataset: dataset, + labels: [ + { key: "test", type: "text" as const, value: "test content" }, + { key: "production", type: "text" as const, value: "production content" }, + ], + }); + + expectSuccess(result); + expectArray(result.predictions); + + // Should have predictions for each input + if (result.predictions.length !== 5) { + console.log(`Note: Expected 5 predictions, got ${result.predictions.length}`); + } + }); +}); diff --git a/tests/test-helpers.ts b/tests/test-helpers.ts index 0a27023..c761a5d 100644 --- a/tests/test-helpers.ts +++ b/tests/test-helpers.ts @@ -1,5 +1,6 @@ // tests/test-helpers.ts - Fix the client structure import Audio from "../src/audio/audio"; +import Classification from "../src/classification/index"; import General from "../src/general/index"; import { RequestClient } from "../src/request"; import Validate from "../src/validate/index"; @@ -36,6 +37,9 @@ export function createJigsawStackClient() { // Web APIs - Include both web scraping AND search web: new Web(client), + // Classification APIs + classification: new Classification(client), + // Validation APIs validate: new Validate(client), }; diff --git a/tests/web.test.ts b/tests/web.test.ts index f0e2376..9c9e225 100644 --- a/tests/web.test.ts +++ b/tests/web.test.ts @@ -1223,79 +1223,3 @@ describe("Web Search Suggestions API", () => { expectArray(result.suggestions); }); }); - -// Deep Research API Tests (lightweight tests to avoid long execution times) -describe("Deep Research API", () => { - let client: ReturnType; - - beforeEach(() => { - client = createJigsawStackClient(); - }); - - test("should fail when query parameter is missing", async () => { - try { - await client.web.search({ - deep_research: true, - // Missing query parameter - } as any); - throw new Error("Expected API call to fail with missing query"); - } catch (error) { - expectType(error, "object"); - } - }); - - test("should validate deep_research_config parameter structure", async () => { - // Test with valid config structure - const result = await client.web.search({ - query: "test", - deep_research: true, - deep_research_config: { - max_depth: 2, - max_breadth: 2, - max_output_tokens: 2000, - target_output_tokens: 1500, - }, - }); - - expectSuccess(result); - expectArray(result.results); - }); - - test("should work with deep_research enabled but no config (uses defaults)", async () => { - const result = await client.web.search({ - query: "quick test", // Use simple query to minimize execution time - deep_research: true, - // No deep_research_config - should use defaults - }); - - expectSuccess(result); - expectArray(result.results); - }); - - test("should handle invalid deep_research_config values", async () => { - try { - await client.web.search({ - query: "test", - deep_research: true, - deep_research_config: { - max_depth: -1, // Invalid negative value - max_breadth: 1, - max_output_tokens: 1000, - }, - }); - throw new Error("Expected API call to fail with invalid max_depth"); - } catch (error) { - expectType(error, "object"); - } - }); - - test("should work with deep_research disabled", async () => { - const result = await client.web.search({ - query: "normal search", - deep_research: false, - }); - - expectSuccess(result); - expectArray(result.results); - }); -}); From ed0b701c75daa1236c60ddf7e7c64c59f380c2c7 Mon Sep 17 00:00:00 2001 From: Win Cheng Date: Wed, 20 Aug 2025 10:45:26 -0700 Subject: [PATCH 4/4] linting --- src/classification/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classification/index.ts b/src/classification/index.ts index 14059bf..ffaf2d4 100644 --- a/src/classification/index.ts +++ b/src/classification/index.ts @@ -1,5 +1,5 @@ import { RequestClient } from "../request"; -import { ClassificationTextParams, ClassificationImageParams, ClassificationResponse } from "./interfaces"; +import { ClassificationImageParams, ClassificationResponse, ClassificationTextParams } from "./interfaces"; class Classification { private readonly client: RequestClient;