@tanstack/ai-groq 0.5.3 → 0.6.0

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.
@@ -1,10 +1,15 @@
1
1
  import { convertFunctionToolToAdapterFormat } from "./function-tool.js";
2
+ //#region src/tools/tool-converter.ts
3
+ /**
4
+ * Converts an array of standard Tools to Groq-specific format.
5
+ * Groq uses an OpenAI-compatible API, so we primarily support function tools.
6
+ */
2
7
  function convertToolsToProviderFormat(tools) {
3
- return tools.map((tool) => {
4
- return convertFunctionToolToAdapterFormat(tool);
5
- });
8
+ return tools.map((tool) => {
9
+ return convertFunctionToolToAdapterFormat(tool);
10
+ });
6
11
  }
7
- export {
8
- convertToolsToProviderFormat
9
- };
10
- //# sourceMappingURL=tool-converter.js.map
12
+ //#endregion
13
+ export { convertToolsToProviderFormat };
14
+
15
+ //# sourceMappingURL=tool-converter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-converter.js","sources":["../../../src/tools/tool-converter.ts"],"sourcesContent":["import { convertFunctionToolToAdapterFormat } from './function-tool'\nimport type { FunctionTool } from './function-tool'\nimport type { Tool } from '@tanstack/ai'\n\n/**\n * Converts an array of standard Tools to Groq-specific format.\n * Groq uses an OpenAI-compatible API, so we primarily support function tools.\n */\nexport function convertToolsToProviderFormat(\n tools: Array<Tool>,\n): Array<FunctionTool> {\n return tools.map((tool) => {\n return convertFunctionToolToAdapterFormat(tool)\n })\n}\n"],"names":[],"mappings":";AAQO,SAAS,6BACd,OACqB;AACrB,SAAO,MAAM,IAAI,CAAC,SAAS;AACzB,WAAO,mCAAmC,IAAI;AAAA,EAChD,CAAC;AACH;"}
1
+ {"version":3,"file":"tool-converter.js","names":[],"sources":["../../../src/tools/tool-converter.ts"],"sourcesContent":["import { convertFunctionToolToAdapterFormat } from './function-tool'\nimport type { FunctionTool } from './function-tool'\nimport type { Tool } from '@tanstack/ai'\n\n/**\n * Converts an array of standard Tools to Groq-specific format.\n * Groq uses an OpenAI-compatible API, so we primarily support function tools.\n */\nexport function convertToolsToProviderFormat(\n tools: Array<Tool>,\n): Array<FunctionTool> {\n return tools.map((tool) => {\n return convertFunctionToolToAdapterFormat(tool)\n })\n}\n"],"mappings":";;;;;;AAQA,SAAgB,6BACd,OACqB;CACrB,OAAO,MAAM,KAAK,SAAS;EACzB,OAAO,mCAAmC,IAAI;CAChD,CAAC;AACH"}
@@ -1,21 +1,28 @@
1
1
  import { getApiKeyFromEnv } from "@tanstack/ai-utils";
2
+ //#region src/utils/client.ts
3
+ /**
4
+ * Gets Groq API key from environment variables
5
+ * @throws Error if GROQ_API_KEY is not found
6
+ */
2
7
  function getGroqApiKeyFromEnv() {
3
- try {
4
- return getApiKeyFromEnv("GROQ_API_KEY");
5
- } catch {
6
- throw new Error(
7
- "GROQ_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key."
8
- );
9
- }
8
+ try {
9
+ return getApiKeyFromEnv("GROQ_API_KEY");
10
+ } catch {
11
+ throw new Error("GROQ_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key.");
12
+ }
10
13
  }
14
+ /**
15
+ * Returns a Groq client config with Groq's OpenAI-compatible base URL
16
+ * applied when not already set. The Groq endpoint accepts the OpenAI SDK
17
+ * verbatim, so the adapter drives it via the OpenAI SDK with this baseURL.
18
+ */
11
19
  function withGroqDefaults(config) {
12
- return {
13
- ...config,
14
- baseURL: config.baseURL || "https://api.groq.com/openai/v1"
15
- };
20
+ return {
21
+ ...config,
22
+ baseURL: config.baseURL || "https://api.groq.com/openai/v1"
23
+ };
16
24
  }
17
- export {
18
- getGroqApiKeyFromEnv,
19
- withGroqDefaults
20
- };
21
- //# sourceMappingURL=client.js.map
25
+ //#endregion
26
+ export { getGroqApiKeyFromEnv, withGroqDefaults };
27
+
28
+ //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sources":["../../../src/utils/client.ts"],"sourcesContent":["import { getApiKeyFromEnv } from '@tanstack/ai-utils'\nimport type { ClientOptions } from 'openai'\n\nexport interface GroqClientConfig extends Omit<ClientOptions, 'apiKey'> {\n apiKey: string\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 try {\n return getApiKeyFromEnv('GROQ_API_KEY')\n } catch {\n throw new Error(\n 'GROQ_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key.',\n )\n }\n}\n\n/**\n * Returns a Groq client config with Groq's OpenAI-compatible base URL\n * applied when not already set. The Groq endpoint accepts the OpenAI SDK\n * verbatim, so the adapter drives it via the OpenAI SDK with this baseURL.\n */\nexport function withGroqDefaults(config: GroqClientConfig): GroqClientConfig {\n return {\n ...config,\n baseURL: config.baseURL || 'https://api.groq.com/openai/v1',\n }\n}\n"],"names":[],"mappings":";AAWO,SAAS,uBAA+B;AAC7C,MAAI;AACF,WAAO,iBAAiB,cAAc;AAAA,EACxC,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AACF;AAOO,SAAS,iBAAiB,QAA4C;AAC3E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,OAAO,WAAW;AAAA,EAAA;AAE/B;"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../../src/utils/client.ts"],"sourcesContent":["import { getApiKeyFromEnv } from '@tanstack/ai-utils'\nimport type { ClientOptions } from 'openai'\n\nexport interface GroqClientConfig extends Omit<ClientOptions, 'apiKey'> {\n apiKey: string\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 try {\n return getApiKeyFromEnv('GROQ_API_KEY')\n } catch {\n throw new Error(\n 'GROQ_API_KEY is required. Please set it in your environment variables or use the factory function with an explicit API key.',\n )\n }\n}\n\n/**\n * Returns a Groq client config with Groq's OpenAI-compatible base URL\n * applied when not already set. The Groq endpoint accepts the OpenAI SDK\n * verbatim, so the adapter drives it via the OpenAI SDK with this baseURL.\n */\nexport function withGroqDefaults(config: GroqClientConfig): GroqClientConfig {\n return {\n ...config,\n baseURL: config.baseURL || 'https://api.groq.com/openai/v1',\n }\n}\n"],"mappings":";;;;;;AAWA,SAAgB,uBAA+B;CAC7C,IAAI;EACF,OAAO,iBAAiB,cAAc;CACxC,QAAQ;EACN,MAAM,IAAI,MACR,6HACF;CACF;AACF;;;;;;AAOA,SAAgB,iBAAiB,QAA4C;CAC3E,OAAO;EACL,GAAG;EACH,SAAS,OAAO,WAAW;CAC7B;AACF"}
@@ -1,5 +1,3 @@
1
- import { transformNullsToUndefined } from '@tanstack/ai-utils';
2
- export { transformNullsToUndefined };
3
1
  /**
4
2
  * Transform a JSON schema to be compatible with Groq's structured output requirements.
5
3
  *
@@ -1,73 +1,73 @@
1
1
  import { makeStructuredOutputCompatible } from "@tanstack/openai-base";
2
- import { transformNullsToUndefined } from "@tanstack/ai-utils";
2
+ //#region src/utils/schema-converter.ts
3
+ /**
4
+ * Recursively removes `required: []` from a schema object.
5
+ * Groq rejects `required` when it is an empty array, even though
6
+ * OpenAI-compatible schemas allow it.
7
+ */
3
8
  function removeEmptyRequired(schema) {
4
- const result = { ...schema };
5
- if (Array.isArray(result.required) && result.required.length === 0) {
6
- delete result.required;
7
- }
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;
16
- }
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
- );
25
- }
26
- }
27
- if (result.additionalProperties && typeof result.additionalProperties === "object" && !Array.isArray(result.additionalProperties)) {
28
- result.additionalProperties = removeEmptyRequired(
29
- result.additionalProperties
30
- );
31
- }
32
- return result;
9
+ const result = { ...schema };
10
+ if (Array.isArray(result.required) && result.required.length === 0) delete result.required;
11
+ if (result.properties && typeof result.properties === "object") {
12
+ const properties = {};
13
+ for (const [key, value] of Object.entries(result.properties)) properties[key] = typeof value === "object" && value !== null && !Array.isArray(value) ? removeEmptyRequired(value) : value;
14
+ result.properties = properties;
15
+ }
16
+ if (result.items && typeof result.items === "object" && !Array.isArray(result.items)) result.items = removeEmptyRequired(result.items);
17
+ for (const keyword of [
18
+ "anyOf",
19
+ "oneOf",
20
+ "allOf"
21
+ ]) if (Array.isArray(result[keyword])) result[keyword] = result[keyword].map((entry) => removeEmptyRequired(entry));
22
+ if (result.additionalProperties && typeof result.additionalProperties === "object" && !Array.isArray(result.additionalProperties)) result.additionalProperties = removeEmptyRequired(result.additionalProperties);
23
+ return result;
33
24
  }
25
+ /**
26
+ * Recursively normalise object schemas so any `{ type: 'object' }` node
27
+ * without `properties` gets an empty `properties: {}` object. The
28
+ * ai-openai-base transformer only descends into objects that already have
29
+ * `properties` set, so a Zod `z.object({})` nested inside `properties`,
30
+ * `items`, `additionalProperties`, or a combinator branch would otherwise
31
+ * skip the strict-mode rewrite and fail Groq validation.
32
+ */
34
33
  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
- );
55
- }
56
- }
57
- if (result.additionalProperties && typeof result.additionalProperties === "object" && !Array.isArray(result.additionalProperties)) {
58
- result.additionalProperties = normalizeObjectSchemas(
59
- result.additionalProperties
60
- );
61
- }
62
- return result;
34
+ const result = schema.type === "object" && !schema.properties ? {
35
+ ...schema,
36
+ properties: {}
37
+ } : { ...schema };
38
+ if (result.properties && typeof result.properties === "object") result.properties = Object.fromEntries(Object.entries(result.properties).map(([key, value]) => [key, typeof value === "object" && value !== null && !Array.isArray(value) ? normalizeObjectSchemas(value) : value]));
39
+ if (result.items && typeof result.items === "object" && !Array.isArray(result.items)) result.items = normalizeObjectSchemas(result.items);
40
+ for (const keyword of [
41
+ "anyOf",
42
+ "oneOf",
43
+ "allOf"
44
+ ]) {
45
+ const branch = result[keyword];
46
+ if (Array.isArray(branch)) result[keyword] = branch.map((entry) => typeof entry === "object" && entry !== null ? normalizeObjectSchemas(entry) : entry);
47
+ }
48
+ if (result.additionalProperties && typeof result.additionalProperties === "object" && !Array.isArray(result.additionalProperties)) result.additionalProperties = normalizeObjectSchemas(result.additionalProperties);
49
+ return result;
63
50
  }
51
+ /**
52
+ * Transform a JSON schema to be compatible with Groq's structured output requirements.
53
+ *
54
+ * Groq requires:
55
+ * - All properties must be in the `required` array
56
+ * - Optional fields should have null added to their type union
57
+ * - additionalProperties must be false for objects
58
+ * - `required` must be omitted (not empty array) when there are no properties
59
+ *
60
+ * Delegates to the shared OpenAI-compatible transformer and applies the
61
+ * Groq-specific quirk of removing empty `required` arrays.
62
+ *
63
+ * @param schema - JSON schema to transform
64
+ * @param originalRequired - Original required array (to know which fields were optional)
65
+ * @returns Transformed schema compatible with Groq structured output
66
+ */
64
67
  function makeGroqStructuredOutputCompatible(schema, originalRequired = []) {
65
- const normalised = normalizeObjectSchemas(schema);
66
- const result = makeStructuredOutputCompatible(normalised, originalRequired);
67
- return removeEmptyRequired(result);
68
+ return removeEmptyRequired(makeStructuredOutputCompatible(normalizeObjectSchemas(schema), originalRequired));
68
69
  }
69
- export {
70
- makeGroqStructuredOutputCompatible,
71
- transformNullsToUndefined
72
- };
73
- //# sourceMappingURL=schema-converter.js.map
70
+ //#endregion
71
+ export { makeGroqStructuredOutputCompatible };
72
+
73
+ //# sourceMappingURL=schema-converter.js.map
@@ -1 +1 @@
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 * ai-openai-base transformer only descends into objects that already have\n * `properties` set, so a Zod `z.object({})` nested inside `properties`,\n * `items`, `additionalProperties`, or a combinator branch would otherwise\n * skip the strict-mode rewrite and fail Groq validation.\n */\nfunction normalizeObjectSchemas(\n schema: Record<string, any>,\n): Record<string, any> {\n const result: Record<string, any> =\n schema.type === 'object' && !schema.properties\n ? { ...schema, properties: {} }\n : { ...schema }\n\n if (result.properties && typeof result.properties === 'object') {\n result.properties = Object.fromEntries(\n Object.entries(result.properties as Record<string, any>).map(\n ([key, value]) => [\n key,\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? normalizeObjectSchemas(value)\n : value,\n ],\n ),\n )\n }\n\n if (\n result.items &&\n typeof result.items === 'object' &&\n !Array.isArray(result.items)\n ) {\n result.items = normalizeObjectSchemas(result.items)\n }\n\n for (const keyword of ['anyOf', 'oneOf', 'allOf'] as const) {\n const branch = result[keyword]\n if (Array.isArray(branch)) {\n result[keyword] = branch.map((entry) =>\n typeof entry === 'object' && entry !== null\n ? normalizeObjectSchemas(entry as Record<string, any>)\n : entry,\n )\n }\n }\n\n if (\n result.additionalProperties &&\n typeof result.additionalProperties === 'object' &&\n !Array.isArray(result.additionalProperties)\n ) {\n result.additionalProperties = normalizeObjectSchemas(\n result.additionalProperties as Record<string, any>,\n )\n }\n\n return result\n}\n\n/**\n * Transform a JSON schema to be compatible with Groq's structured output requirements.\n *\n * Groq requires:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for objects\n * - `required` must be omitted (not empty array) when there are no properties\n *\n * Delegates to the shared OpenAI-compatible transformer and applies the\n * Groq-specific quirk of removing empty `required` arrays.\n *\n * @param schema - JSON schema to transform\n * @param originalRequired - Original required array (to know which fields were optional)\n * @returns Transformed schema compatible with Groq structured output\n */\nexport function makeGroqStructuredOutputCompatible(\n schema: Record<string, any>,\n originalRequired: Array<string> = [],\n): Record<string, any> {\n // Recursively patch every `{ type: 'object' }` node so the ai-openai-base\n // transformer descends into nested empty objects too.\n const normalised = normalizeObjectSchemas(schema)\n\n const result = makeStructuredOutputCompatible(normalised, originalRequired)\n\n // Groq rejects `required` when it is an empty array\n return removeEmptyRequired(result)\n}\n"],"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;"}
1
+ {"version":3,"file":"schema-converter.js","names":[],"sources":["../../../src/utils/schema-converter.ts"],"sourcesContent":["import { makeStructuredOutputCompatible } from '@tanstack/openai-base'\n\n/**\n * Recursively removes `required: []` from a schema object.\n * Groq rejects `required` when it is an empty array, even though\n * OpenAI-compatible schemas allow it.\n */\nfunction removeEmptyRequired(schema: Record<string, any>): Record<string, any> {\n const result = { ...schema }\n\n if (Array.isArray(result.required) && result.required.length === 0) {\n delete result.required\n }\n\n if (result.properties && typeof result.properties === 'object') {\n const properties: Record<string, any> = {}\n for (const [key, value] of Object.entries(\n result.properties as Record<string, any>,\n )) {\n properties[key] =\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? removeEmptyRequired(value)\n : value\n }\n result.properties = properties\n }\n\n if (\n result.items &&\n typeof result.items === 'object' &&\n !Array.isArray(result.items)\n ) {\n result.items = removeEmptyRequired(result.items)\n }\n\n // Recurse into combinator arrays (anyOf, oneOf, allOf)\n for (const keyword of ['anyOf', 'oneOf', 'allOf'] as const) {\n if (Array.isArray(result[keyword])) {\n result[keyword] = result[keyword].map((entry: Record<string, any>) =>\n removeEmptyRequired(entry),\n )\n }\n }\n\n // Recurse into additionalProperties if it's a schema object\n if (\n result.additionalProperties &&\n typeof result.additionalProperties === 'object' &&\n !Array.isArray(result.additionalProperties)\n ) {\n result.additionalProperties = removeEmptyRequired(\n result.additionalProperties,\n )\n }\n\n return result\n}\n\n/**\n * Recursively normalise object schemas so any `{ type: 'object' }` node\n * without `properties` gets an empty `properties: {}` object. The\n * ai-openai-base transformer only descends into objects that already have\n * `properties` set, so a Zod `z.object({})` nested inside `properties`,\n * `items`, `additionalProperties`, or a combinator branch would otherwise\n * skip the strict-mode rewrite and fail Groq validation.\n */\nfunction normalizeObjectSchemas(\n schema: Record<string, any>,\n): Record<string, any> {\n const result: Record<string, any> =\n schema.type === 'object' && !schema.properties\n ? { ...schema, properties: {} }\n : { ...schema }\n\n if (result.properties && typeof result.properties === 'object') {\n result.properties = Object.fromEntries(\n Object.entries(result.properties as Record<string, any>).map(\n ([key, value]) => [\n key,\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? normalizeObjectSchemas(value)\n : value,\n ],\n ),\n )\n }\n\n if (\n result.items &&\n typeof result.items === 'object' &&\n !Array.isArray(result.items)\n ) {\n result.items = normalizeObjectSchemas(result.items)\n }\n\n for (const keyword of ['anyOf', 'oneOf', 'allOf'] as const) {\n const branch = result[keyword]\n if (Array.isArray(branch)) {\n result[keyword] = branch.map((entry) =>\n typeof entry === 'object' && entry !== null\n ? normalizeObjectSchemas(entry as Record<string, any>)\n : entry,\n )\n }\n }\n\n if (\n result.additionalProperties &&\n typeof result.additionalProperties === 'object' &&\n !Array.isArray(result.additionalProperties)\n ) {\n result.additionalProperties = normalizeObjectSchemas(\n result.additionalProperties as Record<string, any>,\n )\n }\n\n return result\n}\n\n/**\n * Transform a JSON schema to be compatible with Groq's structured output requirements.\n *\n * Groq requires:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for objects\n * - `required` must be omitted (not empty array) when there are no properties\n *\n * Delegates to the shared OpenAI-compatible transformer and applies the\n * Groq-specific quirk of removing empty `required` arrays.\n *\n * @param schema - JSON schema to transform\n * @param originalRequired - Original required array (to know which fields were optional)\n * @returns Transformed schema compatible with Groq structured output\n */\nexport function makeGroqStructuredOutputCompatible(\n schema: Record<string, any>,\n originalRequired: Array<string> = [],\n): Record<string, any> {\n // Recursively patch every `{ type: 'object' }` node so the ai-openai-base\n // transformer descends into nested empty objects too.\n const normalised = normalizeObjectSchemas(schema)\n\n const result = makeStructuredOutputCompatible(normalised, originalRequired)\n\n // Groq rejects `required` when it is an empty array\n return removeEmptyRequired(result)\n}\n"],"mappings":";;;;;;;AAOA,SAAS,oBAAoB,QAAkD;CAC7E,MAAM,SAAS,EAAE,GAAG,OAAO;CAE3B,IAAI,MAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,SAAS,WAAW,GAC/D,OAAO,OAAO;CAGhB,IAAI,OAAO,cAAc,OAAO,OAAO,eAAe,UAAU;EAC9D,MAAM,aAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAChC,OAAO,UACT,GACE,WAAW,OACT,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAC/D,oBAAoB,KAAK,IACzB;EAER,OAAO,aAAa;CACtB;CAEA,IACE,OAAO,SACP,OAAO,OAAO,UAAU,YACxB,CAAC,MAAM,QAAQ,OAAO,KAAK,GAE3B,OAAO,QAAQ,oBAAoB,OAAO,KAAK;CAIjD,KAAK,MAAM,WAAW;EAAC;EAAS;EAAS;CAAO,GAC9C,IAAI,MAAM,QAAQ,OAAO,QAAQ,GAC/B,OAAO,WAAW,OAAO,QAAQ,CAAC,KAAK,UACrC,oBAAoB,KAAK,CAC3B;CAKJ,IACE,OAAO,wBACP,OAAO,OAAO,yBAAyB,YACvC,CAAC,MAAM,QAAQ,OAAO,oBAAoB,GAE1C,OAAO,uBAAuB,oBAC5B,OAAO,oBACT;CAGF,OAAO;AACT;;;;;;;;;AAUA,SAAS,uBACP,QACqB;CACrB,MAAM,SACJ,OAAO,SAAS,YAAY,CAAC,OAAO,aAChC;EAAE,GAAG;EAAQ,YAAY,CAAC;CAAE,IAC5B,EAAE,GAAG,OAAO;CAElB,IAAI,OAAO,cAAc,OAAO,OAAO,eAAe,UACpD,OAAO,aAAa,OAAO,YACzB,OAAO,QAAQ,OAAO,UAAiC,CAAC,CAAC,KACtD,CAAC,KAAK,WAAW,CAChB,KACA,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAC/D,uBAAuB,KAAK,IAC5B,KACN,CACF,CACF;CAGF,IACE,OAAO,SACP,OAAO,OAAO,UAAU,YACxB,CAAC,MAAM,QAAQ,OAAO,KAAK,GAE3B,OAAO,QAAQ,uBAAuB,OAAO,KAAK;CAGpD,KAAK,MAAM,WAAW;EAAC;EAAS;EAAS;CAAO,GAAY;EAC1D,MAAM,SAAS,OAAO;EACtB,IAAI,MAAM,QAAQ,MAAM,GACtB,OAAO,WAAW,OAAO,KAAK,UAC5B,OAAO,UAAU,YAAY,UAAU,OACnC,uBAAuB,KAA4B,IACnD,KACN;CAEJ;CAEA,IACE,OAAO,wBACP,OAAO,OAAO,yBAAyB,YACvC,CAAC,MAAM,QAAQ,OAAO,oBAAoB,GAE1C,OAAO,uBAAuB,uBAC5B,OAAO,oBACT;CAGF,OAAO;AACT;;;;;;;;;;;;;;;;;AAkBA,SAAgB,mCACd,QACA,mBAAkC,CAAC,GACd;CAQrB,OAAO,oBAHQ,+BAFI,uBAAuB,MAEI,GAAY,gBAG/B,CAAM;AACnC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-groq",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "Groq adapter for TanStack AI low-latency chat, tool calling, and structured outputs.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -49,23 +49,23 @@
49
49
  ],
50
50
  "devDependencies": {
51
51
  "@vitest/coverage-v8": "4.0.14",
52
- "vite": "^7.3.3"
52
+ "vite": "^8.1.4"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "zod": "^4.0.0",
56
- "@tanstack/ai": "^0.42.0"
56
+ "@tanstack/ai": "^0.44.0"
57
57
  },
58
58
  "dependencies": {
59
59
  "openai": "^6.41.0",
60
- "@tanstack/ai-utils": "0.3.1",
61
- "@tanstack/openai-base": "0.9.9"
60
+ "@tanstack/openai-base": "^0.9.11",
61
+ "@tanstack/ai-utils": "^0.4.0"
62
62
  },
63
63
  "scripts": {
64
64
  "build": "vite build",
65
65
  "clean": "premove ./build ./dist",
66
- "lint:fix": "eslint ./src --fix",
66
+ "lint:fix": "oxlint src --type-aware --fix",
67
67
  "test:build": "publint --strict",
68
- "test:eslint": "eslint ./src",
68
+ "test:oxlint": "oxlint src --type-aware",
69
69
  "test:lib": "vitest run",
70
70
  "test:lib:dev": "pnpm test:lib --watch",
71
71
  "test:types": "tsc"
@@ -0,0 +1,77 @@
1
+ import { ChatStreamSummarizeAdapter } from '@tanstack/ai/adapters'
2
+ import { getGroqApiKeyFromEnv } from '../utils/client'
3
+ import { GroqTextAdapter } from './text'
4
+ import type { InferTextProviderOptions } from '@tanstack/ai/adapters'
5
+ import type { GROQ_CHAT_MODELS } from '../model-meta'
6
+ import type { GroqClientConfig } from '../utils/client'
7
+
8
+ /**
9
+ * Configuration for Groq summarize adapter
10
+ */
11
+ export interface GroqSummarizeConfig extends GroqClientConfig {}
12
+
13
+ /** Model type for Groq summarization */
14
+ export type GroqSummarizeModel = (typeof GROQ_CHAT_MODELS)[number]
15
+
16
+ /**
17
+ * Creates a Groq summarize adapter with explicit API key.
18
+ * Type resolution happens here at the call site.
19
+ *
20
+ * @param model - The model name (e.g., 'llama-3.3-70b-versatile')
21
+ * @param apiKey - Your Groq API key
22
+ * @param config - Optional additional configuration
23
+ * @returns Configured Groq summarize adapter instance with resolved types
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const adapter = createGroqSummarize('llama-3.3-70b-versatile', "gsk_...");
28
+ * ```
29
+ */
30
+ export function createGroqSummarize<TModel extends GroqSummarizeModel>(
31
+ model: TModel,
32
+ apiKey: string,
33
+ config?: Omit<GroqSummarizeConfig, 'apiKey'>,
34
+ ): ChatStreamSummarizeAdapter<
35
+ TModel,
36
+ InferTextProviderOptions<GroqTextAdapter<TModel>>
37
+ > {
38
+ return new ChatStreamSummarizeAdapter(
39
+ new GroqTextAdapter({ apiKey, ...config }, model),
40
+ model,
41
+ 'groq',
42
+ )
43
+ }
44
+
45
+ /**
46
+ * Creates a Groq summarize adapter with automatic API key detection from environment variables.
47
+ * Type resolution happens here at the call site.
48
+ *
49
+ * Looks for `GROQ_API_KEY` in:
50
+ * - `process.env` (Node.js)
51
+ * - `window.env` (Browser with injected env)
52
+ *
53
+ * @param model - The model name (e.g., 'llama-3.3-70b-versatile')
54
+ * @param config - Optional configuration (excluding apiKey which is auto-detected)
55
+ * @returns Configured Groq summarize adapter instance with resolved types
56
+ * @throws Error if GROQ_API_KEY is not found in environment
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // Automatically uses GROQ_API_KEY from environment
61
+ * const adapter = groqSummarize('llama-3.3-70b-versatile');
62
+ *
63
+ * await summarize({
64
+ * adapter,
65
+ * text: "Long article text..."
66
+ * });
67
+ * ```
68
+ */
69
+ export function groqSummarize<TModel extends GroqSummarizeModel>(
70
+ model: TModel,
71
+ config?: Omit<GroqSummarizeConfig, 'apiKey'>,
72
+ ): ChatStreamSummarizeAdapter<
73
+ TModel,
74
+ InferTextProviderOptions<GroqTextAdapter<TModel>>
75
+ > {
76
+ return createGroqSummarize(model, getGroqApiKeyFromEnv(), config)
77
+ }
package/src/index.ts CHANGED
@@ -14,6 +14,14 @@ export {
14
14
  type GroqTextProviderOptions,
15
15
  } from './adapters/text'
16
16
 
17
+ // Summarize - thin factory functions over @tanstack/ai's ChatStreamSummarizeAdapter
18
+ export {
19
+ createGroqSummarize,
20
+ groqSummarize,
21
+ type GroqSummarizeConfig,
22
+ type GroqSummarizeModel,
23
+ } from './adapters/summarize'
24
+
17
25
  // Transcription adapter
18
26
  export {
19
27
  GroqTranscriptionAdapter,
@@ -1,7 +1,4 @@
1
1
  import { makeStructuredOutputCompatible } from '@tanstack/openai-base'
2
- import { transformNullsToUndefined } from '@tanstack/ai-utils'
3
-
4
- export { transformNullsToUndefined }
5
2
 
6
3
  /**
7
4
  * Recursively removes `required: []` from a schema object.
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}