@tanstack/ai-grok 0.0.1 → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +113 -28
- package/dist/esm/adapters/image.d.ts +77 -0
- package/dist/esm/adapters/image.js +63 -0
- package/dist/esm/adapters/image.js.map +1 -0
- package/dist/esm/adapters/summarize.d.ts +75 -0
- package/dist/esm/adapters/summarize.js +81 -0
- package/dist/esm/adapters/summarize.js.map +1 -0
- package/dist/esm/adapters/text.d.ts +96 -0
- package/dist/esm/adapters/text.js +300 -0
- package/dist/esm/adapters/text.js.map +1 -0
- package/dist/esm/image/image-provider-options.d.ts +66 -0
- package/dist/esm/image/image-provider-options.js +39 -0
- package/dist/esm/image/image-provider-options.js.map +1 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/message-types.d.ts +64 -0
- package/dist/esm/model-meta.d.ts +223 -0
- package/dist/esm/model-meta.js +47 -0
- package/dist/esm/model-meta.js.map +1 -0
- package/dist/esm/text/text-provider-options.d.ts +66 -0
- package/dist/esm/text/text-provider-options.js +6 -0
- package/dist/esm/text/text-provider-options.js.map +1 -0
- package/dist/esm/tools/function-tool.d.ts +15 -0
- package/dist/esm/tools/function-tool.js +27 -0
- package/dist/esm/tools/function-tool.js.map +1 -0
- package/dist/esm/tools/index.d.ts +2 -0
- package/dist/esm/tools/tool-converter.d.ts +7 -0
- package/dist/esm/tools/tool-converter.js +10 -0
- package/dist/esm/tools/tool-converter.js.map +1 -0
- package/dist/esm/utils/client.d.ts +18 -0
- package/dist/esm/utils/client.js +26 -0
- package/dist/esm/utils/client.js.map +1 -0
- package/dist/esm/utils/index.d.ts +2 -0
- package/dist/esm/utils/schema-converter.d.ts +24 -0
- package/dist/esm/utils/schema-converter.js +71 -0
- package/dist/esm/utils/schema-converter.js.map +1 -0
- package/package.json +50 -7
- package/src/adapters/image.ts +176 -0
- package/src/adapters/summarize.ts +174 -0
- package/src/adapters/text.ts +506 -0
- package/src/image/image-provider-options.ts +118 -0
- package/src/index.ts +55 -0
- package/src/message-types.ts +67 -0
- package/src/model-meta.ts +298 -0
- package/src/text/text-provider-options.ts +77 -0
- package/src/tools/function-tool.ts +45 -0
- package/src/tools/index.ts +5 -0
- package/src/tools/tool-converter.ts +17 -0
- package/src/utils/client.ts +45 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/schema-converter.ts +110 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import { BaseTextAdapter } from '@tanstack/ai/adapters'
|
|
2
|
+
import { validateTextProviderOptions } from '../text/text-provider-options'
|
|
3
|
+
import { convertToolsToProviderFormat } from '../tools'
|
|
4
|
+
import {
|
|
5
|
+
createGrokClient,
|
|
6
|
+
generateId,
|
|
7
|
+
getGrokApiKeyFromEnv,
|
|
8
|
+
makeGrokStructuredOutputCompatible,
|
|
9
|
+
transformNullsToUndefined,
|
|
10
|
+
} from '../utils'
|
|
11
|
+
import type {
|
|
12
|
+
GROK_CHAT_MODELS,
|
|
13
|
+
ResolveInputModalities,
|
|
14
|
+
ResolveProviderOptions,
|
|
15
|
+
} from '../model-meta'
|
|
16
|
+
import type {
|
|
17
|
+
StructuredOutputOptions,
|
|
18
|
+
StructuredOutputResult,
|
|
19
|
+
} from '@tanstack/ai/adapters'
|
|
20
|
+
import type OpenAI_SDK from 'openai'
|
|
21
|
+
import type {
|
|
22
|
+
ContentPart,
|
|
23
|
+
ModelMessage,
|
|
24
|
+
StreamChunk,
|
|
25
|
+
TextOptions,
|
|
26
|
+
} from '@tanstack/ai'
|
|
27
|
+
import type { InternalTextProviderOptions } from '../text/text-provider-options'
|
|
28
|
+
import type {
|
|
29
|
+
GrokImageMetadata,
|
|
30
|
+
GrokMessageMetadataByModality,
|
|
31
|
+
} from '../message-types'
|
|
32
|
+
import type { GrokClientConfig } from '../utils'
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configuration for Grok text adapter
|
|
36
|
+
*/
|
|
37
|
+
export interface GrokTextConfig extends GrokClientConfig {}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Alias for TextProviderOptions for external use
|
|
41
|
+
*/
|
|
42
|
+
export type { ExternalTextProviderOptions as GrokTextProviderOptions } from '../text/text-provider-options'
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Grok Text (Chat) Adapter
|
|
46
|
+
*
|
|
47
|
+
* Tree-shakeable adapter for Grok chat/text completion functionality.
|
|
48
|
+
* Uses OpenAI-compatible Chat Completions API (not Responses API).
|
|
49
|
+
*/
|
|
50
|
+
export class GrokTextAdapter<
|
|
51
|
+
TModel extends (typeof GROK_CHAT_MODELS)[number],
|
|
52
|
+
> extends BaseTextAdapter<
|
|
53
|
+
TModel,
|
|
54
|
+
ResolveProviderOptions<TModel>,
|
|
55
|
+
ResolveInputModalities<TModel>,
|
|
56
|
+
GrokMessageMetadataByModality
|
|
57
|
+
> {
|
|
58
|
+
readonly kind = 'text' as const
|
|
59
|
+
readonly name = 'grok' as const
|
|
60
|
+
|
|
61
|
+
private client: OpenAI_SDK
|
|
62
|
+
|
|
63
|
+
constructor(config: GrokTextConfig, model: TModel) {
|
|
64
|
+
super({}, model)
|
|
65
|
+
this.client = createGrokClient(config)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async *chatStream(
|
|
69
|
+
options: TextOptions<ResolveProviderOptions<TModel>>,
|
|
70
|
+
): AsyncIterable<StreamChunk> {
|
|
71
|
+
const requestParams = this.mapTextOptionsToGrok(options)
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const stream = await this.client.chat.completions.create({
|
|
75
|
+
...requestParams,
|
|
76
|
+
stream: true,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
yield* this.processGrokStreamChunks(stream, options)
|
|
80
|
+
} catch (error: unknown) {
|
|
81
|
+
const err = error as Error
|
|
82
|
+
console.error('>>> chatStream: Fatal error during response creation <<<')
|
|
83
|
+
console.error('>>> Error message:', err.message)
|
|
84
|
+
console.error('>>> Error stack:', err.stack)
|
|
85
|
+
console.error('>>> Full error:', err)
|
|
86
|
+
throw error
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generate structured output using Grok's JSON Schema response format.
|
|
92
|
+
* Uses stream: false to get the complete response in one call.
|
|
93
|
+
*
|
|
94
|
+
* Grok has strict requirements for structured output (via OpenAI-compatible API):
|
|
95
|
+
* - All properties must be in the `required` array
|
|
96
|
+
* - Optional fields should have null added to their type union
|
|
97
|
+
* - additionalProperties must be false for all objects
|
|
98
|
+
*
|
|
99
|
+
* The outputSchema is already JSON Schema (converted in the ai layer).
|
|
100
|
+
* We apply Grok-specific transformations for structured output compatibility.
|
|
101
|
+
*/
|
|
102
|
+
async structuredOutput(
|
|
103
|
+
options: StructuredOutputOptions<ResolveProviderOptions<TModel>>,
|
|
104
|
+
): Promise<StructuredOutputResult<unknown>> {
|
|
105
|
+
const { chatOptions, outputSchema } = options
|
|
106
|
+
const requestParams = this.mapTextOptionsToGrok(chatOptions)
|
|
107
|
+
|
|
108
|
+
// Apply Grok-specific transformations for structured output compatibility
|
|
109
|
+
const jsonSchema = makeGrokStructuredOutputCompatible(
|
|
110
|
+
outputSchema,
|
|
111
|
+
outputSchema.required || [],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const response = await this.client.chat.completions.create({
|
|
116
|
+
...requestParams,
|
|
117
|
+
stream: false,
|
|
118
|
+
response_format: {
|
|
119
|
+
type: 'json_schema',
|
|
120
|
+
json_schema: {
|
|
121
|
+
name: 'structured_output',
|
|
122
|
+
schema: jsonSchema,
|
|
123
|
+
strict: true,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Extract text content from the response
|
|
129
|
+
const rawText = response.choices[0]?.message.content || ''
|
|
130
|
+
|
|
131
|
+
// Parse the JSON response
|
|
132
|
+
let parsed: unknown
|
|
133
|
+
try {
|
|
134
|
+
parsed = JSON.parse(rawText)
|
|
135
|
+
} catch {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Transform null values to undefined to match original Zod schema expectations
|
|
142
|
+
// Grok returns null for optional fields we made nullable in the schema
|
|
143
|
+
const transformed = transformNullsToUndefined(parsed)
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
data: transformed,
|
|
147
|
+
rawText,
|
|
148
|
+
}
|
|
149
|
+
} catch (error: unknown) {
|
|
150
|
+
const err = error as Error
|
|
151
|
+
console.error('>>> structuredOutput: Error during response creation <<<')
|
|
152
|
+
console.error('>>> Error message:', err.message)
|
|
153
|
+
throw error
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async *processGrokStreamChunks(
|
|
158
|
+
stream: AsyncIterable<OpenAI_SDK.Chat.Completions.ChatCompletionChunk>,
|
|
159
|
+
options: TextOptions,
|
|
160
|
+
): AsyncIterable<StreamChunk> {
|
|
161
|
+
let accumulatedContent = ''
|
|
162
|
+
const timestamp = Date.now()
|
|
163
|
+
let responseId = generateId(this.name)
|
|
164
|
+
|
|
165
|
+
// Track tool calls being streamed (arguments come in chunks)
|
|
166
|
+
const toolCallsInProgress = new Map<
|
|
167
|
+
number,
|
|
168
|
+
{
|
|
169
|
+
id: string
|
|
170
|
+
name: string
|
|
171
|
+
arguments: string
|
|
172
|
+
}
|
|
173
|
+
>()
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
for await (const chunk of stream) {
|
|
177
|
+
responseId = chunk.id || responseId
|
|
178
|
+
const choice = chunk.choices[0]
|
|
179
|
+
|
|
180
|
+
if (!choice) continue
|
|
181
|
+
|
|
182
|
+
const delta = choice.delta
|
|
183
|
+
const deltaContent = delta.content
|
|
184
|
+
const deltaToolCalls = delta.tool_calls
|
|
185
|
+
|
|
186
|
+
// Handle content delta
|
|
187
|
+
if (deltaContent) {
|
|
188
|
+
accumulatedContent += deltaContent
|
|
189
|
+
yield {
|
|
190
|
+
type: 'content',
|
|
191
|
+
id: responseId,
|
|
192
|
+
model: chunk.model || options.model,
|
|
193
|
+
timestamp,
|
|
194
|
+
delta: deltaContent,
|
|
195
|
+
content: accumulatedContent,
|
|
196
|
+
role: 'assistant',
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Handle tool calls - they come in as deltas
|
|
201
|
+
if (deltaToolCalls) {
|
|
202
|
+
for (const toolCallDelta of deltaToolCalls) {
|
|
203
|
+
const index = toolCallDelta.index
|
|
204
|
+
|
|
205
|
+
// Initialize or update the tool call in progress
|
|
206
|
+
if (!toolCallsInProgress.has(index)) {
|
|
207
|
+
toolCallsInProgress.set(index, {
|
|
208
|
+
id: toolCallDelta.id || '',
|
|
209
|
+
name: toolCallDelta.function?.name || '',
|
|
210
|
+
arguments: '',
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const toolCall = toolCallsInProgress.get(index)!
|
|
215
|
+
|
|
216
|
+
// Update with any new data from the delta
|
|
217
|
+
if (toolCallDelta.id) {
|
|
218
|
+
toolCall.id = toolCallDelta.id
|
|
219
|
+
}
|
|
220
|
+
if (toolCallDelta.function?.name) {
|
|
221
|
+
toolCall.name = toolCallDelta.function.name
|
|
222
|
+
}
|
|
223
|
+
if (toolCallDelta.function?.arguments) {
|
|
224
|
+
toolCall.arguments += toolCallDelta.function.arguments
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Handle finish reason
|
|
230
|
+
if (choice.finish_reason) {
|
|
231
|
+
// Emit all completed tool calls
|
|
232
|
+
if (
|
|
233
|
+
choice.finish_reason === 'tool_calls' ||
|
|
234
|
+
toolCallsInProgress.size > 0
|
|
235
|
+
) {
|
|
236
|
+
for (const [index, toolCall] of toolCallsInProgress) {
|
|
237
|
+
yield {
|
|
238
|
+
type: 'tool_call',
|
|
239
|
+
id: responseId,
|
|
240
|
+
model: chunk.model || options.model,
|
|
241
|
+
timestamp,
|
|
242
|
+
index,
|
|
243
|
+
toolCall: {
|
|
244
|
+
id: toolCall.id,
|
|
245
|
+
type: 'function',
|
|
246
|
+
function: {
|
|
247
|
+
name: toolCall.name,
|
|
248
|
+
arguments: toolCall.arguments,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
yield {
|
|
256
|
+
type: 'done',
|
|
257
|
+
id: responseId,
|
|
258
|
+
model: chunk.model || options.model,
|
|
259
|
+
timestamp,
|
|
260
|
+
usage: chunk.usage
|
|
261
|
+
? {
|
|
262
|
+
promptTokens: chunk.usage.prompt_tokens || 0,
|
|
263
|
+
completionTokens: chunk.usage.completion_tokens || 0,
|
|
264
|
+
totalTokens: chunk.usage.total_tokens || 0,
|
|
265
|
+
}
|
|
266
|
+
: undefined,
|
|
267
|
+
finishReason:
|
|
268
|
+
choice.finish_reason === 'tool_calls' ||
|
|
269
|
+
toolCallsInProgress.size > 0
|
|
270
|
+
? 'tool_calls'
|
|
271
|
+
: 'stop',
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} catch (error: unknown) {
|
|
276
|
+
const err = error as Error & { code?: string }
|
|
277
|
+
console.log('[Grok Adapter] Stream ended with error:', err.message)
|
|
278
|
+
yield {
|
|
279
|
+
type: 'error',
|
|
280
|
+
id: responseId,
|
|
281
|
+
model: options.model,
|
|
282
|
+
timestamp,
|
|
283
|
+
error: {
|
|
284
|
+
message: err.message || 'Unknown error occurred',
|
|
285
|
+
code: err.code,
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Maps common options to Grok-specific Chat Completions format
|
|
293
|
+
*/
|
|
294
|
+
private mapTextOptionsToGrok(
|
|
295
|
+
options: TextOptions,
|
|
296
|
+
): OpenAI_SDK.Chat.Completions.ChatCompletionCreateParamsStreaming {
|
|
297
|
+
const modelOptions = options.modelOptions as
|
|
298
|
+
| Omit<
|
|
299
|
+
InternalTextProviderOptions,
|
|
300
|
+
'max_tokens' | 'tools' | 'temperature' | 'input' | 'top_p'
|
|
301
|
+
>
|
|
302
|
+
| undefined
|
|
303
|
+
|
|
304
|
+
if (modelOptions) {
|
|
305
|
+
validateTextProviderOptions({
|
|
306
|
+
...modelOptions,
|
|
307
|
+
model: options.model,
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const tools = options.tools
|
|
312
|
+
? convertToolsToProviderFormat(options.tools)
|
|
313
|
+
: undefined
|
|
314
|
+
|
|
315
|
+
// Build messages array with system prompts
|
|
316
|
+
const messages: Array<OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam> =
|
|
317
|
+
[]
|
|
318
|
+
|
|
319
|
+
// Add system prompts first
|
|
320
|
+
if (options.systemPrompts && options.systemPrompts.length > 0) {
|
|
321
|
+
messages.push({
|
|
322
|
+
role: 'system',
|
|
323
|
+
content: options.systemPrompts.join('\n'),
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Convert messages
|
|
328
|
+
for (const message of options.messages) {
|
|
329
|
+
messages.push(this.convertMessageToGrok(message))
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
model: options.model,
|
|
334
|
+
messages,
|
|
335
|
+
temperature: options.temperature,
|
|
336
|
+
max_tokens: options.maxTokens,
|
|
337
|
+
top_p: options.topP,
|
|
338
|
+
tools: tools as Array<OpenAI_SDK.Chat.Completions.ChatCompletionTool>,
|
|
339
|
+
stream: true,
|
|
340
|
+
stream_options: { include_usage: true },
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private convertMessageToGrok(
|
|
345
|
+
message: ModelMessage,
|
|
346
|
+
): OpenAI_SDK.Chat.Completions.ChatCompletionMessageParam {
|
|
347
|
+
// Handle tool messages
|
|
348
|
+
if (message.role === 'tool') {
|
|
349
|
+
return {
|
|
350
|
+
role: 'tool',
|
|
351
|
+
tool_call_id: message.toolCallId || '',
|
|
352
|
+
content:
|
|
353
|
+
typeof message.content === 'string'
|
|
354
|
+
? message.content
|
|
355
|
+
: JSON.stringify(message.content),
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Handle assistant messages
|
|
360
|
+
if (message.role === 'assistant') {
|
|
361
|
+
const toolCalls = message.toolCalls?.map((tc) => ({
|
|
362
|
+
id: tc.id,
|
|
363
|
+
type: 'function' as const,
|
|
364
|
+
function: {
|
|
365
|
+
name: tc.function.name,
|
|
366
|
+
arguments:
|
|
367
|
+
typeof tc.function.arguments === 'string'
|
|
368
|
+
? tc.function.arguments
|
|
369
|
+
: JSON.stringify(tc.function.arguments),
|
|
370
|
+
},
|
|
371
|
+
}))
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
role: 'assistant',
|
|
375
|
+
content: this.extractTextContent(message.content),
|
|
376
|
+
...(toolCalls && toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Handle user messages - support multimodal content
|
|
381
|
+
const contentParts = this.normalizeContent(message.content)
|
|
382
|
+
|
|
383
|
+
// If only text, use simple string format
|
|
384
|
+
if (contentParts.length === 1 && contentParts[0]?.type === 'text') {
|
|
385
|
+
return {
|
|
386
|
+
role: 'user',
|
|
387
|
+
content: contentParts[0].content,
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Otherwise, use array format for multimodal
|
|
392
|
+
const parts: Array<OpenAI_SDK.Chat.Completions.ChatCompletionContentPart> =
|
|
393
|
+
[]
|
|
394
|
+
for (const part of contentParts) {
|
|
395
|
+
if (part.type === 'text') {
|
|
396
|
+
parts.push({ type: 'text', text: part.content })
|
|
397
|
+
} else if (part.type === 'image') {
|
|
398
|
+
const imageMetadata = part.metadata as GrokImageMetadata | undefined
|
|
399
|
+
parts.push({
|
|
400
|
+
type: 'image_url',
|
|
401
|
+
image_url: {
|
|
402
|
+
url: part.source.value,
|
|
403
|
+
detail: imageMetadata?.detail || 'auto',
|
|
404
|
+
},
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
role: 'user',
|
|
411
|
+
content: parts.length > 0 ? parts : '',
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Normalizes message content to an array of ContentPart.
|
|
417
|
+
* Handles backward compatibility with string content.
|
|
418
|
+
*/
|
|
419
|
+
private normalizeContent(
|
|
420
|
+
content: string | null | Array<ContentPart>,
|
|
421
|
+
): Array<ContentPart> {
|
|
422
|
+
if (content === null) {
|
|
423
|
+
return []
|
|
424
|
+
}
|
|
425
|
+
if (typeof content === 'string') {
|
|
426
|
+
return [{ type: 'text', content: content }]
|
|
427
|
+
}
|
|
428
|
+
return content
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Extracts text content from a content value that may be string, null, or ContentPart array.
|
|
433
|
+
*/
|
|
434
|
+
private extractTextContent(
|
|
435
|
+
content: string | null | Array<ContentPart>,
|
|
436
|
+
): string {
|
|
437
|
+
if (content === null) {
|
|
438
|
+
return ''
|
|
439
|
+
}
|
|
440
|
+
if (typeof content === 'string') {
|
|
441
|
+
return content
|
|
442
|
+
}
|
|
443
|
+
// It's an array of ContentPart
|
|
444
|
+
return content
|
|
445
|
+
.filter((p) => p.type === 'text')
|
|
446
|
+
.map((p) => p.content)
|
|
447
|
+
.join('')
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Creates a Grok text adapter with explicit API key.
|
|
453
|
+
* Type resolution happens here at the call site.
|
|
454
|
+
*
|
|
455
|
+
* @param model - The model name (e.g., 'grok-3', 'grok-4')
|
|
456
|
+
* @param apiKey - Your xAI API key
|
|
457
|
+
* @param config - Optional additional configuration
|
|
458
|
+
* @returns Configured Grok text adapter instance with resolved types
|
|
459
|
+
*
|
|
460
|
+
* @example
|
|
461
|
+
* ```typescript
|
|
462
|
+
* const adapter = createGrokText('grok-3', "xai-...");
|
|
463
|
+
* // adapter has type-safe providerOptions for grok-3
|
|
464
|
+
* ```
|
|
465
|
+
*/
|
|
466
|
+
export function createGrokText<
|
|
467
|
+
TModel extends (typeof GROK_CHAT_MODELS)[number],
|
|
468
|
+
>(
|
|
469
|
+
model: TModel,
|
|
470
|
+
apiKey: string,
|
|
471
|
+
config?: Omit<GrokTextConfig, 'apiKey'>,
|
|
472
|
+
): GrokTextAdapter<TModel> {
|
|
473
|
+
return new GrokTextAdapter({ apiKey, ...config }, model)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Creates a Grok text adapter with automatic API key detection from environment variables.
|
|
478
|
+
* Type resolution happens here at the call site.
|
|
479
|
+
*
|
|
480
|
+
* Looks for `XAI_API_KEY` in:
|
|
481
|
+
* - `process.env` (Node.js)
|
|
482
|
+
* - `window.env` (Browser with injected env)
|
|
483
|
+
*
|
|
484
|
+
* @param model - The model name (e.g., 'grok-3', 'grok-4')
|
|
485
|
+
* @param config - Optional configuration (excluding apiKey which is auto-detected)
|
|
486
|
+
* @returns Configured Grok text adapter instance with resolved types
|
|
487
|
+
* @throws Error if XAI_API_KEY is not found in environment
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```typescript
|
|
491
|
+
* // Automatically uses XAI_API_KEY from environment
|
|
492
|
+
* const adapter = grokText('grok-3');
|
|
493
|
+
*
|
|
494
|
+
* const stream = chat({
|
|
495
|
+
* adapter,
|
|
496
|
+
* messages: [{ role: "user", content: "Hello!" }]
|
|
497
|
+
* });
|
|
498
|
+
* ```
|
|
499
|
+
*/
|
|
500
|
+
export function grokText<TModel extends (typeof GROK_CHAT_MODELS)[number]>(
|
|
501
|
+
model: TModel,
|
|
502
|
+
config?: Omit<GrokTextConfig, 'apiKey'>,
|
|
503
|
+
): GrokTextAdapter<TModel> {
|
|
504
|
+
const apiKey = getGrokApiKeyFromEnv()
|
|
505
|
+
return createGrokText(model, apiKey, config)
|
|
506
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Image Generation Provider Options
|
|
3
|
+
*
|
|
4
|
+
* These are provider-specific options for Grok image generation.
|
|
5
|
+
* Grok uses the grok-2-image-1212 model for image generation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Supported sizes for grok-2-image-1212 model
|
|
10
|
+
*/
|
|
11
|
+
export type GrokImageSize = '1024x1024' | '1536x1024' | '1024x1536'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Base provider options for Grok image models
|
|
15
|
+
*/
|
|
16
|
+
export interface GrokImageBaseProviderOptions {
|
|
17
|
+
/**
|
|
18
|
+
* A unique identifier representing your end-user.
|
|
19
|
+
* Can help xAI to monitor and detect abuse.
|
|
20
|
+
*/
|
|
21
|
+
user?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Provider options for grok-2-image-1212 model
|
|
26
|
+
*/
|
|
27
|
+
export interface GrokImageProviderOptions extends GrokImageBaseProviderOptions {
|
|
28
|
+
/**
|
|
29
|
+
* The quality of the image.
|
|
30
|
+
* @default 'standard'
|
|
31
|
+
*/
|
|
32
|
+
quality?: 'standard' | 'hd'
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The format in which generated images are returned.
|
|
36
|
+
* URLs are only valid for 60 minutes after generation.
|
|
37
|
+
* @default 'url'
|
|
38
|
+
*/
|
|
39
|
+
response_format?: 'url' | 'b64_json'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Type-only map from model name to its specific provider options.
|
|
44
|
+
*/
|
|
45
|
+
export type GrokImageModelProviderOptionsByName = {
|
|
46
|
+
'grok-2-image-1212': GrokImageProviderOptions
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Type-only map from model name to its supported sizes.
|
|
51
|
+
*/
|
|
52
|
+
export type GrokImageModelSizeByName = {
|
|
53
|
+
'grok-2-image-1212': GrokImageSize
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Internal options interface for validation
|
|
58
|
+
*/
|
|
59
|
+
interface ImageValidationOptions {
|
|
60
|
+
prompt: string
|
|
61
|
+
model: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Validates that the provided size is supported by the model.
|
|
66
|
+
* Throws a descriptive error if the size is not supported.
|
|
67
|
+
*/
|
|
68
|
+
export function validateImageSize(
|
|
69
|
+
model: string,
|
|
70
|
+
size: string | undefined,
|
|
71
|
+
): void {
|
|
72
|
+
if (!size) return
|
|
73
|
+
|
|
74
|
+
const validSizes: Record<string, Array<string>> = {
|
|
75
|
+
'grok-2-image-1212': ['1024x1024', '1536x1024', '1024x1536'],
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const modelSizes = validSizes[model]
|
|
79
|
+
if (!modelSizes) {
|
|
80
|
+
throw new Error(`Unknown image model: ${model}`)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!modelSizes.includes(size)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Size "${size}" is not supported by model "${model}". ` +
|
|
86
|
+
`Supported sizes: ${modelSizes.join(', ')}`,
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validates that the number of images is within bounds for the model.
|
|
93
|
+
*/
|
|
94
|
+
export function validateNumberOfImages(
|
|
95
|
+
_model: string,
|
|
96
|
+
numberOfImages: number | undefined,
|
|
97
|
+
): void {
|
|
98
|
+
if (numberOfImages === undefined) return
|
|
99
|
+
|
|
100
|
+
// grok-2-image-1212 supports 1-10 images per request
|
|
101
|
+
if (numberOfImages < 1 || numberOfImages > 10) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Number of images must be between 1 and 10. Requested: ${numberOfImages}`,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const validatePrompt = (options: ImageValidationOptions) => {
|
|
109
|
+
if (options.prompt.length === 0) {
|
|
110
|
+
throw new Error('Prompt cannot be empty.')
|
|
111
|
+
}
|
|
112
|
+
// Grok image model supports up to 4000 characters
|
|
113
|
+
if (options.prompt.length > 4000) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
'For grok-2-image-1212, prompt length must be less than or equal to 4000 characters.',
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// New Tree-Shakeable Adapters (Recommended)
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
// Text (Chat) adapter - for chat/text completion
|
|
6
|
+
export {
|
|
7
|
+
GrokTextAdapter,
|
|
8
|
+
createGrokText,
|
|
9
|
+
grokText,
|
|
10
|
+
type GrokTextConfig,
|
|
11
|
+
type GrokTextProviderOptions,
|
|
12
|
+
} from './adapters/text'
|
|
13
|
+
|
|
14
|
+
// Summarize adapter - for text summarization
|
|
15
|
+
export {
|
|
16
|
+
GrokSummarizeAdapter,
|
|
17
|
+
createGrokSummarize,
|
|
18
|
+
grokSummarize,
|
|
19
|
+
type GrokSummarizeConfig,
|
|
20
|
+
type GrokSummarizeProviderOptions,
|
|
21
|
+
type GrokSummarizeModel,
|
|
22
|
+
} from './adapters/summarize'
|
|
23
|
+
|
|
24
|
+
// Image adapter - for image generation
|
|
25
|
+
export {
|
|
26
|
+
GrokImageAdapter,
|
|
27
|
+
createGrokImage,
|
|
28
|
+
grokImage,
|
|
29
|
+
type GrokImageConfig,
|
|
30
|
+
type GrokImageModel,
|
|
31
|
+
} from './adapters/image'
|
|
32
|
+
export type {
|
|
33
|
+
GrokImageProviderOptions,
|
|
34
|
+
GrokImageModelProviderOptionsByName,
|
|
35
|
+
} from './image/image-provider-options'
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Type Exports
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
export type {
|
|
42
|
+
GrokChatModelProviderOptionsByName,
|
|
43
|
+
GrokModelInputModalitiesByName,
|
|
44
|
+
ResolveProviderOptions,
|
|
45
|
+
ResolveInputModalities,
|
|
46
|
+
} from './model-meta'
|
|
47
|
+
export { GROK_CHAT_MODELS, GROK_IMAGE_MODELS } from './model-meta'
|
|
48
|
+
export type {
|
|
49
|
+
GrokTextMetadata,
|
|
50
|
+
GrokImageMetadata,
|
|
51
|
+
GrokAudioMetadata,
|
|
52
|
+
GrokVideoMetadata,
|
|
53
|
+
GrokDocumentMetadata,
|
|
54
|
+
GrokMessageMetadataByModality,
|
|
55
|
+
} from './message-types'
|