@tanstack/ai-grok 0.1.0 → 0.3.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.
|
@@ -11,7 +11,7 @@ class GrokSummarizeAdapter extends BaseSummarizeAdapter {
|
|
|
11
11
|
async summarize(options) {
|
|
12
12
|
const systemPrompt = this.buildSummarizationPrompt(options);
|
|
13
13
|
let summary = "";
|
|
14
|
-
|
|
14
|
+
const id = "";
|
|
15
15
|
let model = options.model;
|
|
16
16
|
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
17
17
|
for await (const chunk of this.textAdapter.chatStream({
|
|
@@ -21,13 +21,18 @@ class GrokSummarizeAdapter extends BaseSummarizeAdapter {
|
|
|
21
21
|
maxTokens: options.maxLength,
|
|
22
22
|
temperature: 0.3
|
|
23
23
|
})) {
|
|
24
|
-
if (chunk.type === "
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
if (chunk.type === "TEXT_MESSAGE_CONTENT") {
|
|
25
|
+
if (chunk.content) {
|
|
26
|
+
summary = chunk.content;
|
|
27
|
+
} else {
|
|
28
|
+
summary += chunk.delta;
|
|
29
|
+
}
|
|
30
|
+
model = chunk.model || model;
|
|
28
31
|
}
|
|
29
|
-
if (chunk.type === "
|
|
30
|
-
|
|
32
|
+
if (chunk.type === "RUN_FINISHED") {
|
|
33
|
+
if (chunk.usage) {
|
|
34
|
+
usage = chunk.usage;
|
|
35
|
+
}
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
38
|
return { id, model, summary, usage };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summarize.js","sources":["../../../src/adapters/summarize.ts"],"sourcesContent":["import { BaseSummarizeAdapter } from '@tanstack/ai/adapters'\nimport { getGrokApiKeyFromEnv } from '../utils'\nimport { GrokTextAdapter } from './text'\nimport type { GROK_CHAT_MODELS } from '../model-meta'\nimport type {\n StreamChunk,\n SummarizationOptions,\n SummarizationResult,\n} from '@tanstack/ai'\nimport type { GrokClientConfig } from '../utils'\n\n/**\n * Configuration for Grok summarize adapter\n */\nexport interface GrokSummarizeConfig extends GrokClientConfig {}\n\n/**\n * Grok-specific provider options for summarization\n */\nexport interface GrokSummarizeProviderOptions {\n /** Temperature for response generation (0-2) */\n temperature?: number\n /** Maximum tokens in the response */\n maxTokens?: number\n}\n\n/** Model type for Grok summarization */\nexport type GrokSummarizeModel = (typeof GROK_CHAT_MODELS)[number]\n\n/**\n * Grok Summarize Adapter\n *\n * A thin wrapper around the text adapter that adds summarization-specific prompting.\n * Delegates all API calls to the GrokTextAdapter.\n */\nexport class GrokSummarizeAdapter<\n TModel extends GrokSummarizeModel,\n> extends BaseSummarizeAdapter<TModel, GrokSummarizeProviderOptions> {\n readonly kind = 'summarize' as const\n readonly name = 'grok' as const\n\n private textAdapter: GrokTextAdapter<TModel>\n\n constructor(config: GrokSummarizeConfig, model: TModel) {\n super({}, model)\n this.textAdapter = new GrokTextAdapter(config, model)\n }\n\n async summarize(options: SummarizationOptions): Promise<SummarizationResult> {\n const systemPrompt = this.buildSummarizationPrompt(options)\n\n // Use the text adapter's streaming and collect the result\n let summary = ''\n
|
|
1
|
+
{"version":3,"file":"summarize.js","sources":["../../../src/adapters/summarize.ts"],"sourcesContent":["import { BaseSummarizeAdapter } from '@tanstack/ai/adapters'\nimport { getGrokApiKeyFromEnv } from '../utils'\nimport { GrokTextAdapter } from './text'\nimport type { GROK_CHAT_MODELS } from '../model-meta'\nimport type {\n StreamChunk,\n SummarizationOptions,\n SummarizationResult,\n} from '@tanstack/ai'\nimport type { GrokClientConfig } from '../utils'\n\n/**\n * Configuration for Grok summarize adapter\n */\nexport interface GrokSummarizeConfig extends GrokClientConfig {}\n\n/**\n * Grok-specific provider options for summarization\n */\nexport interface GrokSummarizeProviderOptions {\n /** Temperature for response generation (0-2) */\n temperature?: number\n /** Maximum tokens in the response */\n maxTokens?: number\n}\n\n/** Model type for Grok summarization */\nexport type GrokSummarizeModel = (typeof GROK_CHAT_MODELS)[number]\n\n/**\n * Grok Summarize Adapter\n *\n * A thin wrapper around the text adapter that adds summarization-specific prompting.\n * Delegates all API calls to the GrokTextAdapter.\n */\nexport class GrokSummarizeAdapter<\n TModel extends GrokSummarizeModel,\n> extends BaseSummarizeAdapter<TModel, GrokSummarizeProviderOptions> {\n readonly kind = 'summarize' as const\n readonly name = 'grok' as const\n\n private textAdapter: GrokTextAdapter<TModel>\n\n constructor(config: GrokSummarizeConfig, model: TModel) {\n super({}, model)\n this.textAdapter = new GrokTextAdapter(config, model)\n }\n\n async summarize(options: SummarizationOptions): Promise<SummarizationResult> {\n const systemPrompt = this.buildSummarizationPrompt(options)\n\n // Use the text adapter's streaming and collect the result\n let summary = ''\n const id = ''\n let model = options.model\n let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 }\n\n for await (const chunk of this.textAdapter.chatStream({\n model: options.model,\n messages: [{ role: 'user', content: options.text }],\n systemPrompts: [systemPrompt],\n maxTokens: options.maxLength,\n temperature: 0.3,\n })) {\n // AG-UI TEXT_MESSAGE_CONTENT event\n if (chunk.type === 'TEXT_MESSAGE_CONTENT') {\n if (chunk.content) {\n summary = chunk.content\n } else {\n summary += chunk.delta\n }\n model = chunk.model || model\n }\n // AG-UI RUN_FINISHED event\n if (chunk.type === 'RUN_FINISHED') {\n if (chunk.usage) {\n usage = chunk.usage\n }\n }\n }\n\n return { id, model, summary, usage }\n }\n\n async *summarizeStream(\n options: SummarizationOptions,\n ): AsyncIterable<StreamChunk> {\n const systemPrompt = this.buildSummarizationPrompt(options)\n\n // Delegate directly to the text adapter's streaming\n yield* this.textAdapter.chatStream({\n model: options.model,\n messages: [{ role: 'user', content: options.text }],\n systemPrompts: [systemPrompt],\n maxTokens: options.maxLength,\n temperature: 0.3,\n })\n }\n\n private buildSummarizationPrompt(options: SummarizationOptions): string {\n let prompt = 'You are a professional summarizer. '\n\n switch (options.style) {\n case 'bullet-points':\n prompt += 'Provide a summary in bullet point format. '\n break\n case 'paragraph':\n prompt += 'Provide a summary in paragraph format. '\n break\n case 'concise':\n prompt += 'Provide a very concise summary in 1-2 sentences. '\n break\n default:\n prompt += 'Provide a clear and concise summary. '\n }\n\n if (options.focus && options.focus.length > 0) {\n prompt += `Focus on the following aspects: ${options.focus.join(', ')}. `\n }\n\n if (options.maxLength) {\n prompt += `Keep the summary under ${options.maxLength} tokens. `\n }\n\n return prompt\n }\n}\n\n/**\n * Creates a Grok summarize adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'grok-3', 'grok-4')\n * @param apiKey - Your xAI API key\n * @param config - Optional additional configuration\n * @returns Configured Grok summarize adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGrokSummarize('grok-3', \"xai-...\");\n * ```\n */\nexport function createGrokSummarize<TModel extends GrokSummarizeModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<GrokSummarizeConfig, 'apiKey'>,\n): GrokSummarizeAdapter<TModel> {\n return new GrokSummarizeAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Grok summarize adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `XAI_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'grok-3', 'grok-4')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Grok summarize adapter instance with resolved types\n * @throws Error if XAI_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses XAI_API_KEY from environment\n * const adapter = grokSummarize('grok-3');\n *\n * await summarize({\n * adapter,\n * text: \"Long article text...\"\n * });\n * ```\n */\nexport function grokSummarize<TModel extends GrokSummarizeModel>(\n model: TModel,\n config?: Omit<GrokSummarizeConfig, 'apiKey'>,\n): GrokSummarizeAdapter<TModel> {\n const apiKey = getGrokApiKeyFromEnv()\n return createGrokSummarize(model, apiKey, config)\n}\n"],"names":[],"mappings":";;;AAmCO,MAAM,6BAEH,qBAA2D;AAAA,EAMnE,YAAY,QAA6B,OAAe;AACtD,UAAM,CAAA,GAAI,KAAK;AANjB,SAAS,OAAO;AAChB,SAAS,OAAO;AAMd,SAAK,cAAc,IAAI,gBAAgB,QAAQ,KAAK;AAAA,EACtD;AAAA,EAEA,MAAM,UAAU,SAA6D;AAC3E,UAAM,eAAe,KAAK,yBAAyB,OAAO;AAG1D,QAAI,UAAU;AACd,UAAM,KAAK;AACX,QAAI,QAAQ,QAAQ;AACpB,QAAI,QAAQ,EAAE,cAAc,GAAG,kBAAkB,GAAG,aAAa,EAAA;AAEjE,qBAAiB,SAAS,KAAK,YAAY,WAAW;AAAA,MACpD,OAAO,QAAQ;AAAA,MACf,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,QAAQ,MAAM;AAAA,MAClD,eAAe,CAAC,YAAY;AAAA,MAC5B,WAAW,QAAQ;AAAA,MACnB,aAAa;AAAA,IAAA,CACd,GAAG;AAEF,UAAI,MAAM,SAAS,wBAAwB;AACzC,YAAI,MAAM,SAAS;AACjB,oBAAU,MAAM;AAAA,QAClB,OAAO;AACL,qBAAW,MAAM;AAAA,QACnB;AACA,gBAAQ,MAAM,SAAS;AAAA,MACzB;AAEA,UAAI,MAAM,SAAS,gBAAgB;AACjC,YAAI,MAAM,OAAO;AACf,kBAAQ,MAAM;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAEA,WAAO,EAAE,IAAI,OAAO,SAAS,MAAA;AAAA,EAC/B;AAAA,EAEA,OAAO,gBACL,SAC4B;AAC5B,UAAM,eAAe,KAAK,yBAAyB,OAAO;AAG1D,WAAO,KAAK,YAAY,WAAW;AAAA,MACjC,OAAO,QAAQ;AAAA,MACf,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,QAAQ,MAAM;AAAA,MAClD,eAAe,CAAC,YAAY;AAAA,MAC5B,WAAW,QAAQ;AAAA,MACnB,aAAa;AAAA,IAAA,CACd;AAAA,EACH;AAAA,EAEQ,yBAAyB,SAAuC;AACtE,QAAI,SAAS;AAEb,YAAQ,QAAQ,OAAA;AAAA,MACd,KAAK;AACH,kBAAU;AACV;AAAA,MACF,KAAK;AACH,kBAAU;AACV;AAAA,MACF,KAAK;AACH,kBAAU;AACV;AAAA,MACF;AACE,kBAAU;AAAA,IAAA;AAGd,QAAI,QAAQ,SAAS,QAAQ,MAAM,SAAS,GAAG;AAC7C,gBAAU,mCAAmC,QAAQ,MAAM,KAAK,IAAI,CAAC;AAAA,IACvE;AAEA,QAAI,QAAQ,WAAW;AACrB,gBAAU,0BAA0B,QAAQ,SAAS;AAAA,IACvD;AAEA,WAAO;AAAA,EACT;AACF;AAgBO,SAAS,oBACd,OACA,QACA,QAC8B;AAC9B,SAAO,IAAI,qBAAqB,EAAE,QAAQ,GAAG,OAAA,GAAU,KAAK;AAC9D;AA0BO,SAAS,cACd,OACA,QAC8B;AAC9B,QAAM,SAAS,qBAAA;AACf,SAAO,oBAAoB,OAAO,QAAQ,MAAM;AAClD;"}
|
|
@@ -12,19 +12,44 @@ class GrokTextAdapter extends BaseTextAdapter {
|
|
|
12
12
|
}
|
|
13
13
|
async *chatStream(options) {
|
|
14
14
|
const requestParams = this.mapTextOptionsToGrok(options);
|
|
15
|
+
const timestamp = Date.now();
|
|
16
|
+
const aguiState = {
|
|
17
|
+
runId: generateId(this.name),
|
|
18
|
+
messageId: generateId(this.name),
|
|
19
|
+
timestamp,
|
|
20
|
+
hasEmittedRunStarted: false
|
|
21
|
+
};
|
|
15
22
|
try {
|
|
16
23
|
const stream = await this.client.chat.completions.create({
|
|
17
24
|
...requestParams,
|
|
18
25
|
stream: true
|
|
19
26
|
});
|
|
20
|
-
yield* this.processGrokStreamChunks(stream, options);
|
|
27
|
+
yield* this.processGrokStreamChunks(stream, options, aguiState);
|
|
21
28
|
} catch (error) {
|
|
22
29
|
const err = error;
|
|
30
|
+
if (!aguiState.hasEmittedRunStarted) {
|
|
31
|
+
aguiState.hasEmittedRunStarted = true;
|
|
32
|
+
yield {
|
|
33
|
+
type: "RUN_STARTED",
|
|
34
|
+
runId: aguiState.runId,
|
|
35
|
+
model: options.model,
|
|
36
|
+
timestamp
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
yield {
|
|
40
|
+
type: "RUN_ERROR",
|
|
41
|
+
runId: aguiState.runId,
|
|
42
|
+
model: options.model,
|
|
43
|
+
timestamp,
|
|
44
|
+
error: {
|
|
45
|
+
message: err.message || "Unknown error",
|
|
46
|
+
code: err.code
|
|
47
|
+
}
|
|
48
|
+
};
|
|
23
49
|
console.error(">>> chatStream: Fatal error during response creation <<<");
|
|
24
50
|
console.error(">>> Error message:", err.message);
|
|
25
51
|
console.error(">>> Error stack:", err.stack);
|
|
26
52
|
console.error(">>> Full error:", err);
|
|
27
|
-
throw error;
|
|
28
53
|
}
|
|
29
54
|
}
|
|
30
55
|
/**
|
|
@@ -80,29 +105,46 @@ class GrokTextAdapter extends BaseTextAdapter {
|
|
|
80
105
|
throw error;
|
|
81
106
|
}
|
|
82
107
|
}
|
|
83
|
-
async *processGrokStreamChunks(stream, options) {
|
|
108
|
+
async *processGrokStreamChunks(stream, options, aguiState) {
|
|
84
109
|
let accumulatedContent = "";
|
|
85
|
-
const timestamp =
|
|
86
|
-
let
|
|
110
|
+
const timestamp = aguiState.timestamp;
|
|
111
|
+
let hasEmittedTextMessageStart = false;
|
|
87
112
|
const toolCallsInProgress = /* @__PURE__ */ new Map();
|
|
88
113
|
try {
|
|
89
114
|
for await (const chunk of stream) {
|
|
90
|
-
responseId = chunk.id || responseId;
|
|
91
115
|
const choice = chunk.choices[0];
|
|
92
116
|
if (!choice) continue;
|
|
117
|
+
if (!aguiState.hasEmittedRunStarted) {
|
|
118
|
+
aguiState.hasEmittedRunStarted = true;
|
|
119
|
+
yield {
|
|
120
|
+
type: "RUN_STARTED",
|
|
121
|
+
runId: aguiState.runId,
|
|
122
|
+
model: chunk.model || options.model,
|
|
123
|
+
timestamp
|
|
124
|
+
};
|
|
125
|
+
}
|
|
93
126
|
const delta = choice.delta;
|
|
94
127
|
const deltaContent = delta.content;
|
|
95
128
|
const deltaToolCalls = delta.tool_calls;
|
|
96
129
|
if (deltaContent) {
|
|
130
|
+
if (!hasEmittedTextMessageStart) {
|
|
131
|
+
hasEmittedTextMessageStart = true;
|
|
132
|
+
yield {
|
|
133
|
+
type: "TEXT_MESSAGE_START",
|
|
134
|
+
messageId: aguiState.messageId,
|
|
135
|
+
model: chunk.model || options.model,
|
|
136
|
+
timestamp,
|
|
137
|
+
role: "assistant"
|
|
138
|
+
};
|
|
139
|
+
}
|
|
97
140
|
accumulatedContent += deltaContent;
|
|
98
141
|
yield {
|
|
99
|
-
type: "
|
|
100
|
-
|
|
142
|
+
type: "TEXT_MESSAGE_CONTENT",
|
|
143
|
+
messageId: aguiState.messageId,
|
|
101
144
|
model: chunk.model || options.model,
|
|
102
145
|
timestamp,
|
|
103
146
|
delta: deltaContent,
|
|
104
|
-
content: accumulatedContent
|
|
105
|
-
role: "assistant"
|
|
147
|
+
content: accumulatedContent
|
|
106
148
|
};
|
|
107
149
|
}
|
|
108
150
|
if (deltaToolCalls) {
|
|
@@ -112,7 +154,8 @@ class GrokTextAdapter extends BaseTextAdapter {
|
|
|
112
154
|
toolCallsInProgress.set(index, {
|
|
113
155
|
id: toolCallDelta.id || "",
|
|
114
156
|
name: toolCallDelta.function?.name || "",
|
|
115
|
-
arguments: ""
|
|
157
|
+
arguments: "",
|
|
158
|
+
started: false
|
|
116
159
|
});
|
|
117
160
|
}
|
|
118
161
|
const toolCall = toolCallsInProgress.get(index);
|
|
@@ -125,31 +168,59 @@ class GrokTextAdapter extends BaseTextAdapter {
|
|
|
125
168
|
if (toolCallDelta.function?.arguments) {
|
|
126
169
|
toolCall.arguments += toolCallDelta.function.arguments;
|
|
127
170
|
}
|
|
171
|
+
if (toolCall.id && toolCall.name && !toolCall.started) {
|
|
172
|
+
toolCall.started = true;
|
|
173
|
+
yield {
|
|
174
|
+
type: "TOOL_CALL_START",
|
|
175
|
+
toolCallId: toolCall.id,
|
|
176
|
+
toolName: toolCall.name,
|
|
177
|
+
model: chunk.model || options.model,
|
|
178
|
+
timestamp,
|
|
179
|
+
index
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (toolCallDelta.function?.arguments && toolCall.started) {
|
|
183
|
+
yield {
|
|
184
|
+
type: "TOOL_CALL_ARGS",
|
|
185
|
+
toolCallId: toolCall.id,
|
|
186
|
+
model: chunk.model || options.model,
|
|
187
|
+
timestamp,
|
|
188
|
+
delta: toolCallDelta.function.arguments
|
|
189
|
+
};
|
|
190
|
+
}
|
|
128
191
|
}
|
|
129
192
|
}
|
|
130
193
|
if (choice.finish_reason) {
|
|
131
194
|
if (choice.finish_reason === "tool_calls" || toolCallsInProgress.size > 0) {
|
|
132
|
-
for (const [
|
|
195
|
+
for (const [, toolCall] of toolCallsInProgress) {
|
|
196
|
+
let parsedInput = {};
|
|
197
|
+
try {
|
|
198
|
+
parsedInput = toolCall.arguments ? JSON.parse(toolCall.arguments) : {};
|
|
199
|
+
} catch {
|
|
200
|
+
parsedInput = {};
|
|
201
|
+
}
|
|
133
202
|
yield {
|
|
134
|
-
type: "
|
|
135
|
-
|
|
203
|
+
type: "TOOL_CALL_END",
|
|
204
|
+
toolCallId: toolCall.id,
|
|
205
|
+
toolName: toolCall.name,
|
|
136
206
|
model: chunk.model || options.model,
|
|
137
207
|
timestamp,
|
|
138
|
-
|
|
139
|
-
toolCall: {
|
|
140
|
-
id: toolCall.id,
|
|
141
|
-
type: "function",
|
|
142
|
-
function: {
|
|
143
|
-
name: toolCall.name,
|
|
144
|
-
arguments: toolCall.arguments
|
|
145
|
-
}
|
|
146
|
-
}
|
|
208
|
+
input: parsedInput
|
|
147
209
|
};
|
|
148
210
|
}
|
|
149
211
|
}
|
|
212
|
+
const computedFinishReason = choice.finish_reason === "tool_calls" || toolCallsInProgress.size > 0 ? "tool_calls" : "stop";
|
|
213
|
+
if (hasEmittedTextMessageStart) {
|
|
214
|
+
yield {
|
|
215
|
+
type: "TEXT_MESSAGE_END",
|
|
216
|
+
messageId: aguiState.messageId,
|
|
217
|
+
model: chunk.model || options.model,
|
|
218
|
+
timestamp
|
|
219
|
+
};
|
|
220
|
+
}
|
|
150
221
|
yield {
|
|
151
|
-
type: "
|
|
152
|
-
|
|
222
|
+
type: "RUN_FINISHED",
|
|
223
|
+
runId: aguiState.runId,
|
|
153
224
|
model: chunk.model || options.model,
|
|
154
225
|
timestamp,
|
|
155
226
|
usage: chunk.usage ? {
|
|
@@ -157,7 +228,7 @@ class GrokTextAdapter extends BaseTextAdapter {
|
|
|
157
228
|
completionTokens: chunk.usage.completion_tokens || 0,
|
|
158
229
|
totalTokens: chunk.usage.total_tokens || 0
|
|
159
230
|
} : void 0,
|
|
160
|
-
finishReason:
|
|
231
|
+
finishReason: computedFinishReason
|
|
161
232
|
};
|
|
162
233
|
}
|
|
163
234
|
}
|
|
@@ -165,8 +236,8 @@ class GrokTextAdapter extends BaseTextAdapter {
|
|
|
165
236
|
const err = error;
|
|
166
237
|
console.log("[Grok Adapter] Stream ended with error:", err.message);
|
|
167
238
|
yield {
|
|
168
|
-
type: "
|
|
169
|
-
|
|
239
|
+
type: "RUN_ERROR",
|
|
240
|
+
runId: aguiState.runId,
|
|
170
241
|
model: options.model,
|
|
171
242
|
timestamp,
|
|
172
243
|
error: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","sources":["../../../src/adapters/text.ts"],"sourcesContent":["import { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { validateTextProviderOptions } from '../text/text-provider-options'\nimport { convertToolsToProviderFormat } from '../tools'\nimport {\n createGrokClient,\n generateId,\n getGrokApiKeyFromEnv,\n makeGrokStructuredOutputCompatible,\n transformNullsToUndefined,\n} from '../utils'\nimport type {\n GROK_CHAT_MODELS,\n ResolveInputModalities,\n ResolveProviderOptions,\n} from '../model-meta'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type OpenAI_SDK from 'openai'\nimport type {\n ContentPart,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { InternalTextProviderOptions } from '../text/text-provider-options'\nimport type {\n GrokImageMetadata,\n GrokMessageMetadataByModality,\n} from '../message-types'\nimport type { GrokClientConfig } from '../utils'\n\n/**\n * Configuration for Grok text adapter\n */\nexport interface GrokTextConfig extends GrokClientConfig {}\n\n/**\n * Alias for TextProviderOptions for external use\n */\nexport type { ExternalTextProviderOptions as GrokTextProviderOptions } from '../text/text-provider-options'\n\n/**\n * Grok Text (Chat) Adapter\n *\n * Tree-shakeable adapter for Grok chat/text completion functionality.\n * Uses OpenAI-compatible Chat Completions API (not Responses API).\n */\nexport class GrokTextAdapter<\n TModel extends (typeof GROK_CHAT_MODELS)[number],\n> extends BaseTextAdapter<\n TModel,\n ResolveProviderOptions<TModel>,\n ResolveInputModalities<TModel>,\n GrokMessageMetadataByModality\n> {\n readonly kind = 'text' as const\n readonly name = 'grok' as const\n\n private client: OpenAI_SDK\n\n constructor(config: GrokTextConfig, model: TModel) {\n super({}, model)\n this.client = createGrokClient(config)\n }\n\n async *chatStream(\n options: TextOptions<ResolveProviderOptions<TModel>>,\n ): AsyncIterable<StreamChunk> {\n const requestParams = this.mapTextOptionsToGrok(options)\n\n try {\n const stream = await this.client.chat.completions.create({\n ...requestParams,\n stream: true,\n })\n\n yield* this.processGrokStreamChunks(stream, options)\n } catch (error: unknown) {\n const err = error as Error\n console.error('>>> chatStream: Fatal error during response creation <<<')\n console.error('>>> Error message:', err.message)\n console.error('>>> Error stack:', err.stack)\n console.error('>>> Full error:', err)\n throw error\n }\n }\n\n /**\n * Generate structured output using Grok's JSON Schema response format.\n * Uses stream: false to get the complete response in one call.\n *\n * Grok has strict requirements for structured output (via OpenAI-compatible API):\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for all objects\n *\n * The outputSchema is already JSON Schema (converted in the ai layer).\n * We apply Grok-specific transformations for structured output compatibility.\n */\n async structuredOutput(\n options: StructuredOutputOptions<ResolveProviderOptions<TModel>>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const requestParams = this.mapTextOptionsToGrok(chatOptions)\n\n // Apply Grok-specific transformations for structured output compatibility\n const jsonSchema = makeGrokStructuredOutputCompatible(\n outputSchema,\n outputSchema.required || [],\n )\n\n try {\n const response = await this.client.chat.completions.create({\n ...requestParams,\n stream: false,\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'structured_output',\n schema: jsonSchema,\n strict: true,\n },\n },\n })\n\n // Extract text content from the response\n const rawText = response.choices[0]?.message.content || ''\n\n // Parse the JSON response\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n // Transform null values to undefined to match original Zod schema expectations\n // Grok returns null for optional fields we made nullable in the schema\n const transformed = transformNullsToUndefined(parsed)\n\n return {\n data: transformed,\n rawText,\n }\n } catch (error: unknown) {\n const err = error as Error\n console.error('>>> structuredOutput: Error during response creation <<<')\n console.error('>>> Error message:', err.message)\n throw error\n }\n }\n\n private async *processGrokStreamChunks(\n stream: AsyncIterable<OpenAI_SDK.Chat.Completions.ChatCompletionChunk>,\n options: TextOptions,\n ): AsyncIterable<StreamChunk> {\n let accumulatedContent = ''\n const timestamp = Date.now()\n let responseId = generateId(this.name)\n\n // Track tool calls being streamed (arguments come in chunks)\n const toolCallsInProgress = new Map<\n number,\n {\n id: string\n name: string\n arguments: string\n }\n >()\n\n try {\n for await (const chunk of stream) {\n responseId = chunk.id || responseId\n const choice = chunk.choices[0]\n\n if (!choice) continue\n\n const delta = choice.delta\n const deltaContent = delta.content\n const deltaToolCalls = delta.tool_calls\n\n // Handle content delta\n if (deltaContent) {\n accumulatedContent += deltaContent\n yield {\n type: 'content',\n id: responseId,\n model: chunk.model || options.model,\n timestamp,\n delta: deltaContent,\n content: accumulatedContent,\n role: 'assistant',\n }\n }\n\n // Handle tool calls - they come in as deltas\n if (deltaToolCalls) {\n for (const toolCallDelta of deltaToolCalls) {\n const index = toolCallDelta.index\n\n // Initialize or update the tool call in progress\n if (!toolCallsInProgress.has(index)) {\n toolCallsInProgress.set(index, {\n id: toolCallDelta.id || '',\n name: toolCallDelta.function?.name || '',\n arguments: '',\n })\n }\n\n const toolCall = toolCallsInProgress.get(index)!\n\n // Update with any new data from the delta\n if (toolCallDelta.id) {\n toolCall.id = toolCallDelta.id\n }\n if (toolCallDelta.function?.name) {\n toolCall.name = toolCallDelta.function.name\n }\n if (toolCallDelta.function?.arguments) {\n toolCall.arguments += toolCallDelta.function.arguments\n }\n }\n }\n\n // Handle finish reason\n if (choice.finish_reason) {\n // Emit all completed tool calls\n if (\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ) {\n for (const [index, toolCall] of toolCallsInProgress) {\n yield {\n type: 'tool_call',\n id: responseId,\n model: chunk.model || options.model,\n timestamp,\n index,\n toolCall: {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.name,\n arguments: toolCall.arguments,\n },\n },\n }\n }\n }\n\n yield {\n type: 'done',\n id: responseId,\n model: chunk.model || options.model,\n timestamp,\n usage: chunk.usage\n ? {\n promptTokens: chunk.usage.prompt_tokens || 0,\n completionTokens: chunk.usage.completion_tokens || 0,\n totalTokens: chunk.usage.total_tokens || 0,\n }\n : undefined,\n finishReason:\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ? 'tool_calls'\n : 'stop',\n }\n }\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n console.log('[Grok Adapter] Stream ended with error:', err.message)\n yield {\n type: 'error',\n id: responseId,\n model: options.model,\n timestamp,\n error: {\n message: err.message || 'Unknown error occurred',\n code: err.code,\n },\n }\n }\n }\n\n /**\n * Maps common options to Grok-specific Chat Completions format\n */\n private mapTextOptionsToGrok(\n options: TextOptions,\n ): OpenAI_SDK.Chat.Completions.ChatCompletionCreateParamsStreaming {\n const modelOptions = options.modelOptions as\n | Omit<\n InternalTextProviderOptions,\n 'max_tokens' | 'tools' | 'temperature' | 'input' | 'top_p'\n >\n | undefined\n\n if (modelOptions) {\n validateTextProviderOptions({\n ...modelOptions,\n model: options.model,\n })\n }\n\n const tools = options.tools\n ? convertToolsToProviderFormat(options.tools)\n : undefined\n\n // Build messages array with system prompts\n const messages: Array<OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam> =\n []\n\n // Add system prompts first\n if (options.systemPrompts && options.systemPrompts.length > 0) {\n messages.push({\n role: 'system',\n content: options.systemPrompts.join('\\n'),\n })\n }\n\n // Convert messages\n for (const message of options.messages) {\n messages.push(this.convertMessageToGrok(message))\n }\n\n return {\n model: options.model,\n messages,\n temperature: options.temperature,\n max_tokens: options.maxTokens,\n top_p: options.topP,\n tools: tools as Array<OpenAI_SDK.Chat.Completions.ChatCompletionTool>,\n stream: true,\n stream_options: { include_usage: true },\n }\n }\n\n private convertMessageToGrok(\n message: ModelMessage,\n ): OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam {\n // Handle tool messages\n if (message.role === 'tool') {\n return {\n role: 'tool',\n tool_call_id: message.toolCallId || '',\n content:\n typeof message.content === 'string'\n ? message.content\n : JSON.stringify(message.content),\n }\n }\n\n // Handle assistant messages\n if (message.role === 'assistant') {\n const toolCalls = message.toolCalls?.map((tc) => ({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === 'string'\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }))\n\n return {\n role: 'assistant',\n content: this.extractTextContent(message.content),\n ...(toolCalls && toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),\n }\n }\n\n // Handle user messages - support multimodal content\n const contentParts = this.normalizeContent(message.content)\n\n // If only text, use simple string format\n if (contentParts.length === 1 && contentParts[0]?.type === 'text') {\n return {\n role: 'user',\n content: contentParts[0].content,\n }\n }\n\n // Otherwise, use array format for multimodal\n const parts: Array<OpenAI_SDK.Chat.Completions.ChatCompletionContentPart> =\n []\n for (const part of contentParts) {\n if (part.type === 'text') {\n parts.push({ type: 'text', text: part.content })\n } else if (part.type === 'image') {\n const imageMetadata = part.metadata as GrokImageMetadata | undefined\n parts.push({\n type: 'image_url',\n image_url: {\n url: part.source.value,\n detail: imageMetadata?.detail || 'auto',\n },\n })\n }\n }\n\n return {\n role: 'user',\n content: parts.length > 0 ? parts : '',\n }\n }\n\n /**\n * Normalizes message content to an array of ContentPart.\n * Handles backward compatibility with string content.\n */\n private normalizeContent(\n content: string | null | Array<ContentPart>,\n ): Array<ContentPart> {\n if (content === null) {\n return []\n }\n if (typeof content === 'string') {\n return [{ type: 'text', content: content }]\n }\n return content\n }\n\n /**\n * Extracts text content from a content value that may be string, null, or ContentPart array.\n */\n private extractTextContent(\n content: string | null | Array<ContentPart>,\n ): string {\n if (content === null) {\n return ''\n }\n if (typeof content === 'string') {\n return content\n }\n // It's an array of ContentPart\n return content\n .filter((p) => p.type === 'text')\n .map((p) => p.content)\n .join('')\n }\n}\n\n/**\n * Creates a Grok text adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'grok-3', 'grok-4')\n * @param apiKey - Your xAI API key\n * @param config - Optional additional configuration\n * @returns Configured Grok text adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGrokText('grok-3', \"xai-...\");\n * // adapter has type-safe providerOptions for grok-3\n * ```\n */\nexport function createGrokText<\n TModel extends (typeof GROK_CHAT_MODELS)[number],\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<GrokTextConfig, 'apiKey'>,\n): GrokTextAdapter<TModel> {\n return new GrokTextAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Grok text adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `XAI_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'grok-3', 'grok-4')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Grok text adapter instance with resolved types\n * @throws Error if XAI_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses XAI_API_KEY from environment\n * const adapter = grokText('grok-3');\n *\n * const stream = chat({\n * adapter,\n * messages: [{ role: \"user\", content: \"Hello!\" }]\n * });\n * ```\n */\nexport function grokText<TModel extends (typeof GROK_CHAT_MODELS)[number]>(\n model: TModel,\n config?: Omit<GrokTextConfig, 'apiKey'>,\n): GrokTextAdapter<TModel> {\n const apiKey = getGrokApiKeyFromEnv()\n return createGrokText(model, apiKey, config)\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"text.js","sources":["../../../src/adapters/text.ts"],"sourcesContent":["import { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport { validateTextProviderOptions } from '../text/text-provider-options'\nimport { convertToolsToProviderFormat } from '../tools'\nimport {\n createGrokClient,\n generateId,\n getGrokApiKeyFromEnv,\n makeGrokStructuredOutputCompatible,\n transformNullsToUndefined,\n} from '../utils'\nimport type {\n GROK_CHAT_MODELS,\n ResolveInputModalities,\n ResolveProviderOptions,\n} from '../model-meta'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type OpenAI_SDK from 'openai'\nimport type {\n ContentPart,\n ModelMessage,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\nimport type { InternalTextProviderOptions } from '../text/text-provider-options'\nimport type {\n GrokImageMetadata,\n GrokMessageMetadataByModality,\n} from '../message-types'\nimport type { GrokClientConfig } from '../utils'\n\n/**\n * Configuration for Grok text adapter\n */\nexport interface GrokTextConfig extends GrokClientConfig {}\n\n/**\n * Alias for TextProviderOptions for external use\n */\nexport type { ExternalTextProviderOptions as GrokTextProviderOptions } from '../text/text-provider-options'\n\n/**\n * Grok Text (Chat) Adapter\n *\n * Tree-shakeable adapter for Grok chat/text completion functionality.\n * Uses OpenAI-compatible Chat Completions API (not Responses API).\n */\nexport class GrokTextAdapter<\n TModel extends (typeof GROK_CHAT_MODELS)[number],\n> extends BaseTextAdapter<\n TModel,\n ResolveProviderOptions<TModel>,\n ResolveInputModalities<TModel>,\n GrokMessageMetadataByModality\n> {\n readonly kind = 'text' as const\n readonly name = 'grok' as const\n\n private client: OpenAI_SDK\n\n constructor(config: GrokTextConfig, model: TModel) {\n super({}, model)\n this.client = createGrokClient(config)\n }\n\n async *chatStream(\n options: TextOptions<ResolveProviderOptions<TModel>>,\n ): AsyncIterable<StreamChunk> {\n const requestParams = this.mapTextOptionsToGrok(options)\n const timestamp = Date.now()\n\n // AG-UI lifecycle tracking (mutable state object for ESLint compatibility)\n const aguiState = {\n runId: generateId(this.name),\n messageId: generateId(this.name),\n timestamp,\n hasEmittedRunStarted: false,\n }\n\n try {\n const stream = await this.client.chat.completions.create({\n ...requestParams,\n stream: true,\n })\n\n yield* this.processGrokStreamChunks(stream, options, aguiState)\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n\n // Emit RUN_STARTED if not yet emitted\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: 'RUN_STARTED',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n }\n }\n\n // Emit AG-UI RUN_ERROR\n yield {\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n error: {\n message: err.message || 'Unknown error',\n code: err.code,\n },\n }\n\n console.error('>>> chatStream: Fatal error during response creation <<<')\n console.error('>>> Error message:', err.message)\n console.error('>>> Error stack:', err.stack)\n console.error('>>> Full error:', err)\n }\n }\n\n /**\n * Generate structured output using Grok's JSON Schema response format.\n * Uses stream: false to get the complete response in one call.\n *\n * Grok has strict requirements for structured output (via OpenAI-compatible API):\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for all objects\n *\n * The outputSchema is already JSON Schema (converted in the ai layer).\n * We apply Grok-specific transformations for structured output compatibility.\n */\n async structuredOutput(\n options: StructuredOutputOptions<ResolveProviderOptions<TModel>>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const requestParams = this.mapTextOptionsToGrok(chatOptions)\n\n // Apply Grok-specific transformations for structured output compatibility\n const jsonSchema = makeGrokStructuredOutputCompatible(\n outputSchema,\n outputSchema.required || [],\n )\n\n try {\n const response = await this.client.chat.completions.create({\n ...requestParams,\n stream: false,\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'structured_output',\n schema: jsonSchema,\n strict: true,\n },\n },\n })\n\n // Extract text content from the response\n const rawText = response.choices[0]?.message.content || ''\n\n // Parse the JSON response\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n // Transform null values to undefined to match original Zod schema expectations\n // Grok returns null for optional fields we made nullable in the schema\n const transformed = transformNullsToUndefined(parsed)\n\n return {\n data: transformed,\n rawText,\n }\n } catch (error: unknown) {\n const err = error as Error\n console.error('>>> structuredOutput: Error during response creation <<<')\n console.error('>>> Error message:', err.message)\n throw error\n }\n }\n\n private async *processGrokStreamChunks(\n stream: AsyncIterable<OpenAI_SDK.Chat.Completions.ChatCompletionChunk>,\n options: TextOptions,\n aguiState: {\n runId: string\n messageId: string\n timestamp: number\n hasEmittedRunStarted: boolean\n },\n ): AsyncIterable<StreamChunk> {\n let accumulatedContent = ''\n const timestamp = aguiState.timestamp\n let hasEmittedTextMessageStart = false\n\n // Track tool calls being streamed (arguments come in chunks)\n const toolCallsInProgress = new Map<\n number,\n {\n id: string\n name: string\n arguments: string\n started: boolean // Track if TOOL_CALL_START has been emitted\n }\n >()\n\n try {\n for await (const chunk of stream) {\n const choice = chunk.choices[0]\n\n if (!choice) continue\n\n // Emit RUN_STARTED on first chunk\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: 'RUN_STARTED',\n runId: aguiState.runId,\n model: chunk.model || options.model,\n timestamp,\n }\n }\n\n const delta = choice.delta\n const deltaContent = delta.content\n const deltaToolCalls = delta.tool_calls\n\n // Handle content delta\n if (deltaContent) {\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: 'TEXT_MESSAGE_START',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n role: 'assistant',\n }\n }\n\n accumulatedContent += deltaContent\n\n // Emit AG-UI TEXT_MESSAGE_CONTENT\n yield {\n type: 'TEXT_MESSAGE_CONTENT',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n delta: deltaContent,\n content: accumulatedContent,\n }\n }\n\n // Handle tool calls - they come in as deltas\n if (deltaToolCalls) {\n for (const toolCallDelta of deltaToolCalls) {\n const index = toolCallDelta.index\n\n // Initialize or update the tool call in progress\n if (!toolCallsInProgress.has(index)) {\n toolCallsInProgress.set(index, {\n id: toolCallDelta.id || '',\n name: toolCallDelta.function?.name || '',\n arguments: '',\n started: false,\n })\n }\n\n const toolCall = toolCallsInProgress.get(index)!\n\n // Update with any new data from the delta\n if (toolCallDelta.id) {\n toolCall.id = toolCallDelta.id\n }\n if (toolCallDelta.function?.name) {\n toolCall.name = toolCallDelta.function.name\n }\n if (toolCallDelta.function?.arguments) {\n toolCall.arguments += toolCallDelta.function.arguments\n }\n\n // Emit TOOL_CALL_START when we have id and name\n if (toolCall.id && toolCall.name && !toolCall.started) {\n toolCall.started = true\n yield {\n type: 'TOOL_CALL_START',\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp,\n index,\n }\n }\n\n // Emit TOOL_CALL_ARGS for argument deltas\n if (toolCallDelta.function?.arguments && toolCall.started) {\n yield {\n type: 'TOOL_CALL_ARGS',\n toolCallId: toolCall.id,\n model: chunk.model || options.model,\n timestamp,\n delta: toolCallDelta.function.arguments,\n }\n }\n }\n }\n\n // Handle finish reason\n if (choice.finish_reason) {\n // Emit all completed tool calls\n if (\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ) {\n for (const [, toolCall] of toolCallsInProgress) {\n // Parse arguments for TOOL_CALL_END\n let parsedInput: unknown = {}\n try {\n parsedInput = toolCall.arguments\n ? JSON.parse(toolCall.arguments)\n : {}\n } catch {\n parsedInput = {}\n }\n\n // Emit AG-UI TOOL_CALL_END\n yield {\n type: 'TOOL_CALL_END',\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp,\n input: parsedInput,\n }\n }\n }\n\n const computedFinishReason =\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ? 'tool_calls'\n : 'stop'\n\n // Emit TEXT_MESSAGE_END if we had text content\n if (hasEmittedTextMessageStart) {\n yield {\n type: 'TEXT_MESSAGE_END',\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp,\n }\n }\n\n // Emit AG-UI RUN_FINISHED\n yield {\n type: 'RUN_FINISHED',\n runId: aguiState.runId,\n model: chunk.model || options.model,\n timestamp,\n usage: chunk.usage\n ? {\n promptTokens: chunk.usage.prompt_tokens || 0,\n completionTokens: chunk.usage.completion_tokens || 0,\n totalTokens: chunk.usage.total_tokens || 0,\n }\n : undefined,\n finishReason: computedFinishReason,\n }\n }\n }\n } catch (error: unknown) {\n const err = error as Error & { code?: string }\n console.log('[Grok Adapter] Stream ended with error:', err.message)\n\n // Emit AG-UI RUN_ERROR\n yield {\n type: 'RUN_ERROR',\n runId: aguiState.runId,\n model: options.model,\n timestamp,\n error: {\n message: err.message || 'Unknown error occurred',\n code: err.code,\n },\n }\n }\n }\n\n /**\n * Maps common options to Grok-specific Chat Completions format\n */\n private mapTextOptionsToGrok(\n options: TextOptions,\n ): OpenAI_SDK.Chat.Completions.ChatCompletionCreateParamsStreaming {\n const modelOptions = options.modelOptions as\n | Omit<\n InternalTextProviderOptions,\n 'max_tokens' | 'tools' | 'temperature' | 'input' | 'top_p'\n >\n | undefined\n\n if (modelOptions) {\n validateTextProviderOptions({\n ...modelOptions,\n model: options.model,\n })\n }\n\n const tools = options.tools\n ? convertToolsToProviderFormat(options.tools)\n : undefined\n\n // Build messages array with system prompts\n const messages: Array<OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam> =\n []\n\n // Add system prompts first\n if (options.systemPrompts && options.systemPrompts.length > 0) {\n messages.push({\n role: 'system',\n content: options.systemPrompts.join('\\n'),\n })\n }\n\n // Convert messages\n for (const message of options.messages) {\n messages.push(this.convertMessageToGrok(message))\n }\n\n return {\n model: options.model,\n messages,\n temperature: options.temperature,\n max_tokens: options.maxTokens,\n top_p: options.topP,\n tools: tools as Array<OpenAI_SDK.Chat.Completions.ChatCompletionTool>,\n stream: true,\n stream_options: { include_usage: true },\n }\n }\n\n private convertMessageToGrok(\n message: ModelMessage,\n ): OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam {\n // Handle tool messages\n if (message.role === 'tool') {\n return {\n role: 'tool',\n tool_call_id: message.toolCallId || '',\n content:\n typeof message.content === 'string'\n ? message.content\n : JSON.stringify(message.content),\n }\n }\n\n // Handle assistant messages\n if (message.role === 'assistant') {\n const toolCalls = message.toolCalls?.map((tc) => ({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === 'string'\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }))\n\n return {\n role: 'assistant',\n content: this.extractTextContent(message.content),\n ...(toolCalls && toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),\n }\n }\n\n // Handle user messages - support multimodal content\n const contentParts = this.normalizeContent(message.content)\n\n // If only text, use simple string format\n if (contentParts.length === 1 && contentParts[0]?.type === 'text') {\n return {\n role: 'user',\n content: contentParts[0].content,\n }\n }\n\n // Otherwise, use array format for multimodal\n const parts: Array<OpenAI_SDK.Chat.Completions.ChatCompletionContentPart> =\n []\n for (const part of contentParts) {\n if (part.type === 'text') {\n parts.push({ type: 'text', text: part.content })\n } else if (part.type === 'image') {\n const imageMetadata = part.metadata as GrokImageMetadata | undefined\n parts.push({\n type: 'image_url',\n image_url: {\n url: part.source.value,\n detail: imageMetadata?.detail || 'auto',\n },\n })\n }\n }\n\n return {\n role: 'user',\n content: parts.length > 0 ? parts : '',\n }\n }\n\n /**\n * Normalizes message content to an array of ContentPart.\n * Handles backward compatibility with string content.\n */\n private normalizeContent(\n content: string | null | Array<ContentPart>,\n ): Array<ContentPart> {\n if (content === null) {\n return []\n }\n if (typeof content === 'string') {\n return [{ type: 'text', content: content }]\n }\n return content\n }\n\n /**\n * Extracts text content from a content value that may be string, null, or ContentPart array.\n */\n private extractTextContent(\n content: string | null | Array<ContentPart>,\n ): string {\n if (content === null) {\n return ''\n }\n if (typeof content === 'string') {\n return content\n }\n // It's an array of ContentPart\n return content\n .filter((p) => p.type === 'text')\n .map((p) => p.content)\n .join('')\n }\n}\n\n/**\n * Creates a Grok text adapter with explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'grok-3', 'grok-4')\n * @param apiKey - Your xAI API key\n * @param config - Optional additional configuration\n * @returns Configured Grok text adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createGrokText('grok-3', \"xai-...\");\n * // adapter has type-safe providerOptions for grok-3\n * ```\n */\nexport function createGrokText<\n TModel extends (typeof GROK_CHAT_MODELS)[number],\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<GrokTextConfig, 'apiKey'>,\n): GrokTextAdapter<TModel> {\n return new GrokTextAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a Grok text adapter with automatic API key detection from environment variables.\n * Type resolution happens here at the call site.\n *\n * Looks for `XAI_API_KEY` in:\n * - `process.env` (Node.js)\n * - `window.env` (Browser with injected env)\n *\n * @param model - The model name (e.g., 'grok-3', 'grok-4')\n * @param config - Optional configuration (excluding apiKey which is auto-detected)\n * @returns Configured Grok text adapter instance with resolved types\n * @throws Error if XAI_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * // Automatically uses XAI_API_KEY from environment\n * const adapter = grokText('grok-3');\n *\n * const stream = chat({\n * adapter,\n * messages: [{ role: \"user\", content: \"Hello!\" }]\n * });\n * ```\n */\nexport function grokText<TModel extends (typeof GROK_CHAT_MODELS)[number]>(\n model: TModel,\n config?: Omit<GrokTextConfig, 'apiKey'>,\n): GrokTextAdapter<TModel> {\n const apiKey = getGrokApiKeyFromEnv()\n return createGrokText(model, apiKey, config)\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-grok",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Grok (xAI) adapter for TanStack AI",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"zod": "^4.0.0",
|
|
41
|
-
"@tanstack/ai": "^0.
|
|
41
|
+
"@tanstack/ai": "^0.3.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "vite build",
|
|
@@ -51,7 +51,7 @@ export class GrokSummarizeAdapter<
|
|
|
51
51
|
|
|
52
52
|
// Use the text adapter's streaming and collect the result
|
|
53
53
|
let summary = ''
|
|
54
|
-
|
|
54
|
+
const id = ''
|
|
55
55
|
let model = options.model
|
|
56
56
|
let usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
57
57
|
|
|
@@ -62,13 +62,20 @@ export class GrokSummarizeAdapter<
|
|
|
62
62
|
maxTokens: options.maxLength,
|
|
63
63
|
temperature: 0.3,
|
|
64
64
|
})) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
// AG-UI TEXT_MESSAGE_CONTENT event
|
|
66
|
+
if (chunk.type === 'TEXT_MESSAGE_CONTENT') {
|
|
67
|
+
if (chunk.content) {
|
|
68
|
+
summary = chunk.content
|
|
69
|
+
} else {
|
|
70
|
+
summary += chunk.delta
|
|
71
|
+
}
|
|
72
|
+
model = chunk.model || model
|
|
69
73
|
}
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
// AG-UI RUN_FINISHED event
|
|
75
|
+
if (chunk.type === 'RUN_FINISHED') {
|
|
76
|
+
if (chunk.usage) {
|
|
77
|
+
usage = chunk.usage
|
|
78
|
+
}
|
|
72
79
|
}
|
|
73
80
|
}
|
|
74
81
|
|
package/src/adapters/text.ts
CHANGED
|
@@ -69,6 +69,15 @@ export class GrokTextAdapter<
|
|
|
69
69
|
options: TextOptions<ResolveProviderOptions<TModel>>,
|
|
70
70
|
): AsyncIterable<StreamChunk> {
|
|
71
71
|
const requestParams = this.mapTextOptionsToGrok(options)
|
|
72
|
+
const timestamp = Date.now()
|
|
73
|
+
|
|
74
|
+
// AG-UI lifecycle tracking (mutable state object for ESLint compatibility)
|
|
75
|
+
const aguiState = {
|
|
76
|
+
runId: generateId(this.name),
|
|
77
|
+
messageId: generateId(this.name),
|
|
78
|
+
timestamp,
|
|
79
|
+
hasEmittedRunStarted: false,
|
|
80
|
+
}
|
|
72
81
|
|
|
73
82
|
try {
|
|
74
83
|
const stream = await this.client.chat.completions.create({
|
|
@@ -76,14 +85,37 @@ export class GrokTextAdapter<
|
|
|
76
85
|
stream: true,
|
|
77
86
|
})
|
|
78
87
|
|
|
79
|
-
yield* this.processGrokStreamChunks(stream, options)
|
|
88
|
+
yield* this.processGrokStreamChunks(stream, options, aguiState)
|
|
80
89
|
} catch (error: unknown) {
|
|
81
|
-
const err = error as Error
|
|
90
|
+
const err = error as Error & { code?: string }
|
|
91
|
+
|
|
92
|
+
// Emit RUN_STARTED if not yet emitted
|
|
93
|
+
if (!aguiState.hasEmittedRunStarted) {
|
|
94
|
+
aguiState.hasEmittedRunStarted = true
|
|
95
|
+
yield {
|
|
96
|
+
type: 'RUN_STARTED',
|
|
97
|
+
runId: aguiState.runId,
|
|
98
|
+
model: options.model,
|
|
99
|
+
timestamp,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Emit AG-UI RUN_ERROR
|
|
104
|
+
yield {
|
|
105
|
+
type: 'RUN_ERROR',
|
|
106
|
+
runId: aguiState.runId,
|
|
107
|
+
model: options.model,
|
|
108
|
+
timestamp,
|
|
109
|
+
error: {
|
|
110
|
+
message: err.message || 'Unknown error',
|
|
111
|
+
code: err.code,
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
|
|
82
115
|
console.error('>>> chatStream: Fatal error during response creation <<<')
|
|
83
116
|
console.error('>>> Error message:', err.message)
|
|
84
117
|
console.error('>>> Error stack:', err.stack)
|
|
85
118
|
console.error('>>> Full error:', err)
|
|
86
|
-
throw error
|
|
87
119
|
}
|
|
88
120
|
}
|
|
89
121
|
|
|
@@ -157,10 +189,16 @@ export class GrokTextAdapter<
|
|
|
157
189
|
private async *processGrokStreamChunks(
|
|
158
190
|
stream: AsyncIterable<OpenAI_SDK.Chat.Completions.ChatCompletionChunk>,
|
|
159
191
|
options: TextOptions,
|
|
192
|
+
aguiState: {
|
|
193
|
+
runId: string
|
|
194
|
+
messageId: string
|
|
195
|
+
timestamp: number
|
|
196
|
+
hasEmittedRunStarted: boolean
|
|
197
|
+
},
|
|
160
198
|
): AsyncIterable<StreamChunk> {
|
|
161
199
|
let accumulatedContent = ''
|
|
162
|
-
const timestamp =
|
|
163
|
-
let
|
|
200
|
+
const timestamp = aguiState.timestamp
|
|
201
|
+
let hasEmittedTextMessageStart = false
|
|
164
202
|
|
|
165
203
|
// Track tool calls being streamed (arguments come in chunks)
|
|
166
204
|
const toolCallsInProgress = new Map<
|
|
@@ -169,31 +207,55 @@ export class GrokTextAdapter<
|
|
|
169
207
|
id: string
|
|
170
208
|
name: string
|
|
171
209
|
arguments: string
|
|
210
|
+
started: boolean // Track if TOOL_CALL_START has been emitted
|
|
172
211
|
}
|
|
173
212
|
>()
|
|
174
213
|
|
|
175
214
|
try {
|
|
176
215
|
for await (const chunk of stream) {
|
|
177
|
-
responseId = chunk.id || responseId
|
|
178
216
|
const choice = chunk.choices[0]
|
|
179
217
|
|
|
180
218
|
if (!choice) continue
|
|
181
219
|
|
|
220
|
+
// Emit RUN_STARTED on first chunk
|
|
221
|
+
if (!aguiState.hasEmittedRunStarted) {
|
|
222
|
+
aguiState.hasEmittedRunStarted = true
|
|
223
|
+
yield {
|
|
224
|
+
type: 'RUN_STARTED',
|
|
225
|
+
runId: aguiState.runId,
|
|
226
|
+
model: chunk.model || options.model,
|
|
227
|
+
timestamp,
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
182
231
|
const delta = choice.delta
|
|
183
232
|
const deltaContent = delta.content
|
|
184
233
|
const deltaToolCalls = delta.tool_calls
|
|
185
234
|
|
|
186
235
|
// Handle content delta
|
|
187
236
|
if (deltaContent) {
|
|
237
|
+
// Emit TEXT_MESSAGE_START on first text content
|
|
238
|
+
if (!hasEmittedTextMessageStart) {
|
|
239
|
+
hasEmittedTextMessageStart = true
|
|
240
|
+
yield {
|
|
241
|
+
type: 'TEXT_MESSAGE_START',
|
|
242
|
+
messageId: aguiState.messageId,
|
|
243
|
+
model: chunk.model || options.model,
|
|
244
|
+
timestamp,
|
|
245
|
+
role: 'assistant',
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
188
249
|
accumulatedContent += deltaContent
|
|
250
|
+
|
|
251
|
+
// Emit AG-UI TEXT_MESSAGE_CONTENT
|
|
189
252
|
yield {
|
|
190
|
-
type: '
|
|
191
|
-
|
|
253
|
+
type: 'TEXT_MESSAGE_CONTENT',
|
|
254
|
+
messageId: aguiState.messageId,
|
|
192
255
|
model: chunk.model || options.model,
|
|
193
256
|
timestamp,
|
|
194
257
|
delta: deltaContent,
|
|
195
258
|
content: accumulatedContent,
|
|
196
|
-
role: 'assistant',
|
|
197
259
|
}
|
|
198
260
|
}
|
|
199
261
|
|
|
@@ -208,6 +270,7 @@ export class GrokTextAdapter<
|
|
|
208
270
|
id: toolCallDelta.id || '',
|
|
209
271
|
name: toolCallDelta.function?.name || '',
|
|
210
272
|
arguments: '',
|
|
273
|
+
started: false,
|
|
211
274
|
})
|
|
212
275
|
}
|
|
213
276
|
|
|
@@ -223,6 +286,30 @@ export class GrokTextAdapter<
|
|
|
223
286
|
if (toolCallDelta.function?.arguments) {
|
|
224
287
|
toolCall.arguments += toolCallDelta.function.arguments
|
|
225
288
|
}
|
|
289
|
+
|
|
290
|
+
// Emit TOOL_CALL_START when we have id and name
|
|
291
|
+
if (toolCall.id && toolCall.name && !toolCall.started) {
|
|
292
|
+
toolCall.started = true
|
|
293
|
+
yield {
|
|
294
|
+
type: 'TOOL_CALL_START',
|
|
295
|
+
toolCallId: toolCall.id,
|
|
296
|
+
toolName: toolCall.name,
|
|
297
|
+
model: chunk.model || options.model,
|
|
298
|
+
timestamp,
|
|
299
|
+
index,
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Emit TOOL_CALL_ARGS for argument deltas
|
|
304
|
+
if (toolCallDelta.function?.arguments && toolCall.started) {
|
|
305
|
+
yield {
|
|
306
|
+
type: 'TOOL_CALL_ARGS',
|
|
307
|
+
toolCallId: toolCall.id,
|
|
308
|
+
model: chunk.model || options.model,
|
|
309
|
+
timestamp,
|
|
310
|
+
delta: toolCallDelta.function.arguments,
|
|
311
|
+
}
|
|
312
|
+
}
|
|
226
313
|
}
|
|
227
314
|
}
|
|
228
315
|
|
|
@@ -233,28 +320,49 @@ export class GrokTextAdapter<
|
|
|
233
320
|
choice.finish_reason === 'tool_calls' ||
|
|
234
321
|
toolCallsInProgress.size > 0
|
|
235
322
|
) {
|
|
236
|
-
for (const [
|
|
323
|
+
for (const [, toolCall] of toolCallsInProgress) {
|
|
324
|
+
// Parse arguments for TOOL_CALL_END
|
|
325
|
+
let parsedInput: unknown = {}
|
|
326
|
+
try {
|
|
327
|
+
parsedInput = toolCall.arguments
|
|
328
|
+
? JSON.parse(toolCall.arguments)
|
|
329
|
+
: {}
|
|
330
|
+
} catch {
|
|
331
|
+
parsedInput = {}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Emit AG-UI TOOL_CALL_END
|
|
237
335
|
yield {
|
|
238
|
-
type: '
|
|
239
|
-
|
|
336
|
+
type: 'TOOL_CALL_END',
|
|
337
|
+
toolCallId: toolCall.id,
|
|
338
|
+
toolName: toolCall.name,
|
|
240
339
|
model: chunk.model || options.model,
|
|
241
340
|
timestamp,
|
|
242
|
-
|
|
243
|
-
toolCall: {
|
|
244
|
-
id: toolCall.id,
|
|
245
|
-
type: 'function',
|
|
246
|
-
function: {
|
|
247
|
-
name: toolCall.name,
|
|
248
|
-
arguments: toolCall.arguments,
|
|
249
|
-
},
|
|
250
|
-
},
|
|
341
|
+
input: parsedInput,
|
|
251
342
|
}
|
|
252
343
|
}
|
|
253
344
|
}
|
|
254
345
|
|
|
346
|
+
const computedFinishReason =
|
|
347
|
+
choice.finish_reason === 'tool_calls' ||
|
|
348
|
+
toolCallsInProgress.size > 0
|
|
349
|
+
? 'tool_calls'
|
|
350
|
+
: 'stop'
|
|
351
|
+
|
|
352
|
+
// Emit TEXT_MESSAGE_END if we had text content
|
|
353
|
+
if (hasEmittedTextMessageStart) {
|
|
354
|
+
yield {
|
|
355
|
+
type: 'TEXT_MESSAGE_END',
|
|
356
|
+
messageId: aguiState.messageId,
|
|
357
|
+
model: chunk.model || options.model,
|
|
358
|
+
timestamp,
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Emit AG-UI RUN_FINISHED
|
|
255
363
|
yield {
|
|
256
|
-
type: '
|
|
257
|
-
|
|
364
|
+
type: 'RUN_FINISHED',
|
|
365
|
+
runId: aguiState.runId,
|
|
258
366
|
model: chunk.model || options.model,
|
|
259
367
|
timestamp,
|
|
260
368
|
usage: chunk.usage
|
|
@@ -264,20 +372,18 @@ export class GrokTextAdapter<
|
|
|
264
372
|
totalTokens: chunk.usage.total_tokens || 0,
|
|
265
373
|
}
|
|
266
374
|
: undefined,
|
|
267
|
-
finishReason:
|
|
268
|
-
choice.finish_reason === 'tool_calls' ||
|
|
269
|
-
toolCallsInProgress.size > 0
|
|
270
|
-
? 'tool_calls'
|
|
271
|
-
: 'stop',
|
|
375
|
+
finishReason: computedFinishReason,
|
|
272
376
|
}
|
|
273
377
|
}
|
|
274
378
|
}
|
|
275
379
|
} catch (error: unknown) {
|
|
276
380
|
const err = error as Error & { code?: string }
|
|
277
381
|
console.log('[Grok Adapter] Stream ended with error:', err.message)
|
|
382
|
+
|
|
383
|
+
// Emit AG-UI RUN_ERROR
|
|
278
384
|
yield {
|
|
279
|
-
type: '
|
|
280
|
-
|
|
385
|
+
type: 'RUN_ERROR',
|
|
386
|
+
runId: aguiState.runId,
|
|
281
387
|
model: options.model,
|
|
282
388
|
timestamp,
|
|
283
389
|
error: {
|