@ai-sdk/groq 0.0.0-64aae7dd-20260114144918 → 0.0.0-98261322-20260122142521

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.
@@ -0,0 +1,110 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { groqProviderOptions, GroqProviderOptions } from './groq-chat-options';
3
+
4
+ describe('groqProviderOptions', () => {
5
+ describe('reasoningEffort', () => {
6
+ it('accepts valid reasoningEffort values', () => {
7
+ const validValues = ['none', 'default', 'low', 'medium', 'high'] as const;
8
+
9
+ validValues.forEach(value => {
10
+ const result = groqProviderOptions.safeParse({
11
+ reasoningEffort: value,
12
+ });
13
+ expect(result.success).toBe(true);
14
+ expect(result.data?.reasoningEffort).toBe(value);
15
+ });
16
+ });
17
+
18
+ it('rejects invalid reasoningEffort values', () => {
19
+ const invalidValues = [
20
+ 'invalid',
21
+ 'high-effort',
22
+ 'minimal',
23
+ 'maximum',
24
+ '',
25
+ ];
26
+
27
+ invalidValues.forEach(value => {
28
+ const result = groqProviderOptions.safeParse({
29
+ reasoningEffort: value,
30
+ });
31
+ expect(result.success).toBe(false);
32
+ });
33
+ });
34
+
35
+ it('allows reasoningEffort to be undefined', () => {
36
+ const result = groqProviderOptions.safeParse({});
37
+ expect(result.success).toBe(true);
38
+ expect(result.data?.reasoningEffort).toBeUndefined();
39
+ });
40
+
41
+ it('allows reasoningEffort to be omitted explicitly', () => {
42
+ const result = groqProviderOptions.safeParse({
43
+ reasoningEffort: undefined,
44
+ });
45
+ expect(result.success).toBe(true);
46
+ expect(result.data?.reasoningEffort).toBeUndefined();
47
+ });
48
+ });
49
+
50
+ describe('combined options with reasoningEffort', () => {
51
+ it('accepts reasoningEffort with other valid options', () => {
52
+ const result = groqProviderOptions.safeParse({
53
+ reasoningEffort: 'high',
54
+ parallelToolCalls: true,
55
+ user: 'test-user',
56
+ structuredOutputs: false,
57
+ serviceTier: 'flex',
58
+ });
59
+
60
+ expect(result.success).toBe(true);
61
+ expect(result.data?.reasoningEffort).toBe('high');
62
+ expect(result.data?.parallelToolCalls).toBe(true);
63
+ expect(result.data?.user).toBe('test-user');
64
+ });
65
+
66
+ it('rejects when reasoningEffort is invalid among valid options', () => {
67
+ const result = groqProviderOptions.safeParse({
68
+ reasoningEffort: 'ultra-high',
69
+ parallelToolCalls: true,
70
+ user: 'test-user',
71
+ });
72
+
73
+ expect(result.success).toBe(false);
74
+ });
75
+ });
76
+
77
+ describe('all reasoningEffort enum variants', () => {
78
+ it('validates all reasoningEffort variants individually', () => {
79
+ const variants: Array<'none' | 'default' | 'low' | 'medium' | 'high'> = [
80
+ 'none',
81
+ 'default',
82
+ 'low',
83
+ 'medium',
84
+ 'high',
85
+ ];
86
+
87
+ variants.forEach(variant => {
88
+ const result = groqProviderOptions.safeParse({
89
+ reasoningEffort: variant,
90
+ });
91
+ expect(result.success).toBe(true);
92
+ if (result.success) {
93
+ expect(result.data.reasoningEffort).toBe(variant);
94
+ }
95
+ });
96
+ });
97
+ });
98
+
99
+ describe('type inference', () => {
100
+ it('infers GroqProviderOptions type correctly', () => {
101
+ const options: GroqProviderOptions = {
102
+ reasoningEffort: 'medium',
103
+ parallelToolCalls: false,
104
+ };
105
+
106
+ expect(options.reasoningEffort).toBe('medium');
107
+ expect(options.parallelToolCalls).toBe(false);
108
+ });
109
+ });
110
+ });
@@ -0,0 +1,78 @@
1
+ import { z } from 'zod/v4';
2
+
3
+ // https://console.groq.com/docs/models
4
+ export type GroqChatModelId =
5
+ // production models
6
+ | 'gemma2-9b-it'
7
+ | 'llama-3.1-8b-instant'
8
+ | 'llama-3.3-70b-versatile'
9
+ | 'meta-llama/llama-guard-4-12b'
10
+ | 'openai/gpt-oss-120b'
11
+ | 'openai/gpt-oss-20b'
12
+ // preview models (selection)
13
+ | 'deepseek-r1-distill-llama-70b'
14
+ | 'meta-llama/llama-4-maverick-17b-128e-instruct'
15
+ | 'meta-llama/llama-4-scout-17b-16e-instruct'
16
+ | 'meta-llama/llama-prompt-guard-2-22m'
17
+ | 'meta-llama/llama-prompt-guard-2-86m'
18
+ | 'moonshotai/kimi-k2-instruct-0905'
19
+ | 'qwen/qwen3-32b'
20
+ | 'llama-guard-3-8b'
21
+ | 'llama3-70b-8192'
22
+ | 'llama3-8b-8192'
23
+ | 'mixtral-8x7b-32768'
24
+ | 'qwen-qwq-32b'
25
+ | 'qwen-2.5-32b'
26
+ | 'deepseek-r1-distill-qwen-32b'
27
+ | (string & {});
28
+
29
+ export const groqProviderOptions = z.object({
30
+ reasoningFormat: z.enum(['parsed', 'raw', 'hidden']).optional(),
31
+
32
+ /**
33
+ * Specifies the reasoning effort level for model inference.
34
+ * @see https://console.groq.com/docs/reasoning#reasoning-effort
35
+ */
36
+ reasoningEffort: z
37
+ .enum(['none', 'default', 'low', 'medium', 'high'])
38
+ .optional(),
39
+
40
+ /**
41
+ * Whether to enable parallel function calling during tool use. Default to true.
42
+ */
43
+ parallelToolCalls: z.boolean().optional(),
44
+
45
+ /**
46
+ * A unique identifier representing your end-user, which can help OpenAI to
47
+ * monitor and detect abuse. Learn more.
48
+ */
49
+ user: z.string().optional(),
50
+
51
+ /**
52
+ * Whether to use structured outputs.
53
+ *
54
+ * @default true
55
+ */
56
+ structuredOutputs: z.boolean().optional(),
57
+
58
+ /**
59
+ * Whether to use strict JSON schema validation.
60
+ * When true, the model uses constrained decoding to guarantee schema compliance.
61
+ * Only used when structured outputs are enabled and a schema is provided.
62
+ *
63
+ * @default true
64
+ */
65
+ strictJsonSchema: z.boolean().optional(),
66
+
67
+ /**
68
+ * Service tier for the request.
69
+ * - 'on_demand': Default tier with consistent performance and fairness
70
+ * - 'flex': Higher throughput tier optimized for workloads that can handle occasional request failures
71
+ * - 'auto': Uses on_demand rate limits, then falls back to flex tier if exceeded
72
+ *
73
+ * @default 'on_demand'
74
+ */
75
+ serviceTier: z.enum(['on_demand', 'flex', 'auto']).optional(),
76
+ });
77
+
78
+ export type GroqProviderOptions = z.infer<typeof groqProviderOptions>;
@@ -0,0 +1,9 @@
1
+ import { FetchFunction } from '@ai-sdk/provider-utils';
2
+
3
+ export type GroqConfig = {
4
+ provider: string;
5
+ url: (options: { modelId: string; path: string }) => string;
6
+ headers: () => Record<string, string | undefined>;
7
+ fetch?: FetchFunction;
8
+ generateId?: () => string;
9
+ };
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod/v4';
2
+ import { createJsonErrorResponseHandler } from '@ai-sdk/provider-utils';
3
+
4
+ export const groqErrorDataSchema = z.object({
5
+ error: z.object({
6
+ message: z.string(),
7
+ type: z.string(),
8
+ }),
9
+ });
10
+
11
+ export type GroqErrorData = z.infer<typeof groqErrorDataSchema>;
12
+
13
+ export const groqFailedResponseHandler = createJsonErrorResponseHandler({
14
+ errorSchema: groqErrorDataSchema,
15
+ errorToMessage: data => data.error.message,
16
+ });
@@ -0,0 +1,272 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { prepareTools } from './groq-prepare-tools';
3
+
4
+ describe('prepareTools', () => {
5
+ it('should return undefined tools and toolChoice when tools are null', () => {
6
+ const result = prepareTools({
7
+ tools: undefined,
8
+ modelId: 'gemma2-9b-it',
9
+ });
10
+
11
+ expect(result).toEqual({
12
+ tools: undefined,
13
+ toolChoice: undefined,
14
+ toolWarnings: [],
15
+ });
16
+ });
17
+
18
+ it('should return undefined tools and toolChoice when tools are empty', () => {
19
+ const result = prepareTools({
20
+ tools: [],
21
+ modelId: 'gemma2-9b-it',
22
+ });
23
+
24
+ expect(result).toEqual({
25
+ tools: undefined,
26
+ toolChoice: undefined,
27
+ toolWarnings: [],
28
+ });
29
+ });
30
+
31
+ it('should correctly prepare function tools', () => {
32
+ const result = prepareTools({
33
+ tools: [
34
+ {
35
+ type: 'function',
36
+ name: 'testFunction',
37
+ description: 'A test function',
38
+ inputSchema: { type: 'object', properties: {} },
39
+ },
40
+ ],
41
+ modelId: 'gemma2-9b-it',
42
+ });
43
+
44
+ expect(result.tools).toEqual([
45
+ {
46
+ type: 'function',
47
+ function: {
48
+ name: 'testFunction',
49
+ description: 'A test function',
50
+ parameters: { type: 'object', properties: {} },
51
+ },
52
+ },
53
+ ]);
54
+ expect(result.toolChoice).toBeUndefined();
55
+ expect(result.toolWarnings).toEqual([]);
56
+ });
57
+
58
+ it('should add warnings for unsupported provider-defined tools', () => {
59
+ const result = prepareTools({
60
+ tools: [
61
+ {
62
+ type: 'provider',
63
+ id: 'some.unsupported_tool',
64
+ name: 'unsupported_tool',
65
+ args: {},
66
+ },
67
+ ],
68
+ modelId: 'gemma2-9b-it',
69
+ });
70
+
71
+ expect(result.tools).toEqual([]);
72
+ expect(result.toolChoice).toBeUndefined();
73
+ expect(result.toolWarnings).toMatchInlineSnapshot(`
74
+ [
75
+ {
76
+ "feature": "provider-defined tool some.unsupported_tool",
77
+ "type": "unsupported",
78
+ },
79
+ ]
80
+ `);
81
+ });
82
+
83
+ it('should handle tool choice "auto"', () => {
84
+ const result = prepareTools({
85
+ tools: [
86
+ {
87
+ type: 'function',
88
+ name: 'testFunction',
89
+ description: 'Test',
90
+ inputSchema: {},
91
+ },
92
+ ],
93
+ toolChoice: { type: 'auto' },
94
+ modelId: 'gemma2-9b-it',
95
+ });
96
+ expect(result.toolChoice).toEqual('auto');
97
+ });
98
+
99
+ it('should handle tool choice "required"', () => {
100
+ const result = prepareTools({
101
+ tools: [
102
+ {
103
+ type: 'function',
104
+ name: 'testFunction',
105
+ description: 'Test',
106
+ inputSchema: {},
107
+ },
108
+ ],
109
+ toolChoice: { type: 'required' },
110
+ modelId: 'gemma2-9b-it',
111
+ });
112
+ expect(result.toolChoice).toEqual('required');
113
+ });
114
+
115
+ it('should handle tool choice "none"', () => {
116
+ const result = prepareTools({
117
+ tools: [
118
+ {
119
+ type: 'function',
120
+ name: 'testFunction',
121
+ description: 'Test',
122
+ inputSchema: {},
123
+ },
124
+ ],
125
+ toolChoice: { type: 'none' },
126
+ modelId: 'gemma2-9b-it',
127
+ });
128
+ expect(result.toolChoice).toEqual('none');
129
+ });
130
+
131
+ it('should handle tool choice "tool"', () => {
132
+ const result = prepareTools({
133
+ tools: [
134
+ {
135
+ type: 'function',
136
+ name: 'testFunction',
137
+ description: 'Test',
138
+ inputSchema: {},
139
+ },
140
+ ],
141
+ toolChoice: { type: 'tool', toolName: 'testFunction' },
142
+ modelId: 'gemma2-9b-it',
143
+ });
144
+ expect(result.toolChoice).toEqual({
145
+ type: 'function',
146
+ function: { name: 'testFunction' },
147
+ });
148
+ });
149
+
150
+ describe('browser search tool', () => {
151
+ it('should handle browser search tool with supported model', () => {
152
+ const result = prepareTools({
153
+ tools: [
154
+ {
155
+ type: 'provider',
156
+ id: 'groq.browser_search',
157
+ name: 'browser_search',
158
+ args: {},
159
+ },
160
+ ],
161
+ modelId: 'openai/gpt-oss-120b', // Supported model
162
+ });
163
+
164
+ expect(result.tools).toEqual([
165
+ {
166
+ type: 'browser_search',
167
+ },
168
+ ]);
169
+ expect(result.toolWarnings).toEqual([]);
170
+ });
171
+
172
+ it('should warn when browser search is used with unsupported model', () => {
173
+ const result = prepareTools({
174
+ tools: [
175
+ {
176
+ type: 'provider',
177
+ id: 'groq.browser_search',
178
+ name: 'browser_search',
179
+ args: {},
180
+ },
181
+ ],
182
+ modelId: 'gemma2-9b-it', // Unsupported model
183
+ });
184
+
185
+ expect(result.tools).toEqual([]);
186
+ expect(result.toolWarnings).toMatchInlineSnapshot(`
187
+ [
188
+ {
189
+ "details": "Browser search is only supported on the following models: openai/gpt-oss-20b, openai/gpt-oss-120b. Current model: gemma2-9b-it",
190
+ "feature": "provider-defined tool groq.browser_search",
191
+ "type": "unsupported",
192
+ },
193
+ ]
194
+ `);
195
+ });
196
+
197
+ it('should handle mixed tools with model validation', () => {
198
+ const result = prepareTools({
199
+ tools: [
200
+ {
201
+ type: 'function',
202
+ name: 'test-tool',
203
+ description: 'A test tool',
204
+ inputSchema: { type: 'object', properties: {} },
205
+ },
206
+ {
207
+ type: 'provider',
208
+ id: 'groq.browser_search',
209
+ name: 'browser_search',
210
+ args: {},
211
+ },
212
+ ],
213
+ modelId: 'openai/gpt-oss-20b', // Supported model
214
+ });
215
+
216
+ expect(result.tools).toEqual([
217
+ {
218
+ type: 'function',
219
+ function: {
220
+ name: 'test-tool',
221
+ description: 'A test tool',
222
+ parameters: { type: 'object', properties: {} },
223
+ },
224
+ },
225
+ {
226
+ type: 'browser_search',
227
+ },
228
+ ]);
229
+ expect(result.toolWarnings).toEqual([]);
230
+ });
231
+
232
+ it('should validate all browser search supported models', () => {
233
+ const supportedModels = ['openai/gpt-oss-20b', 'openai/gpt-oss-120b'];
234
+
235
+ supportedModels.forEach(modelId => {
236
+ const result = prepareTools({
237
+ tools: [
238
+ {
239
+ type: 'provider',
240
+ id: 'groq.browser_search',
241
+ name: 'browser_search',
242
+ args: {},
243
+ },
244
+ ],
245
+ modelId: modelId as any,
246
+ });
247
+
248
+ expect(result.tools).toEqual([{ type: 'browser_search' }]);
249
+ expect(result.toolWarnings).toEqual([]);
250
+ });
251
+ });
252
+
253
+ it('should handle browser search with tool choice', () => {
254
+ const result = prepareTools({
255
+ tools: [
256
+ {
257
+ type: 'provider',
258
+ id: 'groq.browser_search',
259
+ name: 'browser_search',
260
+ args: {},
261
+ },
262
+ ],
263
+ toolChoice: { type: 'required' },
264
+ modelId: 'openai/gpt-oss-120b',
265
+ });
266
+
267
+ expect(result.tools).toEqual([{ type: 'browser_search' }]);
268
+ expect(result.toolChoice).toEqual('required');
269
+ expect(result.toolWarnings).toEqual([]);
270
+ });
271
+ });
272
+ });
@@ -0,0 +1,128 @@
1
+ import {
2
+ LanguageModelV3CallOptions,
3
+ SharedV3Warning,
4
+ UnsupportedFunctionalityError,
5
+ } from '@ai-sdk/provider';
6
+ import {
7
+ getSupportedModelsString,
8
+ isBrowserSearchSupportedModel,
9
+ } from './groq-browser-search-models';
10
+ import { GroqChatModelId } from './groq-chat-options';
11
+
12
+ export function prepareTools({
13
+ tools,
14
+ toolChoice,
15
+ modelId,
16
+ }: {
17
+ tools: LanguageModelV3CallOptions['tools'];
18
+ toolChoice?: LanguageModelV3CallOptions['toolChoice'];
19
+ modelId: GroqChatModelId;
20
+ }): {
21
+ tools:
22
+ | undefined
23
+ | Array<
24
+ | {
25
+ type: 'function';
26
+ function: {
27
+ name: string;
28
+ description: string | undefined;
29
+ parameters: unknown;
30
+ };
31
+ }
32
+ | {
33
+ type: 'browser_search';
34
+ }
35
+ >;
36
+ toolChoice:
37
+ | { type: 'function'; function: { name: string } }
38
+ | 'auto'
39
+ | 'none'
40
+ | 'required'
41
+ | undefined;
42
+ toolWarnings: SharedV3Warning[];
43
+ } {
44
+ // when the tools array is empty, change it to undefined to prevent errors:
45
+ tools = tools?.length ? tools : undefined;
46
+
47
+ const toolWarnings: SharedV3Warning[] = [];
48
+
49
+ if (tools == null) {
50
+ return { tools: undefined, toolChoice: undefined, toolWarnings };
51
+ }
52
+
53
+ const groqTools: Array<
54
+ | {
55
+ type: 'function';
56
+ function: {
57
+ name: string;
58
+ description: string | undefined;
59
+ parameters: unknown;
60
+ };
61
+ }
62
+ | {
63
+ type: 'browser_search';
64
+ }
65
+ > = [];
66
+
67
+ for (const tool of tools) {
68
+ if (tool.type === 'provider') {
69
+ if (tool.id === 'groq.browser_search') {
70
+ if (!isBrowserSearchSupportedModel(modelId)) {
71
+ toolWarnings.push({
72
+ type: 'unsupported',
73
+ feature: `provider-defined tool ${tool.id}`,
74
+ details: `Browser search is only supported on the following models: ${getSupportedModelsString()}. Current model: ${modelId}`,
75
+ });
76
+ } else {
77
+ groqTools.push({
78
+ type: 'browser_search',
79
+ });
80
+ }
81
+ } else {
82
+ toolWarnings.push({
83
+ type: 'unsupported',
84
+ feature: `provider-defined tool ${tool.id}`,
85
+ });
86
+ }
87
+ } else {
88
+ groqTools.push({
89
+ type: 'function',
90
+ function: {
91
+ name: tool.name,
92
+ description: tool.description,
93
+ parameters: tool.inputSchema,
94
+ },
95
+ });
96
+ }
97
+ }
98
+
99
+ if (toolChoice == null) {
100
+ return { tools: groqTools, toolChoice: undefined, toolWarnings };
101
+ }
102
+
103
+ const type = toolChoice.type;
104
+
105
+ switch (type) {
106
+ case 'auto':
107
+ case 'none':
108
+ case 'required':
109
+ return { tools: groqTools, toolChoice: type, toolWarnings };
110
+ case 'tool':
111
+ return {
112
+ tools: groqTools,
113
+ toolChoice: {
114
+ type: 'function',
115
+ function: {
116
+ name: toolChoice.toolName,
117
+ },
118
+ },
119
+ toolWarnings,
120
+ };
121
+ default: {
122
+ const _exhaustiveCheck: never = type;
123
+ throw new UnsupportedFunctionalityError({
124
+ functionality: `tool choice type: ${_exhaustiveCheck}`,
125
+ });
126
+ }
127
+ }
128
+ }