@tanstack/ai-groq 0.1.9 → 0.1.11
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.
- package/dist/esm/adapters/text.js +2 -1
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/utils/client.js +3 -9
- package/dist/esm/utils/client.js.map +1 -1
- package/dist/esm/utils/schema-converter.d.ts +6 -12
- package/dist/esm/utils/schema-converter.js +57 -62
- package/dist/esm/utils/schema-converter.js.map +1 -1
- package/package.json +5 -3
- package/src/utils/client.ts +3 -16
- package/src/utils/schema-converter.ts +121 -80
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { BaseTextAdapter } from "@tanstack/ai/adapters";
|
|
2
2
|
import { validateTextProviderOptions } from "../text/text-provider-options.js";
|
|
3
|
-
import { makeGroqStructuredOutputCompatible
|
|
3
|
+
import { makeGroqStructuredOutputCompatible } from "../utils/schema-converter.js";
|
|
4
4
|
import { convertToolsToProviderFormat } from "../tools/tool-converter.js";
|
|
5
5
|
import { createGroqClient, generateId, getGroqApiKeyFromEnv } from "../utils/client.js";
|
|
6
|
+
import { transformNullsToUndefined } from "@tanstack/ai-utils";
|
|
6
7
|
const asChunk = (chunk) => chunk;
|
|
7
8
|
class GroqTextAdapter extends BaseTextAdapter {
|
|
8
9
|
constructor(config, model) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","sources":["../../../src/adapters/text.ts"],"sourcesContent":["import { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { validateTextProviderOptions } from '../text/text-provider-options'\nimport { convertToolsToProviderFormat } from '../tools'\nimport {\n createGroqClient,\n generateId,\n getGroqApiKeyFromEnv,\n makeGroqStructuredOutputCompatible,\n transformNullsToUndefined,\n} from '../utils'\nimport type {\n GROQ_CHAT_MODELS,\n GroqChatModelToolCapabilitiesByName,\n ResolveInputModalities,\n ResolveProviderOptions,\n} from '../model-meta'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type GROQ_SDK from 'groq-sdk'\nimport type { ChatCompletionCreateParamsStreaming } from 'groq-sdk/resources/chat/completions'\nimport type {\n ContentPart,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type {\n ExternalTextProviderOptions,\n InternalTextProviderOptions,\n} from '../text/text-provider-options'\nimport type {\n ChatCompletionContentPart,\n ChatCompletionMessageParam,\n GroqImageMetadata,\n GroqMessageMetadataByModality,\n} from '../message-types'\nimport type { GroqClientConfig } from '../utils'\n\ntype GroqTextProviderOptions = ExternalTextProviderOptions\n\ntype ResolveToolCapabilities<TModel extends string> =\n TModel extends keyof GroqChatModelToolCapabilitiesByName\n ? NonNullable<GroqChatModelToolCapabilitiesByName[TModel]>\n : readonly []\n\n/** Cast an event object to StreamChunk. Adapters construct events with string\n * literal types which are structurally compatible with the EventType enum. */\nconst asChunk = (chunk: Record<string, unknown>) =>\n chunk as unknown as StreamChunk\n\n/**\n * Configuration for Groq text adapter\n */\nexport interface GroqTextConfig extends GroqClientConfig {}\n\n/**\n * Alias for TextProviderOptions for external use\n */\nexport type { ExternalTextProviderOptions as GroqTextProviderOptions } from '../text/text-provider-options'\n\n/**\n * Groq Text (Chat) Adapter\n *\n * Tree-shakeable adapter for Groq chat/text completion functionality.\n * Uses the Groq SDK which provides an OpenAI-compatible Chat Completions API.\n */\nexport class GroqTextAdapter<\n TModel extends (typeof GROQ_CHAT_MODELS)[number],\n TProviderOptions extends Record<string, any> = ResolveProviderOptions<TModel>,\n TInputModalities extends ReadonlyArray<Modality> =\n ResolveInputModalities<TModel>,\n TToolCapabilities extends ReadonlyArray<string> =\n ResolveToolCapabilities<TModel>,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n GroqMessageMetadataByModality,\n TToolCapabilities\n> {\n readonly kind = 'text' as const\n readonly name = 'groq' as const\n\n private client: GROQ_SDK\n\n constructor(config: GroqTextConfig, model: TModel) {\n super({}, model)\n this.client = createGroqClient(config)\n }\n\n async *chatStream(\n options: TextOptions<GroqTextProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const requestParams = this.mapTextOptionsToGroq(options)\n const timestamp = Date.now()\n const { logger } = options\n\n const aguiState = {\n runId: options.runId ?? generateId(this.name),\n threadId: options.threadId ?? generateId(this.name),\n messageId: generateId(this.name),\n timestamp,\n hasEmittedRunStarted: false,\n }\n\n try {\n logger.request(\n `activity=chat provider=groq model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,\n { provider: 'groq', model: this.model },\n )\n const stream = await this.client.chat.completions.create({\n ...requestParams,\n stream: true,\n })\n\n yield* this.processGroqStreamChunks(stream, options, aguiState, logger)\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield asChunk({\n type: 'RUN_STARTED',\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: options.model,\n timestamp,\n })\n }\n\n yield asChunk({\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n message: err.message || 'Unknown error',\n code: err.code,\n error: {\n message: err.message || 'Unknown error',\n code: err.code,\n },\n })\n\n logger.errors('groq.chatStream fatal', {\n error,\n source: 'groq.chatStream',\n })\n }\n }\n\n /**\n * Generate structured output using Groq's JSON Schema response format.\n * Uses stream: false to get the complete response in one call.\n *\n * Groq has strict requirements for structured output:\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 all objects\n *\n * The outputSchema is already JSON Schema (converted in the ai layer).\n * We apply Groq-specific transformations for structured output compatibility.\n */\n async structuredOutput(\n options: StructuredOutputOptions<GroqTextProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const requestParams = this.mapTextOptionsToGroq(chatOptions)\n const { logger } = chatOptions\n\n const jsonSchema = makeGroqStructuredOutputCompatible(\n outputSchema,\n outputSchema.required || [],\n )\n\n try {\n logger.request(\n `activity=chat provider=groq model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,\n { provider: 'groq', model: this.model },\n )\n const response = await this.client.chat.completions.create({\n ...requestParams,\n stream: false,\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'structured_output',\n schema: jsonSchema,\n strict: true,\n },\n },\n })\n\n const rawText = response.choices[0]?.message.content || ''\n\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n const transformed = transformNullsToUndefined(parsed)\n\n return {\n data: transformed,\n rawText,\n }\n } catch (error: unknown) {\n logger.errors('groq.structuredOutput fatal', {\n error,\n source: 'groq.structuredOutput',\n })\n throw error\n }\n }\n\n /**\n * Processes streaming chunks from the Groq API and yields AG-UI stream events.\n * Handles text content deltas, tool call assembly, and lifecycle events.\n */\n private async *processGroqStreamChunks(\n stream: AsyncIterable<GROQ_SDK.Chat.Completions.ChatCompletionChunk>,\n options: TextOptions,\n aguiState: {\n runId: string\n threadId: string\n messageId: string\n timestamp: number\n hasEmittedRunStarted: boolean\n },\n logger: InternalLogger,\n ): AsyncIterable<StreamChunk> {\n let accumulatedContent = ''\n const timestamp = aguiState.timestamp\n let hasEmittedTextMessageStart = false\n\n const toolCallsInProgress = new Map<\n number,\n {\n id: string\n name: string\n arguments: string\n started: boolean\n }\n >()\n\n try {\n for await (const chunk of stream) {\n logger.provider(`provider=groq`, { chunk })\n const choice = chunk.choices[0]\n\n if (!choice) continue\n\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield asChunk({\n type: 'RUN_STARTED',\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chunk.model || options.model,\n timestamp,\n })\n }\n\n const delta = choice.delta\n const deltaContent = delta.content\n const deltaToolCalls = delta.tool_calls\n\n if (deltaContent) {\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield asChunk({\n type: 'TEXT_MESSAGE_START',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n role: 'assistant',\n })\n }\n\n accumulatedContent += deltaContent\n\n yield asChunk({\n type: 'TEXT_MESSAGE_CONTENT',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n delta: deltaContent,\n content: accumulatedContent,\n })\n }\n\n if (deltaToolCalls) {\n for (const toolCallDelta of deltaToolCalls) {\n const index = toolCallDelta.index\n\n if (!toolCallsInProgress.has(index)) {\n toolCallsInProgress.set(index, {\n id: toolCallDelta.id || '',\n name: toolCallDelta.function?.name || '',\n arguments: '',\n started: false,\n })\n }\n\n const toolCall = toolCallsInProgress.get(index)!\n\n if (toolCallDelta.id) {\n toolCall.id = toolCallDelta.id\n }\n if (toolCallDelta.function?.name) {\n toolCall.name = toolCallDelta.function.name\n }\n if (toolCallDelta.function?.arguments) {\n toolCall.arguments += toolCallDelta.function.arguments\n }\n\n if (toolCall.id && toolCall.name && !toolCall.started) {\n toolCall.started = true\n yield asChunk({\n type: 'TOOL_CALL_START',\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp,\n index,\n })\n }\n\n if (toolCallDelta.function?.arguments && toolCall.started) {\n yield asChunk({\n type: 'TOOL_CALL_ARGS',\n toolCallId: toolCall.id,\n model: chunk.model || options.model,\n timestamp,\n delta: toolCallDelta.function.arguments,\n })\n }\n }\n }\n\n if (choice.finish_reason) {\n if (\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ) {\n for (const [, toolCall] of toolCallsInProgress) {\n if (!toolCall.started || !toolCall.id || !toolCall.name) {\n continue\n }\n\n let parsedInput: unknown = {}\n try {\n parsedInput = toolCall.arguments\n ? JSON.parse(toolCall.arguments)\n : {}\n } catch {\n parsedInput = {}\n }\n\n yield asChunk({\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp,\n input: parsedInput,\n })\n }\n }\n\n const computedFinishReason =\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ? 'tool_calls'\n : choice.finish_reason === 'length'\n ? 'length'\n : 'stop'\n\n if (hasEmittedTextMessageStart) {\n yield asChunk({\n type: 'TEXT_MESSAGE_END',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n })\n }\n\n const groqUsage = chunk.x_groq?.usage\n\n yield asChunk({\n type: 'RUN_FINISHED',\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chunk.model || options.model,\n timestamp,\n usage: groqUsage\n ? {\n promptTokens: groqUsage.prompt_tokens || 0,\n completionTokens: groqUsage.completion_tokens || 0,\n totalTokens: groqUsage.total_tokens || 0,\n }\n : undefined,\n finishReason: computedFinishReason,\n })\n }\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n logger.errors('groq stream ended with error', {\n error,\n source: 'groq.processGroqStreamChunks',\n })\n\n yield asChunk({\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n message: err.message || 'Unknown error occurred',\n code: err.code,\n error: {\n message: err.message || 'Unknown error occurred',\n code: err.code,\n },\n })\n }\n }\n\n /**\n * Maps common TextOptions to Groq-specific Chat Completions request parameters.\n */\n private mapTextOptionsToGroq(\n options: TextOptions,\n ): ChatCompletionCreateParamsStreaming {\n const modelOptions = options.modelOptions as\n | Omit<\n InternalTextProviderOptions,\n 'max_tokens' | 'tools' | 'temperature' | 'input' | 'top_p'\n >\n | undefined\n\n if (modelOptions) {\n validateTextProviderOptions({\n ...modelOptions,\n model: options.model,\n })\n }\n\n const tools = options.tools\n ? convertToolsToProviderFormat(options.tools)\n : undefined\n\n const messages: Array<ChatCompletionMessageParam> = []\n\n if (options.systemPrompts && options.systemPrompts.length > 0) {\n messages.push({\n role: 'system',\n content: options.systemPrompts.join('\\n'),\n })\n }\n\n for (const message of options.messages) {\n messages.push(this.convertMessageToGroq(message))\n }\n\n return {\n model: options.model,\n messages,\n temperature: options.temperature,\n max_tokens: options.maxTokens,\n top_p: options.topP,\n tools,\n stream: true,\n }\n }\n\n /**\n * Converts a TanStack AI ModelMessage to a Groq ChatCompletionMessageParam.\n * Handles tool, assistant, and user messages including multimodal content.\n */\n private convertMessageToGroq(\n message: ModelMessage,\n ): ChatCompletionMessageParam {\n if (message.role === 'tool') {\n return {\n role: 'tool',\n tool_call_id: message.toolCallId || '',\n content:\n typeof message.content === 'string'\n ? message.content\n : JSON.stringify(message.content),\n }\n }\n\n if (message.role === 'assistant') {\n const toolCalls = message.toolCalls?.map((tc) => ({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === 'string'\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }))\n\n return {\n role: 'assistant',\n content: this.extractTextContent(message.content),\n ...(toolCalls && toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),\n }\n }\n\n const contentParts = this.normalizeContent(message.content)\n\n if (contentParts.length === 1 && contentParts[0]?.type === 'text') {\n return {\n role: 'user',\n content: contentParts[0].content,\n }\n }\n\n const parts: Array<ChatCompletionContentPart> = []\n for (const part of contentParts) {\n if (part.type === 'text') {\n parts.push({ type: 'text', text: part.content })\n } else if (part.type === 'image') {\n const imageMetadata = part.metadata as GroqImageMetadata | undefined\n const imageValue = part.source.value\n const imageUrl =\n part.source.type === 'data' && !imageValue.startsWith('data:')\n ? `data:${part.source.mimeType};base64,${imageValue}`\n : imageValue\n parts.push({\n type: 'image_url',\n image_url: {\n url: imageUrl,\n detail: imageMetadata?.detail || 'auto',\n },\n })\n }\n }\n\n return {\n role: 'user',\n content: parts.length > 0 ? parts : '',\n }\n }\n\n /**\n * Normalizes message content to an array of ContentPart.\n * Handles backward compatibility with string content.\n */\n private normalizeContent(\n content: string | null | Array<ContentPart>,\n ): Array<ContentPart> {\n if (content === null) {\n return []\n }\n if (typeof content === 'string') {\n return [{ type: 'text', content: content }]\n }\n return content\n }\n\n /**\n * Extracts text content from a content value that may be string, null, or ContentPart array.\n */\n private extractTextContent(\n content: string | null | Array<ContentPart>,\n ): string {\n if (content === null) {\n return ''\n }\n if (typeof content === 'string') {\n return content\n }\n return content\n .filter((p) => p.type === 'text')\n .map((p) => p.content)\n .join('')\n }\n}\n\n/**\n * Creates a Groq text adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'llama-3.3-70b-versatile', 'openai/gpt-oss-120b')\n * @param apiKey - Your Groq API key\n * @param config - Optional additional configuration\n * @returns Configured Groq text adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGroqText('llama-3.3-70b-versatile', \"gsk_...\");\n * // adapter has type-safe providerOptions for llama-3.3-70b-versatile\n * ```\n */\nexport function createGroqText<\n TModel extends (typeof GROQ_CHAT_MODELS)[number],\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<GroqTextConfig, 'apiKey'>,\n): GroqTextAdapter<TModel> {\n return new GroqTextAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Groq text adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `GROQ_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'llama-3.3-70b-versatile', 'openai/gpt-oss-120b')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Groq text adapter instance with resolved types\n * @throws Error if GROQ_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses GROQ_API_KEY from environment\n * const adapter = groqText('llama-3.3-70b-versatile');\n *\n * const stream = chat({\n * adapter,\n * messages: [{ role: \"user\", content: \"Hello!\" }]\n * });\n * ```\n */\nexport function groqText<TModel extends (typeof GROQ_CHAT_MODELS)[number]>(\n model: TModel,\n config?: Omit<GroqTextConfig, 'apiKey'>,\n): GroqTextAdapter<TModel> {\n const apiKey = getGroqApiKeyFromEnv()\n return createGroqText(model, apiKey, config)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"text.js","sources":["../../../src/adapters/text.ts"],"sourcesContent":["import { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { validateTextProviderOptions } from '../text/text-provider-options'\nimport { convertToolsToProviderFormat } from '../tools'\nimport {\n createGroqClient,\n generateId,\n getGroqApiKeyFromEnv,\n makeGroqStructuredOutputCompatible,\n transformNullsToUndefined,\n} from '../utils'\nimport type {\n GROQ_CHAT_MODELS,\n GroqChatModelToolCapabilitiesByName,\n ResolveInputModalities,\n ResolveProviderOptions,\n} from '../model-meta'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type GROQ_SDK from 'groq-sdk'\nimport type { ChatCompletionCreateParamsStreaming } from 'groq-sdk/resources/chat/completions'\nimport type {\n ContentPart,\n Modality,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type {\n ExternalTextProviderOptions,\n InternalTextProviderOptions,\n} from '../text/text-provider-options'\nimport type {\n ChatCompletionContentPart,\n ChatCompletionMessageParam,\n GroqImageMetadata,\n GroqMessageMetadataByModality,\n} from '../message-types'\nimport type { GroqClientConfig } from '../utils'\n\ntype GroqTextProviderOptions = ExternalTextProviderOptions\n\ntype ResolveToolCapabilities<TModel extends string> =\n TModel extends keyof GroqChatModelToolCapabilitiesByName\n ? NonNullable<GroqChatModelToolCapabilitiesByName[TModel]>\n : readonly []\n\n/** Cast an event object to StreamChunk. Adapters construct events with string\n * literal types which are structurally compatible with the EventType enum. */\nconst asChunk = (chunk: Record<string, unknown>) =>\n chunk as unknown as StreamChunk\n\n/**\n * Configuration for Groq text adapter\n */\nexport interface GroqTextConfig extends GroqClientConfig {}\n\n/**\n * Alias for TextProviderOptions for external use\n */\nexport type { ExternalTextProviderOptions as GroqTextProviderOptions } from '../text/text-provider-options'\n\n/**\n * Groq Text (Chat) Adapter\n *\n * Tree-shakeable adapter for Groq chat/text completion functionality.\n * Uses the Groq SDK which provides an OpenAI-compatible Chat Completions API.\n */\nexport class GroqTextAdapter<\n TModel extends (typeof GROQ_CHAT_MODELS)[number],\n TProviderOptions extends Record<string, any> = ResolveProviderOptions<TModel>,\n TInputModalities extends ReadonlyArray<Modality> =\n ResolveInputModalities<TModel>,\n TToolCapabilities extends ReadonlyArray<string> =\n ResolveToolCapabilities<TModel>,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n GroqMessageMetadataByModality,\n TToolCapabilities\n> {\n readonly kind = 'text' as const\n readonly name = 'groq' as const\n\n private client: GROQ_SDK\n\n constructor(config: GroqTextConfig, model: TModel) {\n super({}, model)\n this.client = createGroqClient(config)\n }\n\n async *chatStream(\n options: TextOptions<GroqTextProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const requestParams = this.mapTextOptionsToGroq(options)\n const timestamp = Date.now()\n const { logger } = options\n\n const aguiState = {\n runId: options.runId ?? generateId(this.name),\n threadId: options.threadId ?? generateId(this.name),\n messageId: generateId(this.name),\n timestamp,\n hasEmittedRunStarted: false,\n }\n\n try {\n logger.request(\n `activity=chat provider=groq model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,\n { provider: 'groq', model: this.model },\n )\n const stream = await this.client.chat.completions.create({\n ...requestParams,\n stream: true,\n })\n\n yield* this.processGroqStreamChunks(stream, options, aguiState, logger)\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield asChunk({\n type: 'RUN_STARTED',\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: options.model,\n timestamp,\n })\n }\n\n yield asChunk({\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n message: err.message || 'Unknown error',\n code: err.code,\n error: {\n message: err.message || 'Unknown error',\n code: err.code,\n },\n })\n\n logger.errors('groq.chatStream fatal', {\n error,\n source: 'groq.chatStream',\n })\n }\n }\n\n /**\n * Generate structured output using Groq's JSON Schema response format.\n * Uses stream: false to get the complete response in one call.\n *\n * Groq has strict requirements for structured output:\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 all objects\n *\n * The outputSchema is already JSON Schema (converted in the ai layer).\n * We apply Groq-specific transformations for structured output compatibility.\n */\n async structuredOutput(\n options: StructuredOutputOptions<GroqTextProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const requestParams = this.mapTextOptionsToGroq(chatOptions)\n const { logger } = chatOptions\n\n const jsonSchema = makeGroqStructuredOutputCompatible(\n outputSchema,\n outputSchema.required || [],\n )\n\n try {\n logger.request(\n `activity=chat provider=groq model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,\n { provider: 'groq', model: this.model },\n )\n const response = await this.client.chat.completions.create({\n ...requestParams,\n stream: false,\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'structured_output',\n schema: jsonSchema,\n strict: true,\n },\n },\n })\n\n const rawText = response.choices[0]?.message.content || ''\n\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n const transformed = transformNullsToUndefined(parsed)\n\n return {\n data: transformed,\n rawText,\n }\n } catch (error: unknown) {\n logger.errors('groq.structuredOutput fatal', {\n error,\n source: 'groq.structuredOutput',\n })\n throw error\n }\n }\n\n /**\n * Processes streaming chunks from the Groq API and yields AG-UI stream events.\n * Handles text content deltas, tool call assembly, and lifecycle events.\n */\n private async *processGroqStreamChunks(\n stream: AsyncIterable<GROQ_SDK.Chat.Completions.ChatCompletionChunk>,\n options: TextOptions,\n aguiState: {\n runId: string\n threadId: string\n messageId: string\n timestamp: number\n hasEmittedRunStarted: boolean\n },\n logger: InternalLogger,\n ): AsyncIterable<StreamChunk> {\n let accumulatedContent = ''\n const timestamp = aguiState.timestamp\n let hasEmittedTextMessageStart = false\n\n const toolCallsInProgress = new Map<\n number,\n {\n id: string\n name: string\n arguments: string\n started: boolean\n }\n >()\n\n try {\n for await (const chunk of stream) {\n logger.provider(`provider=groq`, { chunk })\n const choice = chunk.choices[0]\n\n if (!choice) continue\n\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield asChunk({\n type: 'RUN_STARTED',\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chunk.model || options.model,\n timestamp,\n })\n }\n\n const delta = choice.delta\n const deltaContent = delta.content\n const deltaToolCalls = delta.tool_calls\n\n if (deltaContent) {\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield asChunk({\n type: 'TEXT_MESSAGE_START',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n role: 'assistant',\n })\n }\n\n accumulatedContent += deltaContent\n\n yield asChunk({\n type: 'TEXT_MESSAGE_CONTENT',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n delta: deltaContent,\n content: accumulatedContent,\n })\n }\n\n if (deltaToolCalls) {\n for (const toolCallDelta of deltaToolCalls) {\n const index = toolCallDelta.index\n\n if (!toolCallsInProgress.has(index)) {\n toolCallsInProgress.set(index, {\n id: toolCallDelta.id || '',\n name: toolCallDelta.function?.name || '',\n arguments: '',\n started: false,\n })\n }\n\n const toolCall = toolCallsInProgress.get(index)!\n\n if (toolCallDelta.id) {\n toolCall.id = toolCallDelta.id\n }\n if (toolCallDelta.function?.name) {\n toolCall.name = toolCallDelta.function.name\n }\n if (toolCallDelta.function?.arguments) {\n toolCall.arguments += toolCallDelta.function.arguments\n }\n\n if (toolCall.id && toolCall.name && !toolCall.started) {\n toolCall.started = true\n yield asChunk({\n type: 'TOOL_CALL_START',\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp,\n index,\n })\n }\n\n if (toolCallDelta.function?.arguments && toolCall.started) {\n yield asChunk({\n type: 'TOOL_CALL_ARGS',\n toolCallId: toolCall.id,\n model: chunk.model || options.model,\n timestamp,\n delta: toolCallDelta.function.arguments,\n })\n }\n }\n }\n\n if (choice.finish_reason) {\n if (\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ) {\n for (const [, toolCall] of toolCallsInProgress) {\n if (!toolCall.started || !toolCall.id || !toolCall.name) {\n continue\n }\n\n let parsedInput: unknown = {}\n try {\n parsedInput = toolCall.arguments\n ? JSON.parse(toolCall.arguments)\n : {}\n } catch {\n parsedInput = {}\n }\n\n yield asChunk({\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp,\n input: parsedInput,\n })\n }\n }\n\n const computedFinishReason =\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ? 'tool_calls'\n : choice.finish_reason === 'length'\n ? 'length'\n : 'stop'\n\n if (hasEmittedTextMessageStart) {\n yield asChunk({\n type: 'TEXT_MESSAGE_END',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n })\n }\n\n const groqUsage = chunk.x_groq?.usage\n\n yield asChunk({\n type: 'RUN_FINISHED',\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chunk.model || options.model,\n timestamp,\n usage: groqUsage\n ? {\n promptTokens: groqUsage.prompt_tokens || 0,\n completionTokens: groqUsage.completion_tokens || 0,\n totalTokens: groqUsage.total_tokens || 0,\n }\n : undefined,\n finishReason: computedFinishReason,\n })\n }\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n logger.errors('groq stream ended with error', {\n error,\n source: 'groq.processGroqStreamChunks',\n })\n\n yield asChunk({\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n message: err.message || 'Unknown error occurred',\n code: err.code,\n error: {\n message: err.message || 'Unknown error occurred',\n code: err.code,\n },\n })\n }\n }\n\n /**\n * Maps common TextOptions to Groq-specific Chat Completions request parameters.\n */\n private mapTextOptionsToGroq(\n options: TextOptions,\n ): ChatCompletionCreateParamsStreaming {\n const modelOptions = options.modelOptions as\n | Omit<\n InternalTextProviderOptions,\n 'max_tokens' | 'tools' | 'temperature' | 'input' | 'top_p'\n >\n | undefined\n\n if (modelOptions) {\n validateTextProviderOptions({\n ...modelOptions,\n model: options.model,\n })\n }\n\n const tools = options.tools\n ? convertToolsToProviderFormat(options.tools)\n : undefined\n\n const messages: Array<ChatCompletionMessageParam> = []\n\n if (options.systemPrompts && options.systemPrompts.length > 0) {\n messages.push({\n role: 'system',\n content: options.systemPrompts.join('\\n'),\n })\n }\n\n for (const message of options.messages) {\n messages.push(this.convertMessageToGroq(message))\n }\n\n return {\n model: options.model,\n messages,\n temperature: options.temperature,\n max_tokens: options.maxTokens,\n top_p: options.topP,\n tools,\n stream: true,\n }\n }\n\n /**\n * Converts a TanStack AI ModelMessage to a Groq ChatCompletionMessageParam.\n * Handles tool, assistant, and user messages including multimodal content.\n */\n private convertMessageToGroq(\n message: ModelMessage,\n ): ChatCompletionMessageParam {\n if (message.role === 'tool') {\n return {\n role: 'tool',\n tool_call_id: message.toolCallId || '',\n content:\n typeof message.content === 'string'\n ? message.content\n : JSON.stringify(message.content),\n }\n }\n\n if (message.role === 'assistant') {\n const toolCalls = message.toolCalls?.map((tc) => ({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === 'string'\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }))\n\n return {\n role: 'assistant',\n content: this.extractTextContent(message.content),\n ...(toolCalls && toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),\n }\n }\n\n const contentParts = this.normalizeContent(message.content)\n\n if (contentParts.length === 1 && contentParts[0]?.type === 'text') {\n return {\n role: 'user',\n content: contentParts[0].content,\n }\n }\n\n const parts: Array<ChatCompletionContentPart> = []\n for (const part of contentParts) {\n if (part.type === 'text') {\n parts.push({ type: 'text', text: part.content })\n } else if (part.type === 'image') {\n const imageMetadata = part.metadata as GroqImageMetadata | undefined\n const imageValue = part.source.value\n const imageUrl =\n part.source.type === 'data' && !imageValue.startsWith('data:')\n ? `data:${part.source.mimeType};base64,${imageValue}`\n : imageValue\n parts.push({\n type: 'image_url',\n image_url: {\n url: imageUrl,\n detail: imageMetadata?.detail || 'auto',\n },\n })\n }\n }\n\n return {\n role: 'user',\n content: parts.length > 0 ? parts : '',\n }\n }\n\n /**\n * Normalizes message content to an array of ContentPart.\n * Handles backward compatibility with string content.\n */\n private normalizeContent(\n content: string | null | Array<ContentPart>,\n ): Array<ContentPart> {\n if (content === null) {\n return []\n }\n if (typeof content === 'string') {\n return [{ type: 'text', content: content }]\n }\n return content\n }\n\n /**\n * Extracts text content from a content value that may be string, null, or ContentPart array.\n */\n private extractTextContent(\n content: string | null | Array<ContentPart>,\n ): string {\n if (content === null) {\n return ''\n }\n if (typeof content === 'string') {\n return content\n }\n return content\n .filter((p) => p.type === 'text')\n .map((p) => p.content)\n .join('')\n }\n}\n\n/**\n * Creates a Groq text adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'llama-3.3-70b-versatile', 'openai/gpt-oss-120b')\n * @param apiKey - Your Groq API key\n * @param config - Optional additional configuration\n * @returns Configured Groq text adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGroqText('llama-3.3-70b-versatile', \"gsk_...\");\n * // adapter has type-safe providerOptions for llama-3.3-70b-versatile\n * ```\n */\nexport function createGroqText<\n TModel extends (typeof GROQ_CHAT_MODELS)[number],\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<GroqTextConfig, 'apiKey'>,\n): GroqTextAdapter<TModel> {\n return new GroqTextAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Groq text adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `GROQ_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'llama-3.3-70b-versatile', 'openai/gpt-oss-120b')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Groq text adapter instance with resolved types\n * @throws Error if GROQ_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses GROQ_API_KEY from environment\n * const adapter = groqText('llama-3.3-70b-versatile');\n *\n * const stream = chat({\n * adapter,\n * messages: [{ role: \"user\", content: \"Hello!\" }]\n * });\n * ```\n */\nexport function groqText<TModel extends (typeof GROQ_CHAT_MODELS)[number]>(\n model: TModel,\n config?: Omit<GroqTextConfig, 'apiKey'>,\n): GroqTextAdapter<TModel> {\n const apiKey = getGroqApiKeyFromEnv()\n return createGroqText(model, apiKey, config)\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/utils/client.js
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
+
import { generateId as generateId$1, getApiKeyFromEnv } from "@tanstack/ai-utils";
|
|
1
2
|
import Groq_SDK from "groq-sdk";
|
|
2
3
|
function createGroqClient(config) {
|
|
3
4
|
return new Groq_SDK(config);
|
|
4
5
|
}
|
|
5
6
|
function getGroqApiKeyFromEnv() {
|
|
6
|
-
|
|
7
|
-
const key = env?.GROQ_API_KEY;
|
|
8
|
-
if (!key) {
|
|
9
|
-
throw new Error(
|
|
10
|
-
"GROQ_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key."
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
return key;
|
|
7
|
+
return getApiKeyFromEnv("GROQ_API_KEY");
|
|
14
8
|
}
|
|
15
9
|
function generateId(prefix) {
|
|
16
|
-
return
|
|
10
|
+
return generateId$1(prefix);
|
|
17
11
|
}
|
|
18
12
|
export {
|
|
19
13
|
createGroqClient,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import Groq_SDK from 'groq-sdk'\nimport type { ClientOptions } from 'groq-sdk'\n\nexport interface GroqClientConfig extends ClientOptions {\n apiKey: string\n}\n\n/**\n * Creates a Groq SDK client instance\n */\nexport function createGroqClient(config: GroqClientConfig): Groq_SDK {\n return new Groq_SDK(config)\n}\n\n/**\n * Gets Groq API key from environment variables\n * @throws Error if GROQ_API_KEY is not found\n */\nexport function getGroqApiKeyFromEnv(): string {\n
|
|
1
|
+
{"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import { generateId as _generateId, getApiKeyFromEnv } from '@tanstack/ai-utils'\nimport Groq_SDK from 'groq-sdk'\nimport type { ClientOptions } from 'groq-sdk'\n\nexport interface GroqClientConfig extends ClientOptions {\n apiKey: string\n}\n\n/**\n * Creates a Groq SDK client instance\n */\nexport function createGroqClient(config: GroqClientConfig): Groq_SDK {\n return new Groq_SDK(config)\n}\n\n/**\n * Gets Groq API key from environment variables\n * @throws Error if GROQ_API_KEY is not found\n */\nexport function getGroqApiKeyFromEnv(): string {\n return getApiKeyFromEnv('GROQ_API_KEY')\n}\n\n/**\n * Generates a unique ID with a prefix\n */\nexport function generateId(prefix: string): string {\n return _generateId(prefix)\n}\n"],"names":["_generateId"],"mappings":";;AAWO,SAAS,iBAAiB,QAAoC;AACnE,SAAO,IAAI,SAAS,MAAM;AAC5B;AAMO,SAAS,uBAA+B;AAC7C,SAAO,iBAAiB,cAAc;AACxC;AAKO,SAAS,WAAW,QAAwB;AACjD,SAAOA,aAAY,MAAM;AAC3B;"}
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* This is needed because Groq's structured output requires all fields to be
|
|
5
|
-
* in the `required` array, with optional fields made nullable (type: ["string", "null"]).
|
|
6
|
-
* When Groq returns null for optional fields, we need to convert them back to
|
|
7
|
-
* undefined to match the original Zod schema expectations.
|
|
8
|
-
*
|
|
9
|
-
* @param obj - Object to transform
|
|
10
|
-
* @returns Object with nulls converted to undefined
|
|
11
|
-
*/
|
|
12
|
-
export declare function transformNullsToUndefined<T>(obj: T): T;
|
|
1
|
+
import { transformNullsToUndefined } from '@tanstack/ai-utils';
|
|
2
|
+
export { transformNullsToUndefined };
|
|
13
3
|
/**
|
|
14
4
|
* Transform a JSON schema to be compatible with Groq's structured output requirements.
|
|
15
5
|
*
|
|
@@ -17,6 +7,10 @@ export declare function transformNullsToUndefined<T>(obj: T): T;
|
|
|
17
7
|
* - All properties must be in the `required` array
|
|
18
8
|
* - Optional fields should have null added to their type union
|
|
19
9
|
* - additionalProperties must be false for objects
|
|
10
|
+
* - `required` must be omitted (not empty array) when there are no properties
|
|
11
|
+
*
|
|
12
|
+
* Delegates to the shared OpenAI-compatible transformer and applies the
|
|
13
|
+
* Groq-specific quirk of removing empty `required` arrays.
|
|
20
14
|
*
|
|
21
15
|
* @param schema - JSON schema to transform
|
|
22
16
|
* @param originalRequired - Original required array (to know which fields were optional)
|
|
@@ -1,76 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { makeStructuredOutputCompatible } from "@tanstack/openai-base";
|
|
2
|
+
import { transformNullsToUndefined } from "@tanstack/ai-utils";
|
|
3
|
+
function removeEmptyRequired(schema) {
|
|
4
|
+
const result = { ...schema };
|
|
5
|
+
if (Array.isArray(result.required) && result.required.length === 0) {
|
|
6
|
+
delete result.required;
|
|
4
7
|
}
|
|
5
|
-
if (
|
|
6
|
-
|
|
8
|
+
if (result.properties && typeof result.properties === "object") {
|
|
9
|
+
const properties = {};
|
|
10
|
+
for (const [key, value] of Object.entries(
|
|
11
|
+
result.properties
|
|
12
|
+
)) {
|
|
13
|
+
properties[key] = typeof value === "object" && value !== null && !Array.isArray(value) ? removeEmptyRequired(value) : value;
|
|
14
|
+
}
|
|
15
|
+
result.properties = properties;
|
|
7
16
|
}
|
|
8
|
-
if (typeof
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
if (result.items && typeof result.items === "object" && !Array.isArray(result.items)) {
|
|
18
|
+
result.items = removeEmptyRequired(result.items);
|
|
19
|
+
}
|
|
20
|
+
for (const keyword of ["anyOf", "oneOf", "allOf"]) {
|
|
21
|
+
if (Array.isArray(result[keyword])) {
|
|
22
|
+
result[keyword] = result[keyword].map(
|
|
23
|
+
(entry) => removeEmptyRequired(entry)
|
|
24
|
+
);
|
|
15
25
|
}
|
|
16
|
-
return result;
|
|
17
26
|
}
|
|
18
|
-
|
|
27
|
+
if (result.additionalProperties && typeof result.additionalProperties === "object" && !Array.isArray(result.additionalProperties)) {
|
|
28
|
+
result.additionalProperties = removeEmptyRequired(
|
|
29
|
+
result.additionalProperties
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
19
33
|
}
|
|
20
|
-
function
|
|
21
|
-
const result = { ...schema };
|
|
22
|
-
if (result.
|
|
23
|
-
|
|
24
|
-
result.properties
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
prop.items.required || []
|
|
42
|
-
)
|
|
43
|
-
};
|
|
44
|
-
} else if (wasOptional) {
|
|
45
|
-
if (prop.type && !Array.isArray(prop.type)) {
|
|
46
|
-
properties[propName] = {
|
|
47
|
-
...prop,
|
|
48
|
-
type: [prop.type, "null"]
|
|
49
|
-
};
|
|
50
|
-
} else if (Array.isArray(prop.type) && !prop.type.includes("null")) {
|
|
51
|
-
properties[propName] = {
|
|
52
|
-
...prop,
|
|
53
|
-
type: [...prop.type, "null"]
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
result.properties = properties;
|
|
59
|
-
if (allPropertyNames.length > 0) {
|
|
60
|
-
result.required = allPropertyNames;
|
|
61
|
-
} else {
|
|
62
|
-
delete result.required;
|
|
34
|
+
function normalizeObjectSchemas(schema) {
|
|
35
|
+
const result = schema.type === "object" && !schema.properties ? { ...schema, properties: {} } : { ...schema };
|
|
36
|
+
if (result.properties && typeof result.properties === "object") {
|
|
37
|
+
result.properties = Object.fromEntries(
|
|
38
|
+
Object.entries(result.properties).map(
|
|
39
|
+
([key, value]) => [
|
|
40
|
+
key,
|
|
41
|
+
typeof value === "object" && value !== null && !Array.isArray(value) ? normalizeObjectSchemas(value) : value
|
|
42
|
+
]
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (result.items && typeof result.items === "object" && !Array.isArray(result.items)) {
|
|
47
|
+
result.items = normalizeObjectSchemas(result.items);
|
|
48
|
+
}
|
|
49
|
+
for (const keyword of ["anyOf", "oneOf", "allOf"]) {
|
|
50
|
+
const branch = result[keyword];
|
|
51
|
+
if (Array.isArray(branch)) {
|
|
52
|
+
result[keyword] = branch.map(
|
|
53
|
+
(entry) => typeof entry === "object" && entry !== null ? normalizeObjectSchemas(entry) : entry
|
|
54
|
+
);
|
|
63
55
|
}
|
|
64
|
-
result.additionalProperties = false;
|
|
65
56
|
}
|
|
66
|
-
if (result.
|
|
67
|
-
result.
|
|
68
|
-
result.
|
|
69
|
-
result.items.required || []
|
|
57
|
+
if (result.additionalProperties && typeof result.additionalProperties === "object" && !Array.isArray(result.additionalProperties)) {
|
|
58
|
+
result.additionalProperties = normalizeObjectSchemas(
|
|
59
|
+
result.additionalProperties
|
|
70
60
|
);
|
|
71
61
|
}
|
|
72
62
|
return result;
|
|
73
63
|
}
|
|
64
|
+
function makeGroqStructuredOutputCompatible(schema, originalRequired = []) {
|
|
65
|
+
const normalised = normalizeObjectSchemas(schema);
|
|
66
|
+
const result = makeStructuredOutputCompatible(normalised, originalRequired);
|
|
67
|
+
return removeEmptyRequired(result);
|
|
68
|
+
}
|
|
74
69
|
export {
|
|
75
70
|
makeGroqStructuredOutputCompatible,
|
|
76
71
|
transformNullsToUndefined
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-converter.js","sources":["../../../src/utils/schema-converter.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"schema-converter.js","sources":["../../../src/utils/schema-converter.ts"],"sourcesContent":["import { makeStructuredOutputCompatible } from '@tanstack/openai-base'\nimport { transformNullsToUndefined } from '@tanstack/ai-utils'\n\nexport { transformNullsToUndefined }\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 * 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 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"],"names":[],"mappings":";;AAUA,SAAS,oBAAoB,QAAkD;AAC7E,QAAM,SAAS,EAAE,GAAG,OAAA;AAEpB,MAAI,MAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,SAAS,WAAW,GAAG;AAClE,WAAO,OAAO;AAAA,EAChB;AAEA,MAAI,OAAO,cAAc,OAAO,OAAO,eAAe,UAAU;AAC9D,UAAM,aAAkC,CAAA;AACxC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO;AAAA,MAChC,OAAO;AAAA,IAAA,GACN;AACD,iBAAW,GAAG,IACZ,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAC/D,oBAAoB,KAAK,IACzB;AAAA,IACR;AACA,WAAO,aAAa;AAAA,EACtB;AAEA,MACE,OAAO,SACP,OAAO,OAAO,UAAU,YACxB,CAAC,MAAM,QAAQ,OAAO,KAAK,GAC3B;AACA,WAAO,QAAQ,oBAAoB,OAAO,KAAK;AAAA,EACjD;AAGA,aAAW,WAAW,CAAC,SAAS,SAAS,OAAO,GAAY;AAC1D,QAAI,MAAM,QAAQ,OAAO,OAAO,CAAC,GAAG;AAClC,aAAO,OAAO,IAAI,OAAO,OAAO,EAAE;AAAA,QAAI,CAAC,UACrC,oBAAoB,KAAK;AAAA,MAAA;AAAA,IAE7B;AAAA,EACF;AAGA,MACE,OAAO,wBACP,OAAO,OAAO,yBAAyB,YACvC,CAAC,MAAM,QAAQ,OAAO,oBAAoB,GAC1C;AACA,WAAO,uBAAuB;AAAA,MAC5B,OAAO;AAAA,IAAA;AAAA,EAEX;AAEA,SAAO;AACT;AAUA,SAAS,uBACP,QACqB;AACrB,QAAM,SACJ,OAAO,SAAS,YAAY,CAAC,OAAO,aAChC,EAAE,GAAG,QAAQ,YAAY,CAAA,EAAC,IAC1B,EAAE,GAAG,OAAA;AAEX,MAAI,OAAO,cAAc,OAAO,OAAO,eAAe,UAAU;AAC9D,WAAO,aAAa,OAAO;AAAA,MACzB,OAAO,QAAQ,OAAO,UAAiC,EAAE;AAAA,QACvD,CAAC,CAAC,KAAK,KAAK,MAAM;AAAA,UAChB;AAAA,UACA,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAC/D,uBAAuB,KAAK,IAC5B;AAAA,QAAA;AAAA,MACN;AAAA,IACF;AAAA,EAEJ;AAEA,MACE,OAAO,SACP,OAAO,OAAO,UAAU,YACxB,CAAC,MAAM,QAAQ,OAAO,KAAK,GAC3B;AACA,WAAO,QAAQ,uBAAuB,OAAO,KAAK;AAAA,EACpD;AAEA,aAAW,WAAW,CAAC,SAAS,SAAS,OAAO,GAAY;AAC1D,UAAM,SAAS,OAAO,OAAO;AAC7B,QAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,aAAO,OAAO,IAAI,OAAO;AAAA,QAAI,CAAC,UAC5B,OAAO,UAAU,YAAY,UAAU,OACnC,uBAAuB,KAA4B,IACnD;AAAA,MAAA;AAAA,IAER;AAAA,EACF;AAEA,MACE,OAAO,wBACP,OAAO,OAAO,yBAAyB,YACvC,CAAC,MAAM,QAAQ,OAAO,oBAAoB,GAC1C;AACA,WAAO,uBAAuB;AAAA,MAC5B,OAAO;AAAA,IAAA;AAAA,EAEX;AAEA,SAAO;AACT;AAkBO,SAAS,mCACd,QACA,mBAAkC,IACb;AAGrB,QAAM,aAAa,uBAAuB,MAAM;AAEhD,QAAM,SAAS,+BAA+B,YAAY,gBAAgB;AAG1E,SAAO,oBAAoB,MAAM;AACnC;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-groq",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Groq adapter for TanStack AI",
|
|
6
6
|
"author": "",
|
|
@@ -38,10 +38,12 @@
|
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"zod": "^4.0.0",
|
|
41
|
-
"@tanstack/ai": "^0.
|
|
41
|
+
"@tanstack/ai": "^0.16.0"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"groq-sdk": "^0.37.0"
|
|
44
|
+
"groq-sdk": "^0.37.0",
|
|
45
|
+
"@tanstack/ai-utils": "0.2.0",
|
|
46
|
+
"@tanstack/openai-base": "0.2.1"
|
|
45
47
|
},
|
|
46
48
|
"scripts": {
|
|
47
49
|
"build": "vite build",
|
package/src/utils/client.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { generateId as _generateId, getApiKeyFromEnv } from '@tanstack/ai-utils'
|
|
1
2
|
import Groq_SDK from 'groq-sdk'
|
|
2
3
|
import type { ClientOptions } from 'groq-sdk'
|
|
3
4
|
|
|
@@ -17,26 +18,12 @@ export function createGroqClient(config: GroqClientConfig): Groq_SDK {
|
|
|
17
18
|
* @throws Error if GROQ_API_KEY is not found
|
|
18
19
|
*/
|
|
19
20
|
export function getGroqApiKeyFromEnv(): string {
|
|
20
|
-
|
|
21
|
-
typeof globalThis !== 'undefined' && (globalThis as any).window?.env
|
|
22
|
-
? (globalThis as any).window.env
|
|
23
|
-
: typeof process !== 'undefined'
|
|
24
|
-
? process.env
|
|
25
|
-
: undefined
|
|
26
|
-
const key = env?.GROQ_API_KEY
|
|
27
|
-
|
|
28
|
-
if (!key) {
|
|
29
|
-
throw new Error(
|
|
30
|
-
'GROQ_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key.',
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return key
|
|
21
|
+
return getApiKeyFromEnv('GROQ_API_KEY')
|
|
35
22
|
}
|
|
36
23
|
|
|
37
24
|
/**
|
|
38
25
|
* Generates a unique ID with a prefix
|
|
39
26
|
*/
|
|
40
27
|
export function generateId(prefix: string): string {
|
|
41
|
-
return
|
|
28
|
+
return _generateId(prefix)
|
|
42
29
|
}
|
|
@@ -1,35 +1,123 @@
|
|
|
1
|
+
import { makeStructuredOutputCompatible } from '@tanstack/openai-base'
|
|
2
|
+
import { transformNullsToUndefined } from '@tanstack/ai-utils'
|
|
3
|
+
|
|
4
|
+
export { transformNullsToUndefined }
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
|
-
* Recursively
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* in the `required` array, with optional fields made nullable (type: ["string", "null"]).
|
|
6
|
-
* When Groq returns null for optional fields, we need to convert them back to
|
|
7
|
-
* undefined to match the original Zod schema expectations.
|
|
8
|
-
*
|
|
9
|
-
* @param obj - Object to transform
|
|
10
|
-
* @returns Object with nulls converted to undefined
|
|
7
|
+
* Recursively removes `required: []` from a schema object.
|
|
8
|
+
* Groq rejects `required` when it is an empty array, even though
|
|
9
|
+
* OpenAI-compatible schemas allow it.
|
|
11
10
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
function removeEmptyRequired(schema: Record<string, any>): Record<string, any> {
|
|
12
|
+
const result = { ...schema }
|
|
13
|
+
|
|
14
|
+
if (Array.isArray(result.required) && result.required.length === 0) {
|
|
15
|
+
delete result.required
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
if (
|
|
18
|
-
|
|
18
|
+
if (result.properties && typeof result.properties === 'object') {
|
|
19
|
+
const properties: Record<string, any> = {}
|
|
20
|
+
for (const [key, value] of Object.entries(
|
|
21
|
+
result.properties as Record<string, any>,
|
|
22
|
+
)) {
|
|
23
|
+
properties[key] =
|
|
24
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
25
|
+
? removeEmptyRequired(value)
|
|
26
|
+
: value
|
|
27
|
+
}
|
|
28
|
+
result.properties = properties
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
result.items &&
|
|
33
|
+
typeof result.items === 'object' &&
|
|
34
|
+
!Array.isArray(result.items)
|
|
35
|
+
) {
|
|
36
|
+
result.items = removeEmptyRequired(result.items)
|
|
19
37
|
}
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
39
|
+
// Recurse into combinator arrays (anyOf, oneOf, allOf)
|
|
40
|
+
for (const keyword of ['anyOf', 'oneOf', 'allOf'] as const) {
|
|
41
|
+
if (Array.isArray(result[keyword])) {
|
|
42
|
+
result[keyword] = result[keyword].map((entry: Record<string, any>) =>
|
|
43
|
+
removeEmptyRequired(entry),
|
|
44
|
+
)
|
|
28
45
|
}
|
|
29
|
-
return result as T
|
|
30
46
|
}
|
|
31
47
|
|
|
32
|
-
|
|
48
|
+
// Recurse into additionalProperties if it's a schema object
|
|
49
|
+
if (
|
|
50
|
+
result.additionalProperties &&
|
|
51
|
+
typeof result.additionalProperties === 'object' &&
|
|
52
|
+
!Array.isArray(result.additionalProperties)
|
|
53
|
+
) {
|
|
54
|
+
result.additionalProperties = removeEmptyRequired(
|
|
55
|
+
result.additionalProperties,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return result
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Recursively normalise object schemas so any `{ type: 'object' }` node
|
|
64
|
+
* without `properties` gets an empty `properties: {}` object. The
|
|
65
|
+
* openai-base transformer only descends into objects that already have
|
|
66
|
+
* `properties` set, so a Zod `z.object({})` nested inside `properties`,
|
|
67
|
+
* `items`, `additionalProperties`, or a combinator branch would otherwise
|
|
68
|
+
* skip the strict-mode rewrite and fail Groq validation.
|
|
69
|
+
*/
|
|
70
|
+
function normalizeObjectSchemas(
|
|
71
|
+
schema: Record<string, any>,
|
|
72
|
+
): Record<string, any> {
|
|
73
|
+
const result: Record<string, any> =
|
|
74
|
+
schema.type === 'object' && !schema.properties
|
|
75
|
+
? { ...schema, properties: {} }
|
|
76
|
+
: { ...schema }
|
|
77
|
+
|
|
78
|
+
if (result.properties && typeof result.properties === 'object') {
|
|
79
|
+
result.properties = Object.fromEntries(
|
|
80
|
+
Object.entries(result.properties as Record<string, any>).map(
|
|
81
|
+
([key, value]) => [
|
|
82
|
+
key,
|
|
83
|
+
typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
84
|
+
? normalizeObjectSchemas(value)
|
|
85
|
+
: value,
|
|
86
|
+
],
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
result.items &&
|
|
93
|
+
typeof result.items === 'object' &&
|
|
94
|
+
!Array.isArray(result.items)
|
|
95
|
+
) {
|
|
96
|
+
result.items = normalizeObjectSchemas(result.items)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const keyword of ['anyOf', 'oneOf', 'allOf'] as const) {
|
|
100
|
+
const branch = result[keyword]
|
|
101
|
+
if (Array.isArray(branch)) {
|
|
102
|
+
result[keyword] = branch.map((entry) =>
|
|
103
|
+
typeof entry === 'object' && entry !== null
|
|
104
|
+
? normalizeObjectSchemas(entry as Record<string, any>)
|
|
105
|
+
: entry,
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
result.additionalProperties &&
|
|
112
|
+
typeof result.additionalProperties === 'object' &&
|
|
113
|
+
!Array.isArray(result.additionalProperties)
|
|
114
|
+
) {
|
|
115
|
+
result.additionalProperties = normalizeObjectSchemas(
|
|
116
|
+
result.additionalProperties as Record<string, any>,
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result
|
|
33
121
|
}
|
|
34
122
|
|
|
35
123
|
/**
|
|
@@ -39,6 +127,10 @@ export function transformNullsToUndefined<T>(obj: T): T {
|
|
|
39
127
|
* - All properties must be in the `required` array
|
|
40
128
|
* - Optional fields should have null added to their type union
|
|
41
129
|
* - additionalProperties must be false for objects
|
|
130
|
+
* - `required` must be omitted (not empty array) when there are no properties
|
|
131
|
+
*
|
|
132
|
+
* Delegates to the shared OpenAI-compatible transformer and applies the
|
|
133
|
+
* Groq-specific quirk of removing empty `required` arrays.
|
|
42
134
|
*
|
|
43
135
|
* @param schema - JSON schema to transform
|
|
44
136
|
* @param originalRequired - Original required array (to know which fields were optional)
|
|
@@ -48,63 +140,12 @@ export function makeGroqStructuredOutputCompatible(
|
|
|
48
140
|
schema: Record<string, any>,
|
|
49
141
|
originalRequired: Array<string> = [],
|
|
50
142
|
): Record<string, any> {
|
|
51
|
-
|
|
143
|
+
// Recursively patch every `{ type: 'object' }` node so the openai-base
|
|
144
|
+
// transformer descends into nested empty objects too.
|
|
145
|
+
const normalised = normalizeObjectSchemas(schema)
|
|
52
146
|
|
|
53
|
-
|
|
54
|
-
if (!result.properties) {
|
|
55
|
-
result.properties = {}
|
|
56
|
-
}
|
|
57
|
-
const properties = { ...result.properties }
|
|
58
|
-
const allPropertyNames = Object.keys(properties)
|
|
59
|
-
|
|
60
|
-
for (const propName of allPropertyNames) {
|
|
61
|
-
const prop = properties[propName]
|
|
62
|
-
const wasOptional = !originalRequired.includes(propName)
|
|
63
|
-
|
|
64
|
-
if (prop.type === 'object' && prop.properties) {
|
|
65
|
-
properties[propName] = makeGroqStructuredOutputCompatible(
|
|
66
|
-
prop,
|
|
67
|
-
prop.required || [],
|
|
68
|
-
)
|
|
69
|
-
} else if (prop.type === 'array' && prop.items) {
|
|
70
|
-
properties[propName] = {
|
|
71
|
-
...prop,
|
|
72
|
-
items: makeGroqStructuredOutputCompatible(
|
|
73
|
-
prop.items,
|
|
74
|
-
prop.items.required || [],
|
|
75
|
-
),
|
|
76
|
-
}
|
|
77
|
-
} else if (wasOptional) {
|
|
78
|
-
if (prop.type && !Array.isArray(prop.type)) {
|
|
79
|
-
properties[propName] = {
|
|
80
|
-
...prop,
|
|
81
|
-
type: [prop.type, 'null'],
|
|
82
|
-
}
|
|
83
|
-
} else if (Array.isArray(prop.type) && !prop.type.includes('null')) {
|
|
84
|
-
properties[propName] = {
|
|
85
|
-
...prop,
|
|
86
|
-
type: [...prop.type, 'null'],
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
147
|
+
const result = makeStructuredOutputCompatible(normalised, originalRequired)
|
|
91
148
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (allPropertyNames.length > 0) {
|
|
95
|
-
result.required = allPropertyNames
|
|
96
|
-
} else {
|
|
97
|
-
delete result.required
|
|
98
|
-
}
|
|
99
|
-
result.additionalProperties = false
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (result.type === 'array' && result.items) {
|
|
103
|
-
result.items = makeGroqStructuredOutputCompatible(
|
|
104
|
-
result.items,
|
|
105
|
-
result.items.required || [],
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return result
|
|
149
|
+
// Groq rejects `required` when it is an empty array
|
|
150
|
+
return removeEmptyRequired(result)
|
|
110
151
|
}
|