@tanstack/ai-grok 0.1.0 → 0.4.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
- let id = "";
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 === "content") {
25
- summary = chunk.content;
26
- id = chunk.id;
27
- model = chunk.model;
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 === "done" && chunk.usage) {
30
- usage = chunk.usage;
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 let 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 if (chunk.type === 'content') {\n summary = chunk.content\n id = chunk.id\n model = chunk.model\n }\n if (chunk.type === 'done' && chunk.usage) {\n usage = chunk.usage\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,QAAI,KAAK;AACT,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;AACF,UAAI,MAAM,SAAS,WAAW;AAC5B,kBAAU,MAAM;AAChB,aAAK,MAAM;AACX,gBAAQ,MAAM;AAAA,MAChB;AACA,UAAI,MAAM,SAAS,UAAU,MAAM,OAAO;AACxC,gBAAQ,MAAM;AAAA,MAChB;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;"}
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 = Date.now();
86
- let responseId = generateId(this.name);
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: "content",
100
- id: responseId,
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 [index, toolCall] of toolCallsInProgress) {
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: "tool_call",
135
- id: responseId,
203
+ type: "TOOL_CALL_END",
204
+ toolCallId: toolCall.id,
205
+ toolName: toolCall.name,
136
206
  model: chunk.model || options.model,
137
207
  timestamp,
138
- index,
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: "done",
152
- id: responseId,
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: choice.finish_reason === "tool_calls" || toolCallsInProgress.size > 0 ? "tool_calls" : "stop"
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: "error",
169
- id: responseId,
239
+ type: "RUN_ERROR",
240
+ runId: aguiState.runId,
170
241
  model: options.model,
171
242
  timestamp,
172
243
  error: {
@@ -245,10 +316,12 @@ class GrokTextAdapter extends BaseTextAdapter {
245
316
  parts.push({ type: "text", text: part.content });
246
317
  } else if (part.type === "image") {
247
318
  const imageMetadata = part.metadata;
319
+ const imageValue = part.source.value;
320
+ const imageUrl = part.source.type === "data" && !imageValue.startsWith("data:") ? `data:${part.source.mimeType};base64,${imageValue}` : imageValue;
248
321
  parts.push({
249
322
  type: "image_url",
250
323
  image_url: {
251
- url: part.source.value,
324
+ url: imageUrl,
252
325
  detail: imageMetadata?.detail || "auto"
253
326
  }
254
327
  });
@@ -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 // For base64 data, construct a data URI using the mimeType from source\n const imageValue = part.source.value\n const imageUrl =\n part.source.type === 'data' && !imageValue.startsWith('data:')\n ? `data:${part.source.mimeType};base64,${imageValue}`\n : imageValue\n parts.push({\n type: 'image_url',\n image_url: {\n url: imageUrl,\n detail: imageMetadata?.detail || 'auto',\n },\n })\n }\n }\n\n return {\n role: 'user',\n content: parts.length > 0 ? parts : '',\n }\n }\n\n /**\n * Normalizes message content to an array of ContentPart.\n * Handles backward compatibility with string content.\n */\n private normalizeContent(\n content: string | null | Array<ContentPart>,\n ): Array<ContentPart> {\n if (content === null) {\n return []\n }\n if (typeof content === 'string') {\n return [{ type: 'text', content: content }]\n }\n return content\n }\n\n /**\n * Extracts text content from a content value that may be string, null, or ContentPart array.\n */\n private extractTextContent(\n content: string | null | Array<ContentPart>,\n ): string {\n if (content === null) {\n return ''\n }\n if (typeof content === 'string') {\n return content\n }\n // 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.1.0",
3
+ "version": "0.4.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.2.1"
41
+ "@tanstack/ai": "^0.4.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
- let id = ''
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
- if (chunk.type === 'content') {
66
- summary = chunk.content
67
- id = chunk.id
68
- model = chunk.model
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
- if (chunk.type === 'done' && chunk.usage) {
71
- usage = chunk.usage
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
 
@@ -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 = Date.now()
163
- let responseId = generateId(this.name)
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: 'content',
191
- id: responseId,
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 [index, toolCall] of toolCallsInProgress) {
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: 'tool_call',
239
- id: responseId,
336
+ type: 'TOOL_CALL_END',
337
+ toolCallId: toolCall.id,
338
+ toolName: toolCall.name,
240
339
  model: chunk.model || options.model,
241
340
  timestamp,
242
- index,
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: 'done',
257
- id: responseId,
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: 'error',
280
- id: responseId,
385
+ type: 'RUN_ERROR',
386
+ runId: aguiState.runId,
281
387
  model: options.model,
282
388
  timestamp,
283
389
  error: {
@@ -396,10 +502,16 @@ export class GrokTextAdapter<
396
502
  parts.push({ type: 'text', text: part.content })
397
503
  } else if (part.type === 'image') {
398
504
  const imageMetadata = part.metadata as GrokImageMetadata | undefined
505
+ // For base64 data, construct a data URI using the mimeType from source
506
+ const imageValue = part.source.value
507
+ const imageUrl =
508
+ part.source.type === 'data' && !imageValue.startsWith('data:')
509
+ ? `data:${part.source.mimeType};base64,${imageValue}`
510
+ : imageValue
399
511
  parts.push({
400
512
  type: 'image_url',
401
513
  image_url: {
402
- url: part.source.value,
514
+ url: imageUrl,
403
515
  detail: imageMetadata?.detail || 'auto',
404
516
  },
405
517
  })