@tanstack/ai-grok 0.7.1 → 0.7.3
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.
- package/dist/esm/adapters/image.d.ts +8 -7
- package/dist/esm/adapters/image.js +10 -60
- package/dist/esm/adapters/image.js.map +1 -1
- package/dist/esm/adapters/summarize.d.ts +2 -7
- package/dist/esm/adapters/summarize.js +11 -95
- package/dist/esm/adapters/summarize.js.map +1 -1
- package/dist/esm/adapters/text.d.ts +10 -34
- package/dist/esm/adapters/text.js +4 -381
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/adapters/transcription.js +3 -1
- package/dist/esm/adapters/transcription.js.map +1 -1
- package/dist/esm/adapters/tts.js +3 -1
- package/dist/esm/adapters/tts.js.map +1 -1
- package/dist/esm/realtime/token.js +2 -0
- package/dist/esm/realtime/token.js.map +1 -1
- package/dist/esm/text/text-provider-options.d.ts +0 -14
- package/dist/esm/tools/index.d.ts +1 -2
- package/dist/esm/tools/index.js +3 -4
- package/dist/esm/tools/index.js.map +1 -1
- package/dist/esm/utils/client.d.ts +5 -9
- package/dist/esm/utils/client.js +11 -17
- package/dist/esm/utils/client.js.map +1 -1
- package/dist/esm/utils/index.d.ts +2 -1
- package/dist/esm/utils/schema-converter.d.ts +2 -24
- package/package.json +6 -6
- package/src/adapters/image.ts +18 -87
- package/src/adapters/summarize.ts +16 -121
- package/src/adapters/text.ts +13 -562
- package/src/text/text-provider-options.ts +0 -22
- package/src/tools/index.ts +4 -4
- package/src/utils/client.ts +15 -30
- package/src/utils/index.ts +2 -2
- package/src/utils/schema-converter.ts +2 -110
- package/dist/esm/text/text-provider-options.js +0 -6
- package/dist/esm/text/text-provider-options.js.map +0 -1
- package/dist/esm/tools/function-tool.d.ts +0 -15
- package/dist/esm/tools/function-tool.js +0 -27
- package/dist/esm/tools/function-tool.js.map +0 -1
- package/dist/esm/tools/tool-converter.d.ts +0 -7
- package/dist/esm/tools/tool-converter.js +0 -10
- package/dist/esm/tools/tool-converter.js.map +0 -1
- package/dist/esm/utils/schema-converter.js +0 -71
- package/dist/esm/utils/schema-converter.js.map +0 -1
- package/src/tools/function-tool.ts +0 -45
- package/src/tools/tool-converter.ts +0 -17
package/src/adapters/text.ts
CHANGED
|
@@ -1,52 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { convertToolsToProviderFormat } from '../tools'
|
|
4
|
-
import {
|
|
5
|
-
createGrokClient,
|
|
6
|
-
generateId,
|
|
7
|
-
getGrokApiKeyFromEnv,
|
|
8
|
-
makeGrokStructuredOutputCompatible,
|
|
9
|
-
transformNullsToUndefined,
|
|
10
|
-
} from '../utils'
|
|
1
|
+
import { OpenAICompatibleChatCompletionsTextAdapter } from '@tanstack/openai-base'
|
|
2
|
+
import { getGrokApiKeyFromEnv, withGrokDefaults } from '../utils/client'
|
|
11
3
|
import type {
|
|
12
4
|
GROK_CHAT_MODELS,
|
|
13
5
|
GrokChatModelToolCapabilitiesByName,
|
|
14
6
|
ResolveInputModalities,
|
|
15
7
|
ResolveProviderOptions,
|
|
16
8
|
} from '../model-meta'
|
|
17
|
-
import type {
|
|
18
|
-
|
|
19
|
-
StructuredOutputResult,
|
|
20
|
-
} from '@tanstack/ai/adapters'
|
|
21
|
-
import type { InternalLogger } from '@tanstack/ai/adapter-internals'
|
|
22
|
-
import type OpenAI_SDK from 'openai'
|
|
23
|
-
import type {
|
|
24
|
-
ContentPart,
|
|
25
|
-
Modality,
|
|
26
|
-
ModelMessage,
|
|
27
|
-
StreamChunk,
|
|
28
|
-
TextOptions,
|
|
29
|
-
} from '@tanstack/ai'
|
|
30
|
-
import type {
|
|
31
|
-
ExternalTextProviderOptions as GrokTextProviderOptions,
|
|
32
|
-
InternalTextProviderOptions,
|
|
33
|
-
} from '../text/text-provider-options'
|
|
34
|
-
import type {
|
|
35
|
-
GrokImageMetadata,
|
|
36
|
-
GrokMessageMetadataByModality,
|
|
37
|
-
} from '../message-types'
|
|
9
|
+
import type { Modality } from '@tanstack/ai'
|
|
10
|
+
import type { GrokMessageMetadataByModality } from '../message-types'
|
|
38
11
|
import type { GrokClientConfig } from '../utils'
|
|
39
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Resolve tool capabilities for a specific Grok model.
|
|
15
|
+
*/
|
|
40
16
|
type ResolveToolCapabilities<TModel extends string> =
|
|
41
17
|
TModel extends keyof GrokChatModelToolCapabilitiesByName
|
|
42
18
|
? NonNullable<GrokChatModelToolCapabilitiesByName[TModel]>
|
|
43
19
|
: readonly []
|
|
44
20
|
|
|
45
|
-
/** Cast an event object to StreamChunk. Adapters construct events with string
|
|
46
|
-
* literal types which are structurally compatible with the EventType enum. */
|
|
47
|
-
const asChunk = (chunk: Record<string, unknown>) =>
|
|
48
|
-
chunk as unknown as StreamChunk
|
|
49
|
-
|
|
50
21
|
/**
|
|
51
22
|
* Configuration for Grok text adapter
|
|
52
23
|
*/
|
|
@@ -62,6 +33,10 @@ export type { ExternalTextProviderOptions as GrokTextProviderOptions } from '../
|
|
|
62
33
|
*
|
|
63
34
|
* Tree-shakeable adapter for Grok chat/text completion functionality.
|
|
64
35
|
* Uses OpenAI-compatible Chat Completions API (not Responses API).
|
|
36
|
+
*
|
|
37
|
+
* Delegates implementation to {@link OpenAICompatibleChatCompletionsTextAdapter}
|
|
38
|
+
* from `@tanstack/openai-base` and threads Grok-specific tool-capability typing
|
|
39
|
+
* through the 5th generic of the base class.
|
|
65
40
|
*/
|
|
66
41
|
export class GrokTextAdapter<
|
|
67
42
|
TModel extends (typeof GROK_CHAT_MODELS)[number],
|
|
@@ -70,7 +45,7 @@ export class GrokTextAdapter<
|
|
|
70
45
|
ResolveInputModalities<TModel>,
|
|
71
46
|
TToolCapabilities extends ReadonlyArray<string> =
|
|
72
47
|
ResolveToolCapabilities<TModel>,
|
|
73
|
-
> extends
|
|
48
|
+
> extends OpenAICompatibleChatCompletionsTextAdapter<
|
|
74
49
|
TModel,
|
|
75
50
|
TProviderOptions,
|
|
76
51
|
TInputModalities,
|
|
@@ -80,532 +55,8 @@ export class GrokTextAdapter<
|
|
|
80
55
|
readonly kind = 'text' as const
|
|
81
56
|
readonly name = 'grok' as const
|
|
82
57
|
|
|
83
|
-
private client: OpenAI_SDK
|
|
84
|
-
|
|
85
58
|
constructor(config: GrokTextConfig, model: TModel) {
|
|
86
|
-
super(
|
|
87
|
-
this.client = createGrokClient(config)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async *chatStream(
|
|
91
|
-
options: TextOptions<GrokTextProviderOptions>,
|
|
92
|
-
): AsyncIterable<StreamChunk> {
|
|
93
|
-
const requestParams = this.mapTextOptionsToGrok(options)
|
|
94
|
-
const timestamp = Date.now()
|
|
95
|
-
const { logger } = options
|
|
96
|
-
|
|
97
|
-
// AG-UI lifecycle tracking (mutable state object for ESLint compatibility)
|
|
98
|
-
const aguiState = {
|
|
99
|
-
runId: options.runId ?? generateId(this.name),
|
|
100
|
-
threadId: options.threadId ?? generateId(this.name),
|
|
101
|
-
messageId: generateId(this.name),
|
|
102
|
-
timestamp,
|
|
103
|
-
hasEmittedRunStarted: false,
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
logger.request(
|
|
108
|
-
`activity=chat provider=grok model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,
|
|
109
|
-
{ provider: 'grok', model: this.model },
|
|
110
|
-
)
|
|
111
|
-
const stream = await this.client.chat.completions.create({
|
|
112
|
-
...requestParams,
|
|
113
|
-
stream: true,
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
yield* this.processGrokStreamChunks(stream, options, aguiState, logger)
|
|
117
|
-
} catch (error: unknown) {
|
|
118
|
-
const err = error as Error & { code?: string }
|
|
119
|
-
|
|
120
|
-
// Emit RUN_STARTED if not yet emitted
|
|
121
|
-
if (!aguiState.hasEmittedRunStarted) {
|
|
122
|
-
aguiState.hasEmittedRunStarted = true
|
|
123
|
-
yield asChunk({
|
|
124
|
-
type: 'RUN_STARTED',
|
|
125
|
-
runId: aguiState.runId,
|
|
126
|
-
threadId: aguiState.threadId,
|
|
127
|
-
model: options.model,
|
|
128
|
-
timestamp,
|
|
129
|
-
})
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Emit AG-UI RUN_ERROR
|
|
133
|
-
yield asChunk({
|
|
134
|
-
type: 'RUN_ERROR',
|
|
135
|
-
runId: aguiState.runId,
|
|
136
|
-
model: options.model,
|
|
137
|
-
timestamp,
|
|
138
|
-
message: err.message || 'Unknown error',
|
|
139
|
-
code: err.code,
|
|
140
|
-
error: {
|
|
141
|
-
message: err.message || 'Unknown error',
|
|
142
|
-
code: err.code,
|
|
143
|
-
},
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
logger.errors('grok.chatStream fatal', {
|
|
147
|
-
error,
|
|
148
|
-
source: 'grok.chatStream',
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Generate structured output using Grok's JSON Schema response format.
|
|
155
|
-
* Uses stream: false to get the complete response in one call.
|
|
156
|
-
*
|
|
157
|
-
* Grok has strict requirements for structured output (via OpenAI-compatible API):
|
|
158
|
-
* - All properties must be in the `required` array
|
|
159
|
-
* - Optional fields should have null added to their type union
|
|
160
|
-
* - additionalProperties must be false for all objects
|
|
161
|
-
*
|
|
162
|
-
* The outputSchema is already JSON Schema (converted in the ai layer).
|
|
163
|
-
* We apply Grok-specific transformations for structured output compatibility.
|
|
164
|
-
*/
|
|
165
|
-
async structuredOutput(
|
|
166
|
-
options: StructuredOutputOptions<GrokTextProviderOptions>,
|
|
167
|
-
): Promise<StructuredOutputResult<unknown>> {
|
|
168
|
-
const { chatOptions, outputSchema } = options
|
|
169
|
-
const requestParams = this.mapTextOptionsToGrok(chatOptions)
|
|
170
|
-
const { logger } = chatOptions
|
|
171
|
-
|
|
172
|
-
// Apply Grok-specific transformations for structured output compatibility
|
|
173
|
-
const jsonSchema = makeGrokStructuredOutputCompatible(
|
|
174
|
-
outputSchema,
|
|
175
|
-
outputSchema.required || [],
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
logger.request(
|
|
180
|
-
`activity=chat provider=grok model=${this.model} messages=${chatOptions.messages.length} tools=${chatOptions.tools?.length ?? 0} stream=false`,
|
|
181
|
-
{ provider: 'grok', model: this.model },
|
|
182
|
-
)
|
|
183
|
-
const response = await this.client.chat.completions.create({
|
|
184
|
-
...requestParams,
|
|
185
|
-
stream: false,
|
|
186
|
-
response_format: {
|
|
187
|
-
type: 'json_schema',
|
|
188
|
-
json_schema: {
|
|
189
|
-
name: 'structured_output',
|
|
190
|
-
schema: jsonSchema,
|
|
191
|
-
strict: true,
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
// Extract text content from the response
|
|
197
|
-
const rawText = response.choices[0]?.message.content || ''
|
|
198
|
-
|
|
199
|
-
// Parse the JSON response
|
|
200
|
-
let parsed: unknown
|
|
201
|
-
try {
|
|
202
|
-
parsed = JSON.parse(rawText)
|
|
203
|
-
} catch {
|
|
204
|
-
throw new Error(
|
|
205
|
-
`Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Transform null values to undefined to match original Zod schema expectations
|
|
210
|
-
// Grok returns null for optional fields we made nullable in the schema
|
|
211
|
-
const transformed = transformNullsToUndefined(parsed)
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
data: transformed,
|
|
215
|
-
rawText,
|
|
216
|
-
}
|
|
217
|
-
} catch (error: unknown) {
|
|
218
|
-
logger.errors('grok.structuredOutput fatal', {
|
|
219
|
-
error,
|
|
220
|
-
source: 'grok.structuredOutput',
|
|
221
|
-
})
|
|
222
|
-
throw error
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private async *processGrokStreamChunks(
|
|
227
|
-
stream: AsyncIterable<OpenAI_SDK.Chat.Completions.ChatCompletionChunk>,
|
|
228
|
-
options: TextOptions,
|
|
229
|
-
aguiState: {
|
|
230
|
-
runId: string
|
|
231
|
-
threadId: string
|
|
232
|
-
messageId: string
|
|
233
|
-
timestamp: number
|
|
234
|
-
hasEmittedRunStarted: boolean
|
|
235
|
-
},
|
|
236
|
-
logger: InternalLogger,
|
|
237
|
-
): AsyncIterable<StreamChunk> {
|
|
238
|
-
let accumulatedContent = ''
|
|
239
|
-
const timestamp = aguiState.timestamp
|
|
240
|
-
let hasEmittedTextMessageStart = false
|
|
241
|
-
|
|
242
|
-
// Track tool calls being streamed (arguments come in chunks)
|
|
243
|
-
const toolCallsInProgress = new Map<
|
|
244
|
-
number,
|
|
245
|
-
{
|
|
246
|
-
id: string
|
|
247
|
-
name: string
|
|
248
|
-
arguments: string
|
|
249
|
-
started: boolean // Track if TOOL_CALL_START has been emitted
|
|
250
|
-
}
|
|
251
|
-
>()
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
for await (const chunk of stream) {
|
|
255
|
-
logger.provider(`provider=grok`, { chunk })
|
|
256
|
-
const choice = chunk.choices[0]
|
|
257
|
-
|
|
258
|
-
if (!choice) continue
|
|
259
|
-
|
|
260
|
-
// Emit RUN_STARTED on first chunk
|
|
261
|
-
if (!aguiState.hasEmittedRunStarted) {
|
|
262
|
-
aguiState.hasEmittedRunStarted = true
|
|
263
|
-
yield asChunk({
|
|
264
|
-
type: 'RUN_STARTED',
|
|
265
|
-
runId: aguiState.runId,
|
|
266
|
-
threadId: aguiState.threadId,
|
|
267
|
-
model: chunk.model || options.model,
|
|
268
|
-
timestamp,
|
|
269
|
-
})
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const delta = choice.delta
|
|
273
|
-
const deltaContent = delta.content
|
|
274
|
-
const deltaToolCalls = delta.tool_calls
|
|
275
|
-
|
|
276
|
-
// Handle content delta
|
|
277
|
-
if (deltaContent) {
|
|
278
|
-
// Emit TEXT_MESSAGE_START on first text content
|
|
279
|
-
if (!hasEmittedTextMessageStart) {
|
|
280
|
-
hasEmittedTextMessageStart = true
|
|
281
|
-
yield asChunk({
|
|
282
|
-
type: 'TEXT_MESSAGE_START',
|
|
283
|
-
messageId: aguiState.messageId,
|
|
284
|
-
model: chunk.model || options.model,
|
|
285
|
-
timestamp,
|
|
286
|
-
role: 'assistant',
|
|
287
|
-
})
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
accumulatedContent += deltaContent
|
|
291
|
-
|
|
292
|
-
// Emit AG-UI TEXT_MESSAGE_CONTENT
|
|
293
|
-
yield asChunk({
|
|
294
|
-
type: 'TEXT_MESSAGE_CONTENT',
|
|
295
|
-
messageId: aguiState.messageId,
|
|
296
|
-
model: chunk.model || options.model,
|
|
297
|
-
timestamp,
|
|
298
|
-
delta: deltaContent,
|
|
299
|
-
content: accumulatedContent,
|
|
300
|
-
})
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Handle tool calls - they come in as deltas
|
|
304
|
-
if (deltaToolCalls) {
|
|
305
|
-
for (const toolCallDelta of deltaToolCalls) {
|
|
306
|
-
const index = toolCallDelta.index
|
|
307
|
-
|
|
308
|
-
// Initialize or update the tool call in progress
|
|
309
|
-
if (!toolCallsInProgress.has(index)) {
|
|
310
|
-
toolCallsInProgress.set(index, {
|
|
311
|
-
id: toolCallDelta.id || '',
|
|
312
|
-
name: toolCallDelta.function?.name || '',
|
|
313
|
-
arguments: '',
|
|
314
|
-
started: false,
|
|
315
|
-
})
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const toolCall = toolCallsInProgress.get(index)!
|
|
319
|
-
|
|
320
|
-
// Update with any new data from the delta
|
|
321
|
-
if (toolCallDelta.id) {
|
|
322
|
-
toolCall.id = toolCallDelta.id
|
|
323
|
-
}
|
|
324
|
-
if (toolCallDelta.function?.name) {
|
|
325
|
-
toolCall.name = toolCallDelta.function.name
|
|
326
|
-
}
|
|
327
|
-
if (toolCallDelta.function?.arguments) {
|
|
328
|
-
toolCall.arguments += toolCallDelta.function.arguments
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Emit TOOL_CALL_START when we have id and name
|
|
332
|
-
if (toolCall.id && toolCall.name && !toolCall.started) {
|
|
333
|
-
toolCall.started = true
|
|
334
|
-
yield asChunk({
|
|
335
|
-
type: 'TOOL_CALL_START',
|
|
336
|
-
toolCallId: toolCall.id,
|
|
337
|
-
toolCallName: toolCall.name,
|
|
338
|
-
toolName: toolCall.name,
|
|
339
|
-
model: chunk.model || options.model,
|
|
340
|
-
timestamp,
|
|
341
|
-
index,
|
|
342
|
-
})
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Emit TOOL_CALL_ARGS for argument deltas
|
|
346
|
-
if (toolCallDelta.function?.arguments && toolCall.started) {
|
|
347
|
-
yield asChunk({
|
|
348
|
-
type: 'TOOL_CALL_ARGS',
|
|
349
|
-
toolCallId: toolCall.id,
|
|
350
|
-
model: chunk.model || options.model,
|
|
351
|
-
timestamp,
|
|
352
|
-
delta: toolCallDelta.function.arguments,
|
|
353
|
-
})
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Handle finish reason
|
|
359
|
-
if (choice.finish_reason) {
|
|
360
|
-
// Emit all completed tool calls
|
|
361
|
-
if (
|
|
362
|
-
choice.finish_reason === 'tool_calls' ||
|
|
363
|
-
toolCallsInProgress.size > 0
|
|
364
|
-
) {
|
|
365
|
-
for (const [, toolCall] of toolCallsInProgress) {
|
|
366
|
-
// Parse arguments for TOOL_CALL_END
|
|
367
|
-
let parsedInput: unknown = {}
|
|
368
|
-
try {
|
|
369
|
-
parsedInput = toolCall.arguments
|
|
370
|
-
? JSON.parse(toolCall.arguments)
|
|
371
|
-
: {}
|
|
372
|
-
} catch {
|
|
373
|
-
parsedInput = {}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Emit AG-UI TOOL_CALL_END
|
|
377
|
-
yield asChunk({
|
|
378
|
-
type: 'TOOL_CALL_END',
|
|
379
|
-
toolCallId: toolCall.id,
|
|
380
|
-
toolCallName: toolCall.name,
|
|
381
|
-
toolName: toolCall.name,
|
|
382
|
-
model: chunk.model || options.model,
|
|
383
|
-
timestamp,
|
|
384
|
-
input: parsedInput,
|
|
385
|
-
})
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const computedFinishReason =
|
|
390
|
-
choice.finish_reason === 'tool_calls' ||
|
|
391
|
-
toolCallsInProgress.size > 0
|
|
392
|
-
? 'tool_calls'
|
|
393
|
-
: 'stop'
|
|
394
|
-
|
|
395
|
-
// Emit TEXT_MESSAGE_END if we had text content
|
|
396
|
-
if (hasEmittedTextMessageStart) {
|
|
397
|
-
yield asChunk({
|
|
398
|
-
type: 'TEXT_MESSAGE_END',
|
|
399
|
-
messageId: aguiState.messageId,
|
|
400
|
-
model: chunk.model || options.model,
|
|
401
|
-
timestamp,
|
|
402
|
-
})
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Emit AG-UI RUN_FINISHED
|
|
406
|
-
yield asChunk({
|
|
407
|
-
type: 'RUN_FINISHED',
|
|
408
|
-
runId: aguiState.runId,
|
|
409
|
-
threadId: aguiState.threadId,
|
|
410
|
-
model: chunk.model || options.model,
|
|
411
|
-
timestamp,
|
|
412
|
-
usage: chunk.usage
|
|
413
|
-
? {
|
|
414
|
-
promptTokens: chunk.usage.prompt_tokens || 0,
|
|
415
|
-
completionTokens: chunk.usage.completion_tokens || 0,
|
|
416
|
-
totalTokens: chunk.usage.total_tokens || 0,
|
|
417
|
-
}
|
|
418
|
-
: undefined,
|
|
419
|
-
finishReason: computedFinishReason,
|
|
420
|
-
})
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
} catch (error: unknown) {
|
|
424
|
-
const err = error as Error & { code?: string }
|
|
425
|
-
logger.errors('grok stream ended with error', {
|
|
426
|
-
error,
|
|
427
|
-
source: 'grok.processGrokStreamChunks',
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
// Emit AG-UI RUN_ERROR
|
|
431
|
-
yield asChunk({
|
|
432
|
-
type: 'RUN_ERROR',
|
|
433
|
-
runId: aguiState.runId,
|
|
434
|
-
model: options.model,
|
|
435
|
-
timestamp,
|
|
436
|
-
message: err.message || 'Unknown error occurred',
|
|
437
|
-
code: err.code,
|
|
438
|
-
error: {
|
|
439
|
-
message: err.message || 'Unknown error occurred',
|
|
440
|
-
code: err.code,
|
|
441
|
-
},
|
|
442
|
-
})
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* Maps common options to Grok-specific Chat Completions format
|
|
448
|
-
*/
|
|
449
|
-
private mapTextOptionsToGrok(
|
|
450
|
-
options: TextOptions,
|
|
451
|
-
): OpenAI_SDK.Chat.Completions.ChatCompletionCreateParamsStreaming {
|
|
452
|
-
const modelOptions = options.modelOptions as
|
|
453
|
-
| Omit<
|
|
454
|
-
InternalTextProviderOptions,
|
|
455
|
-
'max_tokens' | 'tools' | 'temperature' | 'input' | 'top_p'
|
|
456
|
-
>
|
|
457
|
-
| undefined
|
|
458
|
-
|
|
459
|
-
if (modelOptions) {
|
|
460
|
-
validateTextProviderOptions({
|
|
461
|
-
...modelOptions,
|
|
462
|
-
model: options.model,
|
|
463
|
-
})
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const tools = options.tools
|
|
467
|
-
? convertToolsToProviderFormat(options.tools)
|
|
468
|
-
: undefined
|
|
469
|
-
|
|
470
|
-
// Build messages array with system prompts
|
|
471
|
-
const messages: Array<OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam> =
|
|
472
|
-
[]
|
|
473
|
-
|
|
474
|
-
// Add system prompts first
|
|
475
|
-
if (options.systemPrompts && options.systemPrompts.length > 0) {
|
|
476
|
-
messages.push({
|
|
477
|
-
role: 'system',
|
|
478
|
-
content: options.systemPrompts.join('\n'),
|
|
479
|
-
})
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// Convert messages
|
|
483
|
-
for (const message of options.messages) {
|
|
484
|
-
messages.push(this.convertMessageToGrok(message))
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return {
|
|
488
|
-
model: options.model,
|
|
489
|
-
messages,
|
|
490
|
-
temperature: options.temperature,
|
|
491
|
-
max_tokens: options.maxTokens,
|
|
492
|
-
top_p: options.topP,
|
|
493
|
-
tools: tools as Array<OpenAI_SDK.Chat.Completions.ChatCompletionTool>,
|
|
494
|
-
stream: true,
|
|
495
|
-
stream_options: { include_usage: true },
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
private convertMessageToGrok(
|
|
500
|
-
message: ModelMessage,
|
|
501
|
-
): OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam {
|
|
502
|
-
// Handle tool messages
|
|
503
|
-
if (message.role === 'tool') {
|
|
504
|
-
return {
|
|
505
|
-
role: 'tool',
|
|
506
|
-
tool_call_id: message.toolCallId || '',
|
|
507
|
-
content:
|
|
508
|
-
typeof message.content === 'string'
|
|
509
|
-
? message.content
|
|
510
|
-
: JSON.stringify(message.content),
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// Handle assistant messages
|
|
515
|
-
if (message.role === 'assistant') {
|
|
516
|
-
const toolCalls = message.toolCalls?.map((tc) => ({
|
|
517
|
-
id: tc.id,
|
|
518
|
-
type: 'function' as const,
|
|
519
|
-
function: {
|
|
520
|
-
name: tc.function.name,
|
|
521
|
-
arguments:
|
|
522
|
-
typeof tc.function.arguments === 'string'
|
|
523
|
-
? tc.function.arguments
|
|
524
|
-
: JSON.stringify(tc.function.arguments),
|
|
525
|
-
},
|
|
526
|
-
}))
|
|
527
|
-
|
|
528
|
-
return {
|
|
529
|
-
role: 'assistant',
|
|
530
|
-
content: this.extractTextContent(message.content),
|
|
531
|
-
...(toolCalls && toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Handle user messages - support multimodal content
|
|
536
|
-
const contentParts = this.normalizeContent(message.content)
|
|
537
|
-
|
|
538
|
-
// If only text, use simple string format
|
|
539
|
-
if (contentParts.length === 1 && contentParts[0]?.type === 'text') {
|
|
540
|
-
return {
|
|
541
|
-
role: 'user',
|
|
542
|
-
content: contentParts[0].content,
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Otherwise, use array format for multimodal
|
|
547
|
-
const parts: Array<OpenAI_SDK.Chat.Completions.ChatCompletionContentPart> =
|
|
548
|
-
[]
|
|
549
|
-
for (const part of contentParts) {
|
|
550
|
-
if (part.type === 'text') {
|
|
551
|
-
parts.push({ type: 'text', text: part.content })
|
|
552
|
-
} else if (part.type === 'image') {
|
|
553
|
-
const imageMetadata = part.metadata as GrokImageMetadata | undefined
|
|
554
|
-
// For base64 data, construct a data URI using the mimeType from source
|
|
555
|
-
const imageValue = part.source.value
|
|
556
|
-
const imageUrl =
|
|
557
|
-
part.source.type === 'data' && !imageValue.startsWith('data:')
|
|
558
|
-
? `data:${part.source.mimeType};base64,${imageValue}`
|
|
559
|
-
: imageValue
|
|
560
|
-
parts.push({
|
|
561
|
-
type: 'image_url',
|
|
562
|
-
image_url: {
|
|
563
|
-
url: imageUrl,
|
|
564
|
-
detail: imageMetadata?.detail || 'auto',
|
|
565
|
-
},
|
|
566
|
-
})
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
return {
|
|
571
|
-
role: 'user',
|
|
572
|
-
content: parts.length > 0 ? parts : '',
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Normalizes message content to an array of ContentPart.
|
|
578
|
-
* Handles backward compatibility with string content.
|
|
579
|
-
*/
|
|
580
|
-
private normalizeContent(
|
|
581
|
-
content: string | null | Array<ContentPart>,
|
|
582
|
-
): Array<ContentPart> {
|
|
583
|
-
if (content === null) {
|
|
584
|
-
return []
|
|
585
|
-
}
|
|
586
|
-
if (typeof content === 'string') {
|
|
587
|
-
return [{ type: 'text', content: content }]
|
|
588
|
-
}
|
|
589
|
-
return content
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* Extracts text content from a content value that may be string, null, or ContentPart array.
|
|
594
|
-
*/
|
|
595
|
-
private extractTextContent(
|
|
596
|
-
content: string | null | Array<ContentPart>,
|
|
597
|
-
): string {
|
|
598
|
-
if (content === null) {
|
|
599
|
-
return ''
|
|
600
|
-
}
|
|
601
|
-
if (typeof content === 'string') {
|
|
602
|
-
return content
|
|
603
|
-
}
|
|
604
|
-
// It's an array of ContentPart
|
|
605
|
-
return content
|
|
606
|
-
.filter((p) => p.type === 'text')
|
|
607
|
-
.map((p) => p.content)
|
|
608
|
-
.join('')
|
|
59
|
+
super(withGrokDefaults(config), model, 'grok')
|
|
609
60
|
}
|
|
610
61
|
}
|
|
611
62
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { FunctionTool } from '../tools/function-tool'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Grok Text Provider Options
|
|
5
3
|
*
|
|
@@ -51,27 +49,7 @@ export interface GrokTextProviderOptions extends GrokBaseOptions {
|
|
|
51
49
|
stop?: string | Array<string>
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
/**
|
|
55
|
-
* Internal options interface for validation
|
|
56
|
-
* Used internally by the adapter
|
|
57
|
-
*/
|
|
58
|
-
export interface InternalTextProviderOptions extends GrokTextProviderOptions {
|
|
59
|
-
model: string
|
|
60
|
-
stream?: boolean
|
|
61
|
-
tools?: Array<FunctionTool>
|
|
62
|
-
}
|
|
63
|
-
|
|
64
52
|
/**
|
|
65
53
|
* External provider options (what users pass in)
|
|
66
54
|
*/
|
|
67
55
|
export type ExternalTextProviderOptions = GrokTextProviderOptions
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Validates text provider options
|
|
71
|
-
*/
|
|
72
|
-
export function validateTextProviderOptions(
|
|
73
|
-
_options: InternalTextProviderOptions,
|
|
74
|
-
): void {
|
|
75
|
-
// Basic validation can be added here if needed
|
|
76
|
-
// For now, Grok API will handle validation
|
|
77
|
-
}
|
package/src/tools/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
type ChatCompletionFunctionTool as FunctionTool,
|
|
3
|
+
convertFunctionToolToChatCompletionsFormat as convertFunctionToolToAdapterFormat,
|
|
4
|
+
convertToolsToChatCompletionsFormat as convertToolsToProviderFormat,
|
|
5
|
+
} from '@tanstack/openai-base'
|