feat(ai-groq): Add Groq adapter package#332
Conversation
Adds a new `@tanstack/ai-groq` package for Groq AI model integration. This package provides a tree-shakeable text adapter, supporting streaming chat completions, structured output, and tool usage.
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughAdds a new TypeScript package Changes
Sequence DiagramsequenceDiagram
participant Client
participant Adapter as Groq Text Adapter
participant SDK as Groq SDK Client
participant API as Groq API
Client->>Adapter: chatStream(options)
Adapter->>Adapter: mapTextOptionsToGroq(), convertMessageToGroq()
Adapter->>SDK: chat.completions.create(request)
SDK->>API: Stream Request
loop stream chunks
API-->>SDK: chunk (content / tool_call / finish)
SDK-->>Adapter: chunk
Adapter->>Adapter: processGroqStreamChunks()
Adapter->>Client: emit StreamChunk (TEXT_MESSAGE_CONTENT / TOOL_* / etc.)
end
API-->>SDK: [DONE]
SDK-->>Adapter: final response
Adapter->>Client: emit RUN_FINISHED
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (4)
packages/typescript/ai-groq/src/text/text-provider-options.ts (1)
221-225:validateTextProviderOptionsis currently a no-op despite documented invariants.Add minimal preflight checks for constraints already documented in this file (e.g.,
include_reasoningvsreasoning_format,top_logprobsrequiringlogprobs, andn === 1) so invalid configs fail fast.💡 Suggested validation baseline
export function validateTextProviderOptions( - _options: InternalTextProviderOptions, + options: InternalTextProviderOptions, ): void { - // Groq API handles detailed validation + if ( + options.include_reasoning !== undefined && + options.include_reasoning !== null && + options.reasoning_format !== undefined && + options.reasoning_format !== null + ) { + throw new Error( + "Cannot set both 'include_reasoning' and 'reasoning_format' in the same request.", + ) + } + + if ( + options.top_logprobs !== undefined && + options.top_logprobs !== null && + options.logprobs !== true + ) { + throw new Error("'top_logprobs' requires 'logprobs: true'.") + } + + if (options.n !== undefined && options.n !== null && options.n !== 1) { + throw new Error("Groq currently supports only 'n = 1'.") + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai-groq/src/text/text-provider-options.ts` around lines 221 - 225, The validateTextProviderOptions function is currently a no-op; implement fast-fail checks on InternalTextProviderOptions: ensure if options.include_reasoning is true then options.reasoning_format is set/non-empty; ensure if options.top_logprobs is provided (>0) then options.logprobs is also provided/true; ensure options.n (if provided) equals 1 (reject values !== 1); throw a clear Error with descriptive message identifying the offending field (use the function validateTextProviderOptions to locate the change) so invalid configs fail fast.packages/typescript/ai-groq/src/model-meta.ts (2)
37-305: Model constant identifiers don’t follow the repository camelCase naming rule.Please rename these
constvariables to camelCase for consistency with the codebase convention.As per coding guidelines "
**/*.{ts,tsx,js,jsx}: Use camelCase for function and variable names throughout the codebase".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai-groq/src/model-meta.ts` around lines 37 - 305, The model constant identifiers use SCREAMING_SNAKE_CASE but must follow the repo camelCase convention; rename each constant (e.g., LLAMA_3_3_70B_VERSATILE → llama3_3_70bVersatile, LLAMA_4_MAVERICK_17B_128E_INSTRUCT → llama4Maverick17b128eInstruct, LLAMA_4_SCOUT_17B_16E_INSTRUCT → llama4Scout17b16eInstruct, LLAMA_GUARD_4_12B → llamaGuard4_12b, LLAMA_PROMPT_GUARD_2_86M → llamaPromptGuard2_86m, LLAMA_3_1_8B_INSTANT → llama3_1_8bInstant, LLAMA_PROMPT_GUARD_2_22M → llamaPromptGuard2_22m, GPT_OSS_120B → gptOss120b, GPT_OSS_SAFEGUARD_20B → gptOssSafeguard20b, GPT_OSS_20B → gptOss20b, KIMI_K2_INSTRUCT_0905 → kimiK2Instruct0905, QWEN3_32B → qwen3_32b) keeping the object contents and "as const satisfies ModelMeta<GroqTextProviderOptions>" intact; then update all references/imports/usages across the codebase to the new camelCase names (including any exports or registration maps) to avoid breaking references and run typechecks to ensure no missed usages.
350-362:ResolveProviderOptionscurrently doesn’t provide real per-model option narrowing.Since every key maps to
GroqTextProviderOptions, model selection does not change allowed option types at compile time. Consider mapping known models to model-specific option subsets and keeping a generic fallback for unknown models.Based on learnings: "use mapped types over the known ... keys with a Record fallback ... to preserve per-model type safety."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai-groq/src/model-meta.ts` around lines 350 - 362, Change the provider-options mapping to preserve per-model narrowing by creating a merged options map (e.g. name it ProviderOptionsMap) that is a Record<string, GroqTextProviderOptions> fallback intersected with a mapped type over (typeof GROQ_CHAT_MODELS)[number] that assigns model-specific option types for known models; update GroqChatModelProviderOptionsByName (or replace it) with that ProviderOptionsMap and change ResolveProviderOptions<TModel extends string> to use ProviderOptionsMap (TModel extends keyof ProviderOptionsMap ? ProviderOptionsMap[TModel] : GroqTextProviderOptions). Edit the types referenced (ResolveProviderOptions, GroqChatModelProviderOptionsByName, GROQ_CHAT_MODELS, GroqTextProviderOptions) so known models get specific option subsets while unknown models fall back to the generic GroqTextProviderOptions.packages/typescript/ai-groq/src/adapters/text.ts (1)
119-123: Avoid rawconsoleerror dumps from shared adapter runtime.Logging full error objects/stacks here can leak provider metadata and pollute consumer logs; prefer sanitized structured errors (or debug-gated logging only).
Also applies to: 182-183, 376-377
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai-groq/src/adapters/text.ts` around lines 119 - 123, Replace raw console.error dumps in the chatStream error handling with sanitized, structured logging: in the block that prints ">>> chatStream: Fatal error during response creation <<<" (and the similar spots around the sections noted at 182-183 and 376-377), call the module's logger (e.g., processLogger or the existing adapter logger) and log a short message plus only non-sensitive fields such as err.message and a sanitized error code/identifier, omitting full err.stack and the raw error object; alternatively gate detailed stack logging behind a debug flag so stacks are not emitted in normal/shared runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/typescript/ai-groq/package.json`:
- Around line 15-20: Update the package.json "exports" map to add a new
"/adapters/*" subpath so consumers can import tree-shakeable adapters;
specifically extend the existing "exports" object (which currently only has ".")
to include an "/adapters/*" entry that points to the built adapter files (e.g.
"./dist/esm/adapters/*.js" with corresponding types
"./dist/esm/adapters/*.d.ts"), ensuring each adapter file name matches what
exists in the dist (text, embedding, summarize, image) so imports like
"ai-groq/adapters/text" resolve correctly.
In `@packages/typescript/ai-groq/README.md`:
- Around line 47-50: The README example includes a hardcoded API key string when
calling createGroqText; replace that literal with an env-backed placeholder and
update the example to read the key from an environment variable (e.g.,
GROQ_API_KEY) before passing it to createGroqText so users are shown a secure
pattern rather than a secret-like literal. Ensure the updated text clearly shows
the key is loaded from the environment and not hardcoded.
In `@packages/typescript/ai-groq/src/adapters/text.ts`:
- Around line 446-454: The tool message branch currently defaults tool_call_id
to an empty string which hides upstream issues; in the branch that handles
message.role === 'tool' (the returned object containing tool_call_id and
content), validate that message.toolCallId is present and non-empty and do not
emit the tool message if it's missing — either throw a clear Error mentioning
the missing tool_call_id or skip/omit emitting the tool message so you never set
tool_call_id to ''. Update the logic around message.toolCallId (used to populate
tool_call_id) to enforce this check and surface failures instead of silently
defaulting to an empty string.
- Around line 339-346: The mapping for computedFinishReason currently coerces
any non-tool_calls/length finish reasons to 'stop', losing valid values like
'content_filter' and null; update the logic around computedFinishReason
(referencing choice.finish_reason and toolCallsInProgress) so it returns
'tool_calls' when choice.finish_reason === 'tool_calls' ||
toolCallsInProgress.size > 0, 'length' when choice.finish_reason === 'length',
and otherwise preserves choice.finish_reason (including 'content_filter' or
null) so RUN_FINISHED emits the original semantics.
In `@packages/typescript/ai-groq/src/message-types.ts`:
- Around line 313-347: Replace the empty interfaces GroqDocumentMetadata,
GroqTextMetadata, GroqAudioMetadata, and GroqVideoMetadata with explicit type
aliases using Record<string, never> to satisfy the noEmptyInterface rule; locate
the declarations of GroqDocumentMetadata, GroqTextMetadata, GroqAudioMetadata,
and GroqVideoMetadata in message-types.ts and change each empty "interface X {
}" to "type X = Record<string, never>;" so the types remain effectively empty
but comply with Biome's linter.
- Around line 68-73: ChatCompletionNamedToolChoice currently uses a capitalized
Function key and lacks the required discriminator; update the interface so it
matches Groq's required shape: include a literal discriminator property type:
'function' and rename the nested key to lowercase function with { name: string }
(i.e. the object should model {"type":"function","function":{"name":"..."}}),
keeping the symbol ChatCompletionNamedToolChoice as the place to edit.
In `@packages/typescript/ai-groq/src/tools/index.ts`:
- Around line 1-5: The tools module (exports:
convertFunctionToolToAdapterFormat, type FunctionTool,
convertToolsToProviderFormat from the tools index) isn't reachable because
there's no "./tools" subpath export in package.json and the package root
index.ts doesn't re-export it; add a "./tools" entry under the package.json
"exports" map pointing to the compiled tools index, and update the root index.ts
to re-export the tools (e.g., export * from './tools') so consumers can import
`@tanstack/ai-groq/tools` and access convertFunctionToolToAdapterFormat,
FunctionTool, and convertToolsToProviderFormat.
In `@packages/typescript/ai-groq/src/utils/client.ts`:
- Around line 20-26: The current env selection in client.ts binds env to
window.env if that object exists even when it lacks GROQ_API_KEY, preventing
fallback to process.env and causing false "missing key" errors; change the logic
so you first look for GROQ_API_KEY on (globalThis as any).window?.env and use
that value if present, otherwise fall back to process.env.GROQ_API_KEY (or
process.env as the env source) — update the env/key resolution around the env
and key variables so key is derived from window.env.GROQ_API_KEY when defined,
else from process.env.GROQ_API_KEY.
In `@packages/typescript/ai-groq/src/utils/schema-converter.ts`:
- Around line 66-72: The array-handling branch in
makeGroqStructuredOutputCompatible assumes prop.items is a single object and
passing an array (tuple-style JSON Schema) into the object transformation
produces invalid output; update the branches that set properties[propName].items
(and the similar block at the later occurrence) to detect
Array.isArray(prop.items) and, when true, map over prop.items calling
makeGroqStructuredOutputCompatible(item, item.required || []) for each tuple
entry, otherwise keep the existing single-object call for non-tuple schemas
(i.e., preserve the current call makeGroqStructuredOutputCompatible(prop.items,
prop.items.required || []) when items is not an array).
- Around line 57-91: The recursion for object/array props in
makeGroqStructuredOutputCompatible runs before applying optional nullability and
then result.required is incorrectly set to allPropertyNames, which makes
optional fields required; fix by 1) after the existing recursion for prop.type
=== 'object' and prop.type === 'array', apply the optional-nullability patch
using the wasOptional check (i.e., add 'null' into prop.type or prop.type array)
so that object/array props also receive 'null' when optional, and 2) set
result.required = originalRequired (not allPropertyNames) so only originally
required properties remain required; update references in the function to modify
properties[propName] after recursion and use originalRequired for
result.required.
---
Nitpick comments:
In `@packages/typescript/ai-groq/src/adapters/text.ts`:
- Around line 119-123: Replace raw console.error dumps in the chatStream error
handling with sanitized, structured logging: in the block that prints ">>>
chatStream: Fatal error during response creation <<<" (and the similar spots
around the sections noted at 182-183 and 376-377), call the module's logger
(e.g., processLogger or the existing adapter logger) and log a short message
plus only non-sensitive fields such as err.message and a sanitized error
code/identifier, omitting full err.stack and the raw error object; alternatively
gate detailed stack logging behind a debug flag so stacks are not emitted in
normal/shared runtime.
In `@packages/typescript/ai-groq/src/model-meta.ts`:
- Around line 37-305: The model constant identifiers use SCREAMING_SNAKE_CASE
but must follow the repo camelCase convention; rename each constant (e.g.,
LLAMA_3_3_70B_VERSATILE → llama3_3_70bVersatile,
LLAMA_4_MAVERICK_17B_128E_INSTRUCT → llama4Maverick17b128eInstruct,
LLAMA_4_SCOUT_17B_16E_INSTRUCT → llama4Scout17b16eInstruct, LLAMA_GUARD_4_12B →
llamaGuard4_12b, LLAMA_PROMPT_GUARD_2_86M → llamaPromptGuard2_86m,
LLAMA_3_1_8B_INSTANT → llama3_1_8bInstant, LLAMA_PROMPT_GUARD_2_22M →
llamaPromptGuard2_22m, GPT_OSS_120B → gptOss120b, GPT_OSS_SAFEGUARD_20B →
gptOssSafeguard20b, GPT_OSS_20B → gptOss20b, KIMI_K2_INSTRUCT_0905 →
kimiK2Instruct0905, QWEN3_32B → qwen3_32b) keeping the object contents and "as
const satisfies ModelMeta<GroqTextProviderOptions>" intact; then update all
references/imports/usages across the codebase to the new camelCase names
(including any exports or registration maps) to avoid breaking references and
run typechecks to ensure no missed usages.
- Around line 350-362: Change the provider-options mapping to preserve per-model
narrowing by creating a merged options map (e.g. name it ProviderOptionsMap)
that is a Record<string, GroqTextProviderOptions> fallback intersected with a
mapped type over (typeof GROQ_CHAT_MODELS)[number] that assigns model-specific
option types for known models; update GroqChatModelProviderOptionsByName (or
replace it) with that ProviderOptionsMap and change
ResolveProviderOptions<TModel extends string> to use ProviderOptionsMap (TModel
extends keyof ProviderOptionsMap ? ProviderOptionsMap[TModel] :
GroqTextProviderOptions). Edit the types referenced (ResolveProviderOptions,
GroqChatModelProviderOptionsByName, GROQ_CHAT_MODELS, GroqTextProviderOptions)
so known models get specific option subsets while unknown models fall back to
the generic GroqTextProviderOptions.
In `@packages/typescript/ai-groq/src/text/text-provider-options.ts`:
- Around line 221-225: The validateTextProviderOptions function is currently a
no-op; implement fast-fail checks on InternalTextProviderOptions: ensure if
options.include_reasoning is true then options.reasoning_format is
set/non-empty; ensure if options.top_logprobs is provided (>0) then
options.logprobs is also provided/true; ensure options.n (if provided) equals 1
(reject values !== 1); throw a clear Error with descriptive message identifying
the offending field (use the function validateTextProviderOptions to locate the
change) so invalid configs fail fast.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a538dc68-4883-4090-842f-741d7ddeb3ee
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (18)
.changeset/free-impalas-doubt.mdpackages/typescript/ai-groq/README.mdpackages/typescript/ai-groq/package.jsonpackages/typescript/ai-groq/src/adapters/text.tspackages/typescript/ai-groq/src/index.tspackages/typescript/ai-groq/src/message-types.tspackages/typescript/ai-groq/src/model-meta.tspackages/typescript/ai-groq/src/text/text-provider-options.tspackages/typescript/ai-groq/src/tools/function-tool.tspackages/typescript/ai-groq/src/tools/index.tspackages/typescript/ai-groq/src/tools/tool-converter.tspackages/typescript/ai-groq/src/utils/client.tspackages/typescript/ai-groq/src/utils/index.tspackages/typescript/ai-groq/src/utils/schema-converter.tspackages/typescript/ai-groq/tests/groq-adapter.test.tspackages/typescript/ai-groq/tsconfig.jsonpackages/typescript/ai-groq/vite.config.tspackages/typescript/ai-groq/vitest.config.ts
| "exports": { | ||
| ".": { | ||
| "types": "./dist/esm/index.d.ts", | ||
| "import": "./dist/esm/index.js" | ||
| } | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add /adapters subpath exports to match tree-shakeable architecture.
Line 15 currently exports only ".", which prevents specialized adapter imports via /adapters/* at the package boundary.
Proposed export map adjustment
"exports": {
".": {
"types": "./dist/esm/index.d.ts",
"import": "./dist/esm/index.js"
+ },
+ "./adapters/text": {
+ "types": "./dist/esm/adapters/text.d.ts",
+ "import": "./dist/esm/adapters/text.js"
}
},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/typescript/ai-groq/package.json` around lines 15 - 20, Update the
package.json "exports" map to add a new "/adapters/*" subpath so consumers can
import tree-shakeable adapters; specifically extend the existing "exports"
object (which currently only has ".") to include an "/adapters/*" entry that
points to the built adapter files (e.g. "./dist/esm/adapters/*.js" with
corresponding types "./dist/esm/adapters/*.d.ts"), ensuring each adapter file
name matches what exists in the dist (text, embedding, summarize, image) so
imports like "ai-groq/adapters/text" resolve correctly.
| import { createGroqText } from '@tanstack/ai-groq' | ||
|
|
||
| const adapter = createGroqText('llama-3.3-70b-versatile', 'gsk_api_key') | ||
| ``` |
There was a problem hiding this comment.
Avoid secret-like literal API key examples.
Line 49 shows a hardcoded key-looking string. This pattern is often copy-pasted into source code; prefer env-backed examples to reduce accidental secret exposure.
Suggested doc tweak
import { createGroqText } from '@tanstack/ai-groq'
-const adapter = createGroqText('llama-3.3-70b-versatile', 'gsk_api_key')
+const adapter = createGroqText('llama-3.3-70b-versatile', process.env.GROQ_API_KEY!)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { createGroqText } from '@tanstack/ai-groq' | |
| const adapter = createGroqText('llama-3.3-70b-versatile', 'gsk_api_key') | |
| ``` | |
| import { createGroqText } from '@tanstack/ai-groq' | |
| const adapter = createGroqText('llama-3.3-70b-versatile', process.env.GROQ_API_KEY!) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/typescript/ai-groq/README.md` around lines 47 - 50, The README
example includes a hardcoded API key string when calling createGroqText; replace
that literal with an env-backed placeholder and update the example to read the
key from an environment variable (e.g., GROQ_API_KEY) before passing it to
createGroqText so users are shown a secure pattern rather than a secret-like
literal. Ensure the updated text clearly shows the key is loaded from the
environment and not hardcoded.
| if (message.role === 'tool') { | ||
| return { | ||
| role: 'tool', | ||
| tool_call_id: message.toolCallId || '', | ||
| content: | ||
| typeof message.content === 'string' | ||
| ? message.content | ||
| : JSON.stringify(message.content), | ||
| } |
There was a problem hiding this comment.
Do not emit tool messages with an empty tool_call_id.
Defaulting tool_call_id to '' hides upstream data issues and can break tool-response correlation.
Suggested fix
if (message.role === 'tool') {
+ if (!message.toolCallId) {
+ throw new Error('Tool message requires a non-empty toolCallId')
+ }
return {
role: 'tool',
- tool_call_id: message.toolCallId || '',
+ tool_call_id: message.toolCallId,
content:
typeof message.content === 'string'
? message.content
: JSON.stringify(message.content),
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (message.role === 'tool') { | |
| return { | |
| role: 'tool', | |
| tool_call_id: message.toolCallId || '', | |
| content: | |
| typeof message.content === 'string' | |
| ? message.content | |
| : JSON.stringify(message.content), | |
| } | |
| if (message.role === 'tool') { | |
| if (!message.toolCallId) { | |
| throw new Error('Tool message requires a non-empty toolCallId') | |
| } | |
| return { | |
| role: 'tool', | |
| tool_call_id: message.toolCallId, | |
| content: | |
| typeof message.content === 'string' | |
| ? message.content | |
| : JSON.stringify(message.content), | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/typescript/ai-groq/src/adapters/text.ts` around lines 446 - 454, The
tool message branch currently defaults tool_call_id to an empty string which
hides upstream issues; in the branch that handles message.role === 'tool' (the
returned object containing tool_call_id and content), validate that
message.toolCallId is present and non-empty and do not emit the tool message if
it's missing — either throw a clear Error mentioning the missing tool_call_id or
skip/omit emitting the tool message so you never set tool_call_id to ''. Update
the logic around message.toolCallId (used to populate tool_call_id) to enforce
this check and surface failures instead of silently defaulting to an empty
string.
| export interface ChatCompletionNamedToolChoice { | ||
| Function: { | ||
| /** The name of the function to call. */ | ||
| name: string | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd packages/typescript/ai-groq && head -n 350 src/message-types.ts | tail -n +60Repository: TanStack/ai
Length of output: 8253
🌐 Web query:
Groq Chat Completions API tool_choice parameter JSON schema specification
💡 Result:
Groq’s Chat Completions request body defines tool_choice as string | object | null. It controls whether the model calls tools, and can be one of:
"none","auto", or"required"(string forms)- an object forcing a specific tool:
{"type":"function","function":{"name":"my_function"}}
(console.groq.com)
A concise JSON Schema you can use to validate the tool_choice parameter:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Groq Chat Completions tool_choice",
"description": "Controls which (if any) tool is called by the model.",
"oneOf": [
{ "type": "null" },
{
"type": "string",
"enum": ["none", "auto", "required"]
},
{
"type": "object",
"additionalProperties": false,
"required": ["type", "function"],
"properties": {
"type": { "const": "function" },
"function": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {
"name": { "type": "string", "minLength": 1 }
}
}
}
}
]
}Note: Groq also documents the older, deprecated function_call parameter (superseded by tool_choice), which used "none", "auto", or an object like {"name":"my_function"}. (console.groq.com)
Citations:
🏁 Script executed:
cd packages/typescript/ai-groq && sed -n '105,120p' src/message-types.tsRepository: TanStack/ai
Length of output: 617
ChatCompletionNamedToolChoice violates Groq API specification for forced tool selection.
The type uses capitalized Function instead of lowercase function and omits the required type: 'function' discriminator. Groq's Chat Completions API specifies tool forcing via {"type":"function","function":{"name":"..."}}. The current implementation will generate invalid payloads.
Required fix
export interface ChatCompletionNamedToolChoice {
- Function: {
+ type: 'function'
+ function: {
/** The name of the function to call. */
name: string
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/typescript/ai-groq/src/message-types.ts` around lines 68 - 73,
ChatCompletionNamedToolChoice currently uses a capitalized Function key and
lacks the required discriminator; update the interface so it matches Groq's
required shape: include a literal discriminator property type: 'function' and
rename the nested key to lowercase function with { name: string } (i.e. the
object should model {"type":"function","function":{"name":"..."}}), keeping the
symbol ChatCompletionNamedToolChoice as the place to edit.
| export interface GroqDocumentMetadata { } | ||
|
|
||
| /** | ||
| * Metadata for Groq text content parts. | ||
| * Currently no specific metadata options for text in Groq. | ||
| */ | ||
| export interface GroqTextMetadata { } | ||
|
|
||
| /** | ||
| * Metadata for Groq image content parts. | ||
| * Controls how the model processes and analyzes images. | ||
| */ | ||
| export interface GroqImageMetadata { | ||
| /** | ||
| * Specifies the detail level of the image. | ||
| * - 'auto': Let the model decide based on image size and content | ||
| * - 'low': Use low resolution processing (faster, cheaper, less detail) | ||
| * - 'high': Use high resolution processing (slower, more expensive, more detail) | ||
| * | ||
| * @default 'auto' | ||
| */ | ||
| detail?: 'auto' | 'low' | 'high' | ||
| } | ||
|
|
||
| /** | ||
| * Metadata for Groq audio content parts. | ||
| * Note: Audio support in Groq is limited; check current API capabilities. | ||
| */ | ||
| export interface GroqAudioMetadata { } | ||
|
|
||
| /** | ||
| * Metadata for Groq video content parts. | ||
| * Note: Groq does not currently support video input. | ||
| */ | ||
| export interface GroqVideoMetadata { } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if the file exists and examine the relevant lines
if [ -f "packages/typescript/ai-groq/src/message-types.ts" ]; then
echo "=== File exists, checking lines 313-347 ==="
sed -n '313,347p' packages/typescript/ai-groq/src/message-types.ts
else
echo "File not found at packages/typescript/ai-groq/src/message-types.ts"
fi
# Search for all empty interface declarations in the file
echo ""
echo "=== All empty metadata interfaces ==="
rg -n 'export interface Groq\w*Metadata\s*\{\s*\}' packages/typescript/ai-groq/src/message-types.ts
# Check for Biome configuration files
echo ""
echo "=== Looking for Biome configuration ==="
fd -t f '(biome\.json|\.biomrc\.json|biome\.jsonc)' .Repository: TanStack/ai
Length of output: 1357
🏁 Script executed:
#!/bin/bash
# Check the Biome configuration for noEmptyInterface rule
echo "=== Biome configuration (if found) ==="
for config_file in biome.json biome.jsonc .biomrc.json; do
if [ -f "$config_file" ]; then
echo "Found: $config_file"
cat "$config_file" | head -100
fi
done
# Also check package.json for biome configuration
if [ -f "package.json" ]; then
echo ""
echo "=== Checking package.json for biome config ==="
cat package.json | jq '.biome // empty' 2>/dev/null || echo "No biome config in package.json"
fiRepository: TanStack/ai
Length of output: 1933
🏁 Script executed:
#!/bin/bash
# Verify the exact interfaces mentioned and check for any other empty interfaces in the file
echo "=== Full content of message-types.ts to understand context ==="
wc -l packages/typescript/ai-groq/src/message-types.ts
echo ""
echo "=== Lines 300-350 for context ==="
sed -n '300,350p' packages/typescript/ai-groq/src/message-types.tsRepository: TanStack/ai
Length of output: 1622
🏁 Script executed:
#!/bin/bash
# Check usages of these empty metadata interfaces
echo "=== Searching for usages of empty metadata interfaces ==="
rg -n 'GroqDocumentMetadata|GroqTextMetadata|GroqAudioMetadata|GroqVideoMetadata' packages/typescript/ai-groq/src/ --type ts --type tsx
# Also check the full packages directory for cross-package usages
echo ""
echo "=== Checking for usages in entire packages directory ==="
rg -c 'GroqDocumentMetadata|GroqTextMetadata|GroqAudioMetadata|GroqVideoMetadata' packages/ --type ts --type tsx | head -20Repository: TanStack/ai
Length of output: 243
🏁 Script executed:
#!/bin/bash
# Verify that Record<string, never> is correct and check TypeScript/Biome best practices
echo "=== Checking if Record<string, never> pattern is used elsewhere in codebase ==="
rg -n 'Record<string, never>' packages/ --type ts --type tsx | head -10
# Also check for similar empty type patterns
echo ""
echo "=== Checking for other empty object type patterns ==="
rg -n 'type.*= Record<string, never>|type.*= \{\}' packages/typescript/ --type ts --type tsx | head -15Repository: TanStack/ai
Length of output: 262
🏁 Script executed:
#!/bin/bash
# Look at imports and exports around the metadata interfaces to understand their usage patterns
echo "=== Context around the metadata interface exports (lines 310-355) ==="
sed -n '310,355p' packages/typescript/ai-groq/src/message-types.tsRepository: TanStack/ai
Length of output: 1409
Replace empty metadata interfaces with Record<string, never> to satisfy Biome's noEmptyInterface rule.
Biome's linter has the noEmptyInterface rule enabled (via recommended preset in biome.json). The following empty interfaces should be replaced with explicit type aliases:
Suggested fix
-export interface GroqDocumentMetadata { }
+export type GroqDocumentMetadata = Record<string, never>
@@
-export interface GroqTextMetadata { }
+export type GroqTextMetadata = Record<string, never>
@@
-export interface GroqAudioMetadata { }
+export type GroqAudioMetadata = Record<string, never>
@@
-export interface GroqVideoMetadata { }
+export type GroqVideoMetadata = Record<string, never>🧰 Tools
🪛 Biome (2.4.4)
[error] 313-313: An empty interface is equivalent to {}.
(lint/suspicious/noEmptyInterface)
[error] 319-319: An empty interface is equivalent to {}.
(lint/suspicious/noEmptyInterface)
[error] 341-341: An empty interface is equivalent to {}.
(lint/suspicious/noEmptyInterface)
[error] 347-347: An empty interface is equivalent to {}.
(lint/suspicious/noEmptyInterface)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/typescript/ai-groq/src/message-types.ts` around lines 313 - 347,
Replace the empty interfaces GroqDocumentMetadata, GroqTextMetadata,
GroqAudioMetadata, and GroqVideoMetadata with explicit type aliases using
Record<string, never> to satisfy the noEmptyInterface rule; locate the
declarations of GroqDocumentMetadata, GroqTextMetadata, GroqAudioMetadata, and
GroqVideoMetadata in message-types.ts and change each empty "interface X { }" to
"type X = Record<string, never>;" so the types remain effectively empty but
comply with Biome's linter.
| export { | ||
| convertFunctionToolToAdapterFormat, | ||
| type FunctionTool, | ||
| } from './function-tool' | ||
| export { convertToolsToProviderFormat } from './tool-converter' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check the exports configuration in package.json
echo "=== Package.json exports for ai-groq ==="
jq '.exports' packages/typescript/ai-groq/package.json
# Also check if ./tools is specifically exported
echo ""
echo "=== Checking for tools export ==="
jq '.exports | has("./tools") or (.exports | to_entries[] | select(.value | type == "string" or (.import or .require)) | .key)' packages/typescript/ai-groq/package.json 2>/dev/null || echo "Checking exports structure..."
# Get full exports structure for clarity
echo ""
echo "=== Full exports structure ==="
jq -r '.exports | to_entries[] | "\(.key): \(.value)"' packages/typescript/ai-groq/package.jsonRepository: TanStack/ai
Length of output: 351
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check if root index.ts re-exports the tools
echo "=== Root index.ts content ==="
cat -n packages/typescript/ai-groq/src/index.ts | head -50
# Also check the build output to confirm what's available
echo ""
echo "=== Built tools index structure ==="
find packages/typescript/ai-groq/dist -name "*tools*" -o -name "*function-tool*" -o -name "*tool-converter*" 2>/dev/null | head -20Repository: TanStack/ai
Length of output: 1112
Add ./tools subpath export to package.json and update root index.ts to re-export tools utilities.
The tools are exported from packages/typescript/ai-groq/src/tools/index.ts but remain unreachable to consumers—there is no ./tools entry in the package.json exports, and the root index.ts does not re-export them. This violates the coding guideline to export tree-shakeable adapters with clear subpath exports in package.json. Add the export entry and update root exports accordingly to make tools accessible as @tanstack/ai-groq/tools.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/typescript/ai-groq/src/tools/index.ts` around lines 1 - 5, The tools
module (exports: convertFunctionToolToAdapterFormat, type FunctionTool,
convertToolsToProviderFormat from the tools index) isn't reachable because
there's no "./tools" subpath export in package.json and the package root
index.ts doesn't re-export it; add a "./tools" entry under the package.json
"exports" map pointing to the compiled tools index, and update the root index.ts
to re-export the tools (e.g., export * from './tools') so consumers can import
`@tanstack/ai-groq/tools` and access convertFunctionToolToAdapterFormat,
FunctionTool, and convertToolsToProviderFormat.
| const env = | ||
| typeof globalThis !== 'undefined' && (globalThis as any).window?.env | ||
| ? (globalThis as any).window.env | ||
| : typeof process !== 'undefined' | ||
| ? process.env | ||
| : undefined | ||
| const key = env?.GROQ_API_KEY |
There was a problem hiding this comment.
Fix API key source fallback logic in hybrid runtimes.
Line 21 chooses window.env as a whole object; if it exists without GROQ_API_KEY, Line 26 never falls back to process.env, causing false “missing key” errors.
Proposed fix
export function getGroqApiKeyFromEnv(): string {
- const env =
- typeof globalThis !== 'undefined' && (globalThis as any).window?.env
- ? (globalThis as any).window.env
- : typeof process !== 'undefined'
- ? process.env
- : undefined
- const key = env?.GROQ_API_KEY
+ const browserKey =
+ typeof globalThis !== 'undefined'
+ ? (globalThis as { window?: { env?: Record<string, string | undefined> } }).window?.env
+ ?.GROQ_API_KEY
+ : undefined
+ const nodeKey = typeof process !== 'undefined' ? process.env?.GROQ_API_KEY : undefined
+ const key = browserKey ?? nodeKey🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/typescript/ai-groq/src/utils/client.ts` around lines 20 - 26, The
current env selection in client.ts binds env to window.env if that object exists
even when it lacks GROQ_API_KEY, preventing fallback to process.env and causing
false "missing key" errors; change the logic so you first look for GROQ_API_KEY
on (globalThis as any).window?.env and use that value if present, otherwise fall
back to process.env.GROQ_API_KEY (or process.env as the env source) — update the
env/key resolution around the env and key variables so key is derived from
window.env.GROQ_API_KEY when defined, else from process.env.GROQ_API_KEY.
| for (const propName of allPropertyNames) { | ||
| const prop = properties[propName] | ||
| const wasOptional = !originalRequired.includes(propName) | ||
|
|
||
| if (prop.type === 'object' && prop.properties) { | ||
| properties[propName] = makeGroqStructuredOutputCompatible( | ||
| prop, | ||
| prop.required || [], | ||
| ) | ||
| } else if (prop.type === 'array' && prop.items) { | ||
| properties[propName] = { | ||
| ...prop, | ||
| items: makeGroqStructuredOutputCompatible( | ||
| prop.items, | ||
| prop.items.required || [], | ||
| ), | ||
| } | ||
| } else if (wasOptional) { | ||
| if (prop.type && !Array.isArray(prop.type)) { | ||
| properties[propName] = { | ||
| ...prop, | ||
| type: [prop.type, 'null'], | ||
| } | ||
| } else if (Array.isArray(prop.type) && !prop.type.includes('null')) { | ||
| properties[propName] = { | ||
| ...prop, | ||
| type: [...prop.type, 'null'], | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| result.properties = properties | ||
| result.required = allPropertyNames | ||
| result.additionalProperties = false |
There was a problem hiding this comment.
Optional object/array properties lose optional semantics after transformation.
Lines 61 and 66 recurse before optional handling, so optional object/array fields never get 'null' added. Then Line 90 makes all fields required, effectively making those optional fields required.
Proposed fix (apply nullability after recursion for all optional properties)
for (const propName of allPropertyNames) {
const prop = properties[propName]
const wasOptional = !originalRequired.includes(propName)
+ let transformedProp = prop
if (prop.type === 'object' && prop.properties) {
- properties[propName] = makeGroqStructuredOutputCompatible(
+ transformedProp = makeGroqStructuredOutputCompatible(
prop,
prop.required || [],
)
} else if (prop.type === 'array' && prop.items) {
- properties[propName] = {
+ transformedProp = {
...prop,
items: makeGroqStructuredOutputCompatible(
prop.items,
prop.items.required || [],
),
}
- } else if (wasOptional) {
- if (prop.type && !Array.isArray(prop.type)) {
- properties[propName] = {
- ...prop,
- type: [prop.type, 'null'],
- }
- } else if (Array.isArray(prop.type) && !prop.type.includes('null')) {
- properties[propName] = {
- ...prop,
- type: [...prop.type, 'null'],
- }
- }
}
+
+ if (wasOptional) {
+ if (transformedProp.type && !Array.isArray(transformedProp.type)) {
+ transformedProp = { ...transformedProp, type: [transformedProp.type, 'null'] }
+ } else if (Array.isArray(transformedProp.type) && !transformedProp.type.includes('null')) {
+ transformedProp = { ...transformedProp, type: [...transformedProp.type, 'null'] }
+ }
+ }
+ properties[propName] = transformedProp
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/typescript/ai-groq/src/utils/schema-converter.ts` around lines 57 -
91, The recursion for object/array props in makeGroqStructuredOutputCompatible
runs before applying optional nullability and then result.required is
incorrectly set to allPropertyNames, which makes optional fields required; fix
by 1) after the existing recursion for prop.type === 'object' and prop.type ===
'array', apply the optional-nullability patch using the wasOptional check (i.e.,
add 'null' into prop.type or prop.type array) so that object/array props also
receive 'null' when optional, and 2) set result.required = originalRequired (not
allPropertyNames) so only originally required properties remain required; update
references in the function to modify properties[propName] after recursion and
use originalRequired for result.required.
| } else if (prop.type === 'array' && prop.items) { | ||
| properties[propName] = { | ||
| ...prop, | ||
| items: makeGroqStructuredOutputCompatible( | ||
| prop.items, | ||
| prop.items.required || [], | ||
| ), |
There was a problem hiding this comment.
Handle tuple-style array schemas (items: JSONSchema[]) explicitly.
Line 70 and Line 96 assume items is a single schema object. When items is an array (tuple schema), this logic passes an array into object-based transformation and can produce invalid output.
Proposed fix for tuple `items` support
} else if (prop.type === 'array' && prop.items) {
transformedProp = {
...prop,
- items: makeGroqStructuredOutputCompatible(
- prop.items,
- prop.items.required || [],
- ),
+ items: Array.isArray(prop.items)
+ ? prop.items.map((item) =>
+ makeGroqStructuredOutputCompatible(item, item.required || []),
+ )
+ : makeGroqStructuredOutputCompatible(
+ prop.items,
+ prop.items.required || [],
+ ),
}
}
@@
if (result.type === 'array' && result.items) {
- result.items = makeGroqStructuredOutputCompatible(
- result.items,
- result.items.required || [],
- )
+ result.items = Array.isArray(result.items)
+ ? result.items.map((item) =>
+ makeGroqStructuredOutputCompatible(item, item.required || []),
+ )
+ : makeGroqStructuredOutputCompatible(result.items, result.items.required || [])
}Also applies to: 94-98
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/typescript/ai-groq/src/utils/schema-converter.ts` around lines 66 -
72, The array-handling branch in makeGroqStructuredOutputCompatible assumes
prop.items is a single object and passing an array (tuple-style JSON Schema)
into the object transformation produces invalid output; update the branches that
set properties[propName].items (and the similar block at the later occurrence)
to detect Array.isArray(prop.items) and, when true, map over prop.items calling
makeGroqStructuredOutputCompatible(item, item.required || []) for each tuple
entry, otherwise keep the existing single-object call for non-tuple schemas
(i.e., preserve the current call makeGroqStructuredOutputCompatible(prop.items,
prop.items.required || []) when items is not an array).
|
View your CI Pipeline Execution ↗ for commit b80b693
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-anthropic
@tanstack/ai-client
@tanstack/ai-devtools-core
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-groq
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-openrouter
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
Adds a new
@tanstack/ai-groqpackage for Groq AI model integration. This package provides a tree-shakeable text adapter, supporting streaming chat completions, structured output, and tool usage.🎯 Changes
✅ Checklist
pnpm run test:pr.🚀 Release Impact
Summary by CodeRabbit