@tanstack/ai-groq 0.6.0 → 0.6.1

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.
@@ -49,7 +49,8 @@ var GroqTTSAdapter = class extends BaseTTSAdapter {
49
49
  provider: this.name,
50
50
  model
51
51
  });
52
- const base64 = arrayBufferToBase64(await (await this.client.audio.speech.create(request)).arrayBuffer());
52
+ const arrayBuffer = await (await this.client.audio.speech.create(request)).arrayBuffer();
53
+ const base64 = arrayBufferToBase64(arrayBuffer);
53
54
  const outputFormat = request.response_format ?? "wav";
54
55
  const contentType = this.getContentType(outputFormat);
55
56
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"tts.js","names":[],"sources":["../../../src/adapters/tts.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { BaseTTSAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport { getGroqApiKeyFromEnv, withGroqDefaults } from '../utils/client'\nimport { validateAudioInput } from '../audio/audio-provider-options'\nimport type { TTSOptions, TTSResult } from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { GroqTTSModel } from '../model-meta'\nimport type { GroqTTSProviderOptions } from '../audio/tts-provider-options'\nimport type { GroqClientConfig } from '../utils/client'\n\n/**\n * Configuration for Groq TTS adapter\n */\nexport interface GroqTTSConfig extends GroqClientConfig {}\n\n/**\n * Groq Text-to-Speech Adapter\n *\n * Tree-shakeable adapter for Groq TTS functionality. Groq exposes an\n * OpenAI-compatible `/audio/speech` endpoint, so the adapter drives it with\n * the OpenAI SDK via a `baseURL` override (the same pattern as the Groq text\n * adapter).\n *\n * Supports `canopylabs/orpheus-v1-english` and\n * `canopylabs/orpheus-arabic-saudi`.\n *\n * Features:\n * - English voices: autumn(f), diana(f), hannah(f), austin(m), daniel(m), troy(m)\n * - Arabic voices: fahad(m), sultan(m), lulwa(f), noura(f)\n * - Output formats: flac, mp3, mulaw, ogg, wav (default wav)\n * - Speed control\n * - Configurable sample rate via `modelOptions`\n */\nexport class GroqTTSAdapter<TModel extends GroqTTSModel> extends BaseTTSAdapter<\n TModel,\n GroqTTSProviderOptions\n> {\n readonly name = 'groq' as const\n\n protected client: OpenAI\n\n constructor(config: GroqTTSConfig, model: TModel) {\n super(model, {})\n this.client = new OpenAI(withGroqDefaults(config))\n }\n\n async generateSpeech(\n options: TTSOptions<GroqTTSProviderOptions>,\n ): Promise<TTSResult> {\n const { model, text, voice, format, speed, modelOptions } = options\n\n validateAudioInput({ input: text, model: this.model })\n\n // Spreading optional inputs conditionally keeps the request compatible\n // with the vendor SDK shape under exactOptionalPropertyTypes. `sample_rate`\n // is a Groq-only body field carried via modelOptions.\n const request: OpenAI_SDK.Audio.SpeechCreateParams = {\n model,\n input: text,\n voice: voice ?? 'autumn',\n response_format: format ?? 'wav',\n ...(speed !== undefined && { speed }),\n ...(modelOptions ?? {}),\n }\n\n try {\n options.logger.request(\n `activity=tts provider=${this.name} model=${model} format=${request.response_format ?? 'default'} voice=${request.voice}`,\n { provider: this.name, model },\n )\n const response = await this.client.audio.speech.create(request)\n\n const arrayBuffer = await response.arrayBuffer()\n const base64 = arrayBufferToBase64(arrayBuffer)\n\n const outputFormat = request.response_format ?? 'wav'\n const contentType = this.getContentType(outputFormat)\n\n return {\n id: generateId(this.name),\n model,\n audio: base64,\n format: outputFormat,\n contentType,\n }\n } catch (error: unknown) {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n options.logger.errors(`${this.name}.generateSpeech fatal`, {\n error: toRunErrorPayload(error, `${this.name}.generateSpeech failed`),\n source: `${this.name}.generateSpeech`,\n })\n throw error\n }\n }\n\n private getContentType(format: string): string {\n const contentTypes: Record<string, string> = {\n flac: 'audio/flac',\n mp3: 'audio/mpeg',\n mulaw: 'audio/basic',\n ogg: 'audio/ogg',\n wav: 'audio/wav',\n }\n return contentTypes[format] || 'audio/wav'\n }\n}\n\n/**\n * Creates a Groq speech adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'canopylabs/orpheus-v1-english')\n * @param apiKey - Your Groq API key\n * @param config - Optional additional configuration\n * @returns Configured Groq speech adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGroqSpeech('canopylabs/orpheus-v1-english', 'gsk_...')\n *\n * const result = await generateSpeech({\n * adapter,\n * text: 'Hello, world!',\n * voice: 'autumn',\n * })\n * ```\n */\nexport function createGroqSpeech<TModel extends GroqTTSModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GroqTTSConfig, 'apiKey'>,\n): GroqTTSAdapter<TModel> {\n return new GroqTTSAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Groq speech adapter with automatic API key detection from\n * environment variables.\n *\n * Looks for `GROQ_API_KEY` in the environment.\n *\n * @param model - The model name (e.g., 'canopylabs/orpheus-v1-english')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Groq speech adapter instance with resolved types\n * @throws Error if GROQ_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = groqSpeech('canopylabs/orpheus-v1-english')\n *\n * const result = await generateSpeech({\n * adapter,\n * text: 'Welcome to TanStack AI!',\n * voice: 'autumn',\n * format: 'wav',\n * })\n * ```\n */\nexport function groqSpeech<TModel extends GroqTTSModel>(\n model: TModel,\n config?: Omit<GroqTTSConfig, 'apiKey'>,\n): GroqTTSAdapter<TModel> {\n const apiKey = getGroqApiKeyFromEnv()\n return createGroqSpeech(model, apiKey, config)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,IAAa,iBAAb,cAAiE,eAG/D;CACA,OAAgB;CAEhB;CAEA,YAAY,QAAuB,OAAe;EAChD,MAAM,OAAO,CAAC,CAAC;EACf,KAAK,SAAS,IAAI,OAAO,iBAAiB,MAAM,CAAC;CACnD;CAEA,MAAM,eACJ,SACoB;EACpB,MAAM,EAAE,OAAO,MAAM,OAAO,QAAQ,OAAO,iBAAiB;EAE5D,mBAAmB;GAAE,OAAO;GAAM,OAAO,KAAK;EAAM,CAAC;EAKrD,MAAM,UAA+C;GACnD;GACA,OAAO;GACP,OAAO,SAAS;GAChB,iBAAiB,UAAU;GAC3B,GAAI,UAAU,KAAA,KAAa,EAAE,MAAM;GACnC,GAAI,gBAAgB,CAAC;EACvB;EAEA,IAAI;GACF,QAAQ,OAAO,QACb,yBAAyB,KAAK,KAAK,SAAS,MAAM,UAAU,QAAQ,mBAAmB,UAAU,SAAS,QAAQ,SAClH;IAAE,UAAU,KAAK;IAAM;GAAM,CAC/B;GAIA,MAAM,SAAS,oBAAoB,OADT,MAFH,KAAK,OAAO,MAAM,OAAO,OAAO,OAAO,EAAA,CAE3B,YAAY,CACD;GAE9C,MAAM,eAAe,QAAQ,mBAAmB;GAChD,MAAM,cAAc,KAAK,eAAe,YAAY;GAEpD,OAAO;IACL,IAAI,WAAW,KAAK,IAAI;IACxB;IACA,OAAO;IACP,QAAQ;IACR;GACF;EACF,SAAS,OAAgB;GAGvB,QAAQ,OAAO,OAAO,GAAG,KAAK,KAAK,wBAAwB;IACzD,OAAO,kBAAkB,OAAO,GAAG,KAAK,KAAK,uBAAuB;IACpE,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;CACF;CAEA,eAAuB,QAAwB;EAQ7C,OAAO;GANL,MAAM;GACN,KAAK;GACL,OAAO;GACP,KAAK;GACL,KAAK;EAEA,EAAa,WAAW;CACjC;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,iBACd,OACA,QACA,QACwB;CACxB,OAAO,IAAI,eAAe;EAAE;EAAQ,GAAG;CAAO,GAAG,KAAK;AACxD;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,WACd,OACA,QACwB;CAExB,OAAO,iBAAiB,OADT,qBACgB,GAAQ,MAAM;AAC/C"}
1
+ {"version":3,"file":"tts.js","names":[],"sources":["../../../src/adapters/tts.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { BaseTTSAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport { getGroqApiKeyFromEnv, withGroqDefaults } from '../utils/client'\nimport { validateAudioInput } from '../audio/audio-provider-options'\nimport type { TTSOptions, TTSResult } from '@tanstack/ai'\nimport type OpenAI_SDK from 'openai'\nimport type { GroqTTSModel } from '../model-meta'\nimport type { GroqTTSProviderOptions } from '../audio/tts-provider-options'\nimport type { GroqClientConfig } from '../utils/client'\n\n/**\n * Configuration for Groq TTS adapter\n */\nexport interface GroqTTSConfig extends GroqClientConfig {}\n\n/**\n * Groq Text-to-Speech Adapter\n *\n * Tree-shakeable adapter for Groq TTS functionality. Groq exposes an\n * OpenAI-compatible `/audio/speech` endpoint, so the adapter drives it with\n * the OpenAI SDK via a `baseURL` override (the same pattern as the Groq text\n * adapter).\n *\n * Supports `canopylabs/orpheus-v1-english` and\n * `canopylabs/orpheus-arabic-saudi`.\n *\n * Features:\n * - English voices: autumn(f), diana(f), hannah(f), austin(m), daniel(m), troy(m)\n * - Arabic voices: fahad(m), sultan(m), lulwa(f), noura(f)\n * - Output formats: flac, mp3, mulaw, ogg, wav (default wav)\n * - Speed control\n * - Configurable sample rate via `modelOptions`\n */\nexport class GroqTTSAdapter<TModel extends GroqTTSModel> extends BaseTTSAdapter<\n TModel,\n GroqTTSProviderOptions\n> {\n readonly name = 'groq' as const\n\n protected client: OpenAI\n\n constructor(config: GroqTTSConfig, model: TModel) {\n super(model, {})\n this.client = new OpenAI(withGroqDefaults(config))\n }\n\n async generateSpeech(\n options: TTSOptions<GroqTTSProviderOptions>,\n ): Promise<TTSResult> {\n const { model, text, voice, format, speed, modelOptions } = options\n\n validateAudioInput({ input: text, model: this.model })\n\n // Spreading optional inputs conditionally keeps the request compatible\n // with the vendor SDK shape under exactOptionalPropertyTypes. `sample_rate`\n // is a Groq-only body field carried via modelOptions.\n const request: OpenAI_SDK.Audio.SpeechCreateParams = {\n model,\n input: text,\n voice: voice ?? 'autumn',\n response_format: format ?? 'wav',\n ...(speed !== undefined && { speed }),\n ...(modelOptions ?? {}),\n }\n\n try {\n options.logger.request(\n `activity=tts provider=${this.name} model=${model} format=${request.response_format ?? 'default'} voice=${request.voice}`,\n { provider: this.name, model },\n )\n const response = await this.client.audio.speech.create(request)\n\n const arrayBuffer = await response.arrayBuffer()\n const base64 = arrayBufferToBase64(arrayBuffer)\n\n const outputFormat = request.response_format ?? 'wav'\n const contentType = this.getContentType(outputFormat)\n\n return {\n id: generateId(this.name),\n model,\n audio: base64,\n format: outputFormat,\n contentType,\n }\n } catch (error: unknown) {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n options.logger.errors(`${this.name}.generateSpeech fatal`, {\n error: toRunErrorPayload(error, `${this.name}.generateSpeech failed`),\n source: `${this.name}.generateSpeech`,\n })\n throw error\n }\n }\n\n private getContentType(format: string): string {\n const contentTypes: Record<string, string> = {\n flac: 'audio/flac',\n mp3: 'audio/mpeg',\n mulaw: 'audio/basic',\n ogg: 'audio/ogg',\n wav: 'audio/wav',\n }\n return contentTypes[format] || 'audio/wav'\n }\n}\n\n/**\n * Creates a Groq speech adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'canopylabs/orpheus-v1-english')\n * @param apiKey - Your Groq API key\n * @param config - Optional additional configuration\n * @returns Configured Groq speech adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGroqSpeech('canopylabs/orpheus-v1-english', 'gsk_...')\n *\n * const result = await generateSpeech({\n * adapter,\n * text: 'Hello, world!',\n * voice: 'autumn',\n * })\n * ```\n */\nexport function createGroqSpeech<TModel extends GroqTTSModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GroqTTSConfig, 'apiKey'>,\n): GroqTTSAdapter<TModel> {\n return new GroqTTSAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Groq speech adapter with automatic API key detection from\n * environment variables.\n *\n * Looks for `GROQ_API_KEY` in the environment.\n *\n * @param model - The model name (e.g., 'canopylabs/orpheus-v1-english')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Groq speech adapter instance with resolved types\n * @throws Error if GROQ_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = groqSpeech('canopylabs/orpheus-v1-english')\n *\n * const result = await generateSpeech({\n * adapter,\n * text: 'Welcome to TanStack AI!',\n * voice: 'autumn',\n * format: 'wav',\n * })\n * ```\n */\nexport function groqSpeech<TModel extends GroqTTSModel>(\n model: TModel,\n config?: Omit<GroqTTSConfig, 'apiKey'>,\n): GroqTTSAdapter<TModel> {\n const apiKey = getGroqApiKeyFromEnv()\n return createGroqSpeech(model, apiKey, config)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,IAAa,iBAAb,cAAiE,eAG/D;CACA,OAAgB;CAEhB;CAEA,YAAY,QAAuB,OAAe;EAChD,MAAM,OAAO,CAAC,CAAC;EACf,KAAK,SAAS,IAAI,OAAO,iBAAiB,MAAM,CAAC;CACnD;CAEA,MAAM,eACJ,SACoB;EACpB,MAAM,EAAE,OAAO,MAAM,OAAO,QAAQ,OAAO,iBAAiB;EAE5D,mBAAmB;GAAE,OAAO;GAAM,OAAO,KAAK;EAAM,CAAC;EAKrD,MAAM,UAA+C;GACnD;GACA,OAAO;GACP,OAAO,SAAS;GAChB,iBAAiB,UAAU;GAC3B,GAAI,UAAU,KAAA,KAAa,EAAE,MAAM;GACnC,GAAI,gBAAgB,CAAC;EACvB;EAEA,IAAI;GACF,QAAQ,OAAO,QACb,yBAAyB,KAAK,KAAK,SAAS,MAAM,UAAU,QAAQ,mBAAmB,UAAU,SAAS,QAAQ,SAClH;IAAE,UAAU,KAAK;IAAM;GAAM,CAC/B;GAGA,MAAM,cAAc,OAAM,MAFH,KAAK,OAAO,MAAM,OAAO,OAAO,OAAO,EAAA,CAE3B,YAAY;GAC/C,MAAM,SAAS,oBAAoB,WAAW;GAE9C,MAAM,eAAe,QAAQ,mBAAmB;GAChD,MAAM,cAAc,KAAK,eAAe,YAAY;GAEpD,OAAO;IACL,IAAI,WAAW,KAAK,IAAI;IACxB;IACA,OAAO;IACP,QAAQ;IACR;GACF;EACF,SAAS,OAAgB;GAGvB,QAAQ,OAAO,OAAO,GAAG,KAAK,KAAK,wBAAwB;IACzD,OAAO,kBAAkB,OAAO,GAAG,KAAK,KAAK,uBAAuB;IACpE,QAAQ,GAAG,KAAK,KAAK;GACvB,CAAC;GACD,MAAM;EACR;CACF;CAEA,eAAuB,QAAwB;EAQ7C,OAAO;GANL,MAAM;GACN,KAAK;GACL,OAAO;GACP,KAAK;GACL,KAAK;EAEA,EAAa,WAAW;CACjC;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,iBACd,OACA,QACA,QACwB;CACxB,OAAO,IAAI,eAAe;EAAE;EAAQ,GAAG;CAAO,GAAG,KAAK;AACxD;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,WACd,OACA,QACwB;CAExB,OAAO,iBAAiB,OADT,qBACgB,GAAQ,MAAM;AAC/C"}
@@ -65,7 +65,8 @@ function normalizeObjectSchemas(schema) {
65
65
  * @returns Transformed schema compatible with Groq structured output
66
66
  */
67
67
  function makeGroqStructuredOutputCompatible(schema, originalRequired = []) {
68
- return removeEmptyRequired(makeStructuredOutputCompatible(normalizeObjectSchemas(schema), originalRequired));
68
+ const normalised = normalizeObjectSchemas(schema);
69
+ return removeEmptyRequired(makeStructuredOutputCompatible(normalised, originalRequired));
69
70
  }
70
71
  //#endregion
71
72
  export { makeGroqStructuredOutputCompatible };
@@ -1 +1 @@
1
- {"version":3,"file":"schema-converter.js","names":[],"sources":["../../../src/utils/schema-converter.ts"],"sourcesContent":["import { makeStructuredOutputCompatible } from '@tanstack/openai-base'\n\n/**\n * Recursively removes `required: []` from a schema object.\n * Groq rejects `required` when it is an empty array, even though\n * OpenAI-compatible schemas allow it.\n */\nfunction removeEmptyRequired(schema: Record<string, any>): Record<string, any> {\n const result = { ...schema }\n\n if (Array.isArray(result.required) && result.required.length === 0) {\n delete result.required\n }\n\n if (result.properties && typeof result.properties === 'object') {\n const properties: Record<string, any> = {}\n for (const [key, value] of Object.entries(\n result.properties as Record<string, any>,\n )) {\n properties[key] =\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? removeEmptyRequired(value)\n : value\n }\n result.properties = properties\n }\n\n if (\n result.items &&\n typeof result.items === 'object' &&\n !Array.isArray(result.items)\n ) {\n result.items = removeEmptyRequired(result.items)\n }\n\n // Recurse into combinator arrays (anyOf, oneOf, allOf)\n for (const keyword of ['anyOf', 'oneOf', 'allOf'] as const) {\n if (Array.isArray(result[keyword])) {\n result[keyword] = result[keyword].map((entry: Record<string, any>) =>\n removeEmptyRequired(entry),\n )\n }\n }\n\n // Recurse into additionalProperties if it's a schema object\n if (\n result.additionalProperties &&\n typeof result.additionalProperties === 'object' &&\n !Array.isArray(result.additionalProperties)\n ) {\n result.additionalProperties = removeEmptyRequired(\n result.additionalProperties,\n )\n }\n\n return result\n}\n\n/**\n * Recursively normalise object schemas so any `{ type: 'object' }` node\n * without `properties` gets an empty `properties: {}` object. The\n * ai-openai-base transformer only descends into objects that already have\n * `properties` set, so a Zod `z.object({})` nested inside `properties`,\n * `items`, `additionalProperties`, or a combinator branch would otherwise\n * skip the strict-mode rewrite and fail Groq validation.\n */\nfunction normalizeObjectSchemas(\n schema: Record<string, any>,\n): Record<string, any> {\n const result: Record<string, any> =\n schema.type === 'object' && !schema.properties\n ? { ...schema, properties: {} }\n : { ...schema }\n\n if (result.properties && typeof result.properties === 'object') {\n result.properties = Object.fromEntries(\n Object.entries(result.properties as Record<string, any>).map(\n ([key, value]) => [\n key,\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? normalizeObjectSchemas(value)\n : value,\n ],\n ),\n )\n }\n\n if (\n result.items &&\n typeof result.items === 'object' &&\n !Array.isArray(result.items)\n ) {\n result.items = normalizeObjectSchemas(result.items)\n }\n\n for (const keyword of ['anyOf', 'oneOf', 'allOf'] as const) {\n const branch = result[keyword]\n if (Array.isArray(branch)) {\n result[keyword] = branch.map((entry) =>\n typeof entry === 'object' && entry !== null\n ? normalizeObjectSchemas(entry as Record<string, any>)\n : entry,\n )\n }\n }\n\n if (\n result.additionalProperties &&\n typeof result.additionalProperties === 'object' &&\n !Array.isArray(result.additionalProperties)\n ) {\n result.additionalProperties = normalizeObjectSchemas(\n result.additionalProperties as Record<string, any>,\n )\n }\n\n return result\n}\n\n/**\n * Transform a JSON schema to be compatible with Groq's structured output requirements.\n *\n * Groq requires:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for objects\n * - `required` must be omitted (not empty array) when there are no properties\n *\n * Delegates to the shared OpenAI-compatible transformer and applies the\n * Groq-specific quirk of removing empty `required` arrays.\n *\n * @param schema - JSON schema to transform\n * @param originalRequired - Original required array (to know which fields were optional)\n * @returns Transformed schema compatible with Groq structured output\n */\nexport function makeGroqStructuredOutputCompatible(\n schema: Record<string, any>,\n originalRequired: Array<string> = [],\n): Record<string, any> {\n // Recursively patch every `{ type: 'object' }` node so the ai-openai-base\n // transformer descends into nested empty objects too.\n const normalised = normalizeObjectSchemas(schema)\n\n const result = makeStructuredOutputCompatible(normalised, originalRequired)\n\n // Groq rejects `required` when it is an empty array\n return removeEmptyRequired(result)\n}\n"],"mappings":";;;;;;;AAOA,SAAS,oBAAoB,QAAkD;CAC7E,MAAM,SAAS,EAAE,GAAG,OAAO;CAE3B,IAAI,MAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,SAAS,WAAW,GAC/D,OAAO,OAAO;CAGhB,IAAI,OAAO,cAAc,OAAO,OAAO,eAAe,UAAU;EAC9D,MAAM,aAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAChC,OAAO,UACT,GACE,WAAW,OACT,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAC/D,oBAAoB,KAAK,IACzB;EAER,OAAO,aAAa;CACtB;CAEA,IACE,OAAO,SACP,OAAO,OAAO,UAAU,YACxB,CAAC,MAAM,QAAQ,OAAO,KAAK,GAE3B,OAAO,QAAQ,oBAAoB,OAAO,KAAK;CAIjD,KAAK,MAAM,WAAW;EAAC;EAAS;EAAS;CAAO,GAC9C,IAAI,MAAM,QAAQ,OAAO,QAAQ,GAC/B,OAAO,WAAW,OAAO,QAAQ,CAAC,KAAK,UACrC,oBAAoB,KAAK,CAC3B;CAKJ,IACE,OAAO,wBACP,OAAO,OAAO,yBAAyB,YACvC,CAAC,MAAM,QAAQ,OAAO,oBAAoB,GAE1C,OAAO,uBAAuB,oBAC5B,OAAO,oBACT;CAGF,OAAO;AACT;;;;;;;;;AAUA,SAAS,uBACP,QACqB;CACrB,MAAM,SACJ,OAAO,SAAS,YAAY,CAAC,OAAO,aAChC;EAAE,GAAG;EAAQ,YAAY,CAAC;CAAE,IAC5B,EAAE,GAAG,OAAO;CAElB,IAAI,OAAO,cAAc,OAAO,OAAO,eAAe,UACpD,OAAO,aAAa,OAAO,YACzB,OAAO,QAAQ,OAAO,UAAiC,CAAC,CAAC,KACtD,CAAC,KAAK,WAAW,CAChB,KACA,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAC/D,uBAAuB,KAAK,IAC5B,KACN,CACF,CACF;CAGF,IACE,OAAO,SACP,OAAO,OAAO,UAAU,YACxB,CAAC,MAAM,QAAQ,OAAO,KAAK,GAE3B,OAAO,QAAQ,uBAAuB,OAAO,KAAK;CAGpD,KAAK,MAAM,WAAW;EAAC;EAAS;EAAS;CAAO,GAAY;EAC1D,MAAM,SAAS,OAAO;EACtB,IAAI,MAAM,QAAQ,MAAM,GACtB,OAAO,WAAW,OAAO,KAAK,UAC5B,OAAO,UAAU,YAAY,UAAU,OACnC,uBAAuB,KAA4B,IACnD,KACN;CAEJ;CAEA,IACE,OAAO,wBACP,OAAO,OAAO,yBAAyB,YACvC,CAAC,MAAM,QAAQ,OAAO,oBAAoB,GAE1C,OAAO,uBAAuB,uBAC5B,OAAO,oBACT;CAGF,OAAO;AACT;;;;;;;;;;;;;;;;;AAkBA,SAAgB,mCACd,QACA,mBAAkC,CAAC,GACd;CAQrB,OAAO,oBAHQ,+BAFI,uBAAuB,MAEI,GAAY,gBAG/B,CAAM;AACnC"}
1
+ {"version":3,"file":"schema-converter.js","names":[],"sources":["../../../src/utils/schema-converter.ts"],"sourcesContent":["import { makeStructuredOutputCompatible } from '@tanstack/openai-base'\n\n/**\n * Recursively removes `required: []` from a schema object.\n * Groq rejects `required` when it is an empty array, even though\n * OpenAI-compatible schemas allow it.\n */\nfunction removeEmptyRequired(schema: Record<string, any>): Record<string, any> {\n const result = { ...schema }\n\n if (Array.isArray(result.required) && result.required.length === 0) {\n delete result.required\n }\n\n if (result.properties && typeof result.properties === 'object') {\n const properties: Record<string, any> = {}\n for (const [key, value] of Object.entries(\n result.properties as Record<string, any>,\n )) {\n properties[key] =\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? removeEmptyRequired(value)\n : value\n }\n result.properties = properties\n }\n\n if (\n result.items &&\n typeof result.items === 'object' &&\n !Array.isArray(result.items)\n ) {\n result.items = removeEmptyRequired(result.items)\n }\n\n // Recurse into combinator arrays (anyOf, oneOf, allOf)\n for (const keyword of ['anyOf', 'oneOf', 'allOf'] as const) {\n if (Array.isArray(result[keyword])) {\n result[keyword] = result[keyword].map((entry: Record<string, any>) =>\n removeEmptyRequired(entry),\n )\n }\n }\n\n // Recurse into additionalProperties if it's a schema object\n if (\n result.additionalProperties &&\n typeof result.additionalProperties === 'object' &&\n !Array.isArray(result.additionalProperties)\n ) {\n result.additionalProperties = removeEmptyRequired(\n result.additionalProperties,\n )\n }\n\n return result\n}\n\n/**\n * Recursively normalise object schemas so any `{ type: 'object' }` node\n * without `properties` gets an empty `properties: {}` object. The\n * ai-openai-base transformer only descends into objects that already have\n * `properties` set, so a Zod `z.object({})` nested inside `properties`,\n * `items`, `additionalProperties`, or a combinator branch would otherwise\n * skip the strict-mode rewrite and fail Groq validation.\n */\nfunction normalizeObjectSchemas(\n schema: Record<string, any>,\n): Record<string, any> {\n const result: Record<string, any> =\n schema.type === 'object' && !schema.properties\n ? { ...schema, properties: {} }\n : { ...schema }\n\n if (result.properties && typeof result.properties === 'object') {\n result.properties = Object.fromEntries(\n Object.entries(result.properties as Record<string, any>).map(\n ([key, value]) => [\n key,\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? normalizeObjectSchemas(value)\n : value,\n ],\n ),\n )\n }\n\n if (\n result.items &&\n typeof result.items === 'object' &&\n !Array.isArray(result.items)\n ) {\n result.items = normalizeObjectSchemas(result.items)\n }\n\n for (const keyword of ['anyOf', 'oneOf', 'allOf'] as const) {\n const branch = result[keyword]\n if (Array.isArray(branch)) {\n result[keyword] = branch.map((entry) =>\n typeof entry === 'object' && entry !== null\n ? normalizeObjectSchemas(entry as Record<string, any>)\n : entry,\n )\n }\n }\n\n if (\n result.additionalProperties &&\n typeof result.additionalProperties === 'object' &&\n !Array.isArray(result.additionalProperties)\n ) {\n result.additionalProperties = normalizeObjectSchemas(\n result.additionalProperties as Record<string, any>,\n )\n }\n\n return result\n}\n\n/**\n * Transform a JSON schema to be compatible with Groq's structured output requirements.\n *\n * Groq requires:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for objects\n * - `required` must be omitted (not empty array) when there are no properties\n *\n * Delegates to the shared OpenAI-compatible transformer and applies the\n * Groq-specific quirk of removing empty `required` arrays.\n *\n * @param schema - JSON schema to transform\n * @param originalRequired - Original required array (to know which fields were optional)\n * @returns Transformed schema compatible with Groq structured output\n */\nexport function makeGroqStructuredOutputCompatible(\n schema: Record<string, any>,\n originalRequired: Array<string> = [],\n): Record<string, any> {\n // Recursively patch every `{ type: 'object' }` node so the ai-openai-base\n // transformer descends into nested empty objects too.\n const normalised = normalizeObjectSchemas(schema)\n\n const result = makeStructuredOutputCompatible(normalised, originalRequired)\n\n // Groq rejects `required` when it is an empty array\n return removeEmptyRequired(result)\n}\n"],"mappings":";;;;;;;AAOA,SAAS,oBAAoB,QAAkD;CAC7E,MAAM,SAAS,EAAE,GAAG,OAAO;CAE3B,IAAI,MAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,SAAS,WAAW,GAC/D,OAAO,OAAO;CAGhB,IAAI,OAAO,cAAc,OAAO,OAAO,eAAe,UAAU;EAC9D,MAAM,aAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAChC,OAAO,UACT,GACE,WAAW,OACT,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAC/D,oBAAoB,KAAK,IACzB;EAER,OAAO,aAAa;CACtB;CAEA,IACE,OAAO,SACP,OAAO,OAAO,UAAU,YACxB,CAAC,MAAM,QAAQ,OAAO,KAAK,GAE3B,OAAO,QAAQ,oBAAoB,OAAO,KAAK;CAIjD,KAAK,MAAM,WAAW;EAAC;EAAS;EAAS;CAAO,GAC9C,IAAI,MAAM,QAAQ,OAAO,QAAQ,GAC/B,OAAO,WAAW,OAAO,QAAQ,CAAC,KAAK,UACrC,oBAAoB,KAAK,CAC3B;CAKJ,IACE,OAAO,wBACP,OAAO,OAAO,yBAAyB,YACvC,CAAC,MAAM,QAAQ,OAAO,oBAAoB,GAE1C,OAAO,uBAAuB,oBAC5B,OAAO,oBACT;CAGF,OAAO;AACT;;;;;;;;;AAUA,SAAS,uBACP,QACqB;CACrB,MAAM,SACJ,OAAO,SAAS,YAAY,CAAC,OAAO,aAChC;EAAE,GAAG;EAAQ,YAAY,CAAC;CAAE,IAC5B,EAAE,GAAG,OAAO;CAElB,IAAI,OAAO,cAAc,OAAO,OAAO,eAAe,UACpD,OAAO,aAAa,OAAO,YACzB,OAAO,QAAQ,OAAO,UAAiC,CAAC,CAAC,KACtD,CAAC,KAAK,WAAW,CAChB,KACA,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAC/D,uBAAuB,KAAK,IAC5B,KACN,CACF,CACF;CAGF,IACE,OAAO,SACP,OAAO,OAAO,UAAU,YACxB,CAAC,MAAM,QAAQ,OAAO,KAAK,GAE3B,OAAO,QAAQ,uBAAuB,OAAO,KAAK;CAGpD,KAAK,MAAM,WAAW;EAAC;EAAS;EAAS;CAAO,GAAY;EAC1D,MAAM,SAAS,OAAO;EACtB,IAAI,MAAM,QAAQ,MAAM,GACtB,OAAO,WAAW,OAAO,KAAK,UAC5B,OAAO,UAAU,YAAY,UAAU,OACnC,uBAAuB,KAA4B,IACnD,KACN;CAEJ;CAEA,IACE,OAAO,wBACP,OAAO,OAAO,yBAAyB,YACvC,CAAC,MAAM,QAAQ,OAAO,oBAAoB,GAE1C,OAAO,uBAAuB,uBAC5B,OAAO,oBACT;CAGF,OAAO;AACT;;;;;;;;;;;;;;;;;AAkBA,SAAgB,mCACd,QACA,mBAAkC,CAAC,GACd;CAGrB,MAAM,aAAa,uBAAuB,MAAM;CAKhD,OAAO,oBAHQ,+BAA+B,YAAY,gBAG/B,CAAM;AACnC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-groq",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Groq adapter for TanStack AI low-latency chat, tool calling, and structured outputs.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -48,17 +48,17 @@
48
48
  "structured-outputs"
49
49
  ],
50
50
  "devDependencies": {
51
- "@vitest/coverage-v8": "4.0.14",
52
- "vite": "^8.1.4"
51
+ "@vitest/coverage-v8": "4.1.10",
52
+ "vite": "^8.2.1"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "zod": "^4.0.0",
56
- "@tanstack/ai": "^0.44.0"
56
+ "@tanstack/ai": "^0.45.0"
57
57
  },
58
58
  "dependencies": {
59
59
  "openai": "^6.41.0",
60
- "@tanstack/openai-base": "^0.9.11",
61
- "@tanstack/ai-utils": "^0.4.0"
60
+ "@tanstack/ai-utils": "^0.4.0",
61
+ "@tanstack/openai-base": "^0.9.13"
62
62
  },
63
63
  "scripts": {
64
64
  "build": "vite build",